Skip to content

Commit

Permalink
Refactoring ObjectSpace(s) to support Persistence Ignorance (PI)
Browse files Browse the repository at this point in the history
  • Loading branch information
HermanSchoenfeld committed Dec 13, 2024
1 parent 57d53d6 commit 8827b75
Show file tree
Hide file tree
Showing 27 changed files with 982 additions and 740 deletions.
10 changes: 10 additions & 0 deletions src/Hydrogen (Win).sln
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{AA4A8AEA-0
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HashLib4CSharp.Tests", "..\tests\HashLib4CSharp.Tests\HashLib4CSharp.Tests.csproj", "{9A1EF7C9-A988-0039-A0DB-D84F5F25BD0D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hydrogen.Generators", "Hydrogen.Generators\Hydrogen.Generators.csproj", "{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -404,6 +406,14 @@ Global
{9A1EF7C9-A988-0039-A0DB-D84F5F25BD0D}.Release|Any CPU.Build.0 = Release|Any CPU
{9A1EF7C9-A988-0039-A0DB-D84F5F25BD0D}.Release|x86.ActiveCfg = Release|Any CPU
{9A1EF7C9-A988-0039-A0DB-D84F5F25BD0D}.Release|x86.Build.0 = Release|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Debug|x86.ActiveCfg = Debug|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Debug|x86.Build.0 = Debug|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Release|Any CPU.Build.0 = Release|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Release|x86.ActiveCfg = Release|Any CPU
{E4B09863-F1C9-4AEC-83CA-DAD3D13A7478}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
14 changes: 14 additions & 0 deletions src/Hydrogen.Generators/Class1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.ComponentModel;

namespace Hydrogen.Generators {

public interface IObjectSpaceObject : INotifyPropertyChanging, INotifyPropertyChanged {
bool Dirty { get; set; }
}

public class Class1 : INotifyPropertyChanging, INotifyPropertyChanged {

public event PropertyChangingEventHandler? PropertyChanging;
public event PropertyChangedEventHandler? PropertyChanged;
}
}
9 changes: 9 additions & 0 deletions src/Hydrogen.Generators/Hydrogen.Generators.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public interface IObjectSpaceDimensionBuilder {

IObjectSpaceDimensionBuilder WithUniqueIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull);

IObjectSpaceDimensionBuilder WithChangeTrackingVia(Member member);

IObjectSpaceDimensionBuilder OptimizeAssumingAverageItemSize(int bytes);

ObjectSpaceDefinition.DimensionDefinition BuildDefinition();
Expand Down
11 changes: 9 additions & 2 deletions src/Hydrogen/ObjectSpaces/Builder/ObjectSpaceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class ObjectSpaceBuilder {
private ComparerFactory _comparerFactory;
private bool _usingCustomComparerFactory;
private bool _specifiedCustomComparer;
private bool _autoSave;

public ObjectSpaceBuilder() {
_type = null;
Expand All @@ -64,6 +65,8 @@ public ObjectSpaceBuilder() {
_comparerFactory = new ComparerFactory(ComparerFactory.Default);
_usingCustomComparerFactory = false;
_specifiedCustomComparer = false;

_autoSave = false;
}

public ObjectSpaceBuilder UseFile(string filePath) {
Expand Down Expand Up @@ -125,6 +128,11 @@ public ObjectSpaceBuilder WithEndianness(Endianness endianness) {
return this;
}

public ObjectSpaceBuilder AutoSave() {
_autoSave = true;
return this;
}

public ObjectSpaceBuilder Merkleized() {
_merkleized = true;
foreach(var dimension in _dimensions) {
Expand Down Expand Up @@ -252,8 +260,7 @@ public ObjectSpaceDimensionBuilder<T> Configure<T>() {
}

public ObjectSpaceDefinition BuildDefinition() {
var definition = new ObjectSpaceDefinition {
Merkleized = _merkleized,
var definition = new ObjectSpaceDefinition(_merkleized, _autoSave) {
HashFunction = _hashFunction,
Dimensions = _dimensions.Select(x => x.BuildDefinition()).ToArray()
};
Expand Down
14 changes: 13 additions & 1 deletion src/Hydrogen/ObjectSpaces/Builder/ObjectSpaceDimensionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ public class ObjectSpaceDimensionBuilder<T> : IObjectSpaceDimensionBuilder {
protected readonly ObjectSpaceBuilder _parent;
private readonly IList<ObjectSpaceDefinition.IndexDefinition> _indexes;
private int? _averageItemSize;
private ObjectChangeTracker _changeTracker;

public ObjectSpaceDimensionBuilder(ObjectSpaceBuilder parent) {
_parent = parent;
_averageItemSize = null;
_indexes = new List<ObjectSpaceDefinition.IndexDefinition>();
_changeTracker = ObjectChangeTracker.Default;

// Add recyclable free index store by default
WithRecyclableIndexes();
Expand Down Expand Up @@ -127,6 +129,7 @@ public ObjectSpaceDimensionBuilder<T> WithUniqueIndexOn<TMember>(Expression<Func
public ObjectSpaceDimensionBuilder<T> WithUniqueIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull)
=> (ObjectSpaceDimensionBuilder<T>)((IObjectSpaceDimensionBuilder)this).WithUniqueIndexOn(member, indexName, nullPolicy);


IObjectSpaceDimensionBuilder IObjectSpaceDimensionBuilder.WithUniqueIndexOn(Member member, string indexName = null, IndexNullPolicy nullPolicy = IndexNullPolicy.IgnoreNull) {
Guard.ArgumentNotNull(member, nameof(member));
Guard.Argument(member.DeclaringType == typeof(T), nameof(member), $"Not a member of {typeof(T).ToStringCS()}");
Expand Down Expand Up @@ -160,6 +163,14 @@ public ObjectSpaceDimensionBuilder<T> Merkleized(string indexName = null) {
return this;
}

public ObjectSpaceDimensionBuilder<T> WithChangeTrackingVia<TMember>(Expression<Func<T, TMember>> memberExpression)
=> (ObjectSpaceDimensionBuilder<T>)WithChangeTrackingVia(memberExpression.ToMember());

public IObjectSpaceDimensionBuilder WithChangeTrackingVia(Member member) {
_changeTracker = new ObjectChangeTracker(member);
return this;
}

public ObjectSpaceDimensionBuilder<T> OptimizeAssumingAverageItemSize(int bytes) {
_averageItemSize = bytes;
return this;
Expand All @@ -175,7 +186,8 @@ public ObjectSpaceDefinition.DimensionDefinition BuildDefinition() {
return new ObjectSpaceDefinition.DimensionDefinition {
ObjectType = typeof(T),
AverageObjectSizeBytes = _averageItemSize,
Indexes = _indexes.ToArray()
Indexes = _indexes.ToArray(),
ChangeTracker = _changeTracker
};
}

Expand Down
6 changes: 4 additions & 2 deletions src/Hydrogen/ObjectSpaces/FileObjectSpace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class FileObjectSpace : ObjectSpaceBase, ITransactionalObject{
private readonly TransactionalStream _fileStream;

public FileObjectSpace(HydrogenFileDescriptor file, ObjectSpaceDefinition objectSpaceDefinition, SerializerFactory serializerFactory, ComparerFactory comparerFactory, FileAccessMode accessMode = FileAccessMode.Default)
: base(CreateStreams(file, objectSpaceDefinition.Merkleized, accessMode, out var fileStream), objectSpaceDefinition, serializerFactory, comparerFactory, accessMode) {
: base(CreateStreams(file, objectSpaceDefinition.Traits.HasFlag(ObjectSpaceTraits.Merkleized), accessMode, out var fileStream), objectSpaceDefinition, serializerFactory, comparerFactory, accessMode) {
Guard.ArgumentNotNull(file, nameof(file));
Guard.ArgumentNotNull(objectSpaceDefinition, nameof(objectSpaceDefinition));
Guard.ArgumentNotNull(serializerFactory, nameof(serializerFactory));
Expand All @@ -30,6 +30,8 @@ public FileObjectSpace(HydrogenFileDescriptor file, ObjectSpaceDefinition object
File = file;
AccessMode = accessMode;

FlushOnDispose = false; // this flushes on Commiting event, on Dispose we don't want to risk changing the file (Rollback scenario)

// Create the file stream
_fileStream = fileStream;
SubscribeToFileStreamEvents();
Expand Down Expand Up @@ -60,7 +62,7 @@ private static ClusteredStreams CreateStreams(HydrogenFileDescriptor file, bool

public void Commit() {
using (EnterAccessScope()) {
// flush all cached changes
// flush all changed (stored in uncommitted pages)
_fileStream.Commit();
}
}
Expand Down
28 changes: 18 additions & 10 deletions src/Hydrogen/ObjectSpaces/InstanceTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@

using System;
using System.Collections.Generic;
using System.Linq;

namespace Hydrogen.ObjectSpaces;

/// <summary>
/// Used to track instances of fetched objects within an <see cref="ObjectSpace"/>.
/// Used to track instances of fetched objects within an <see cref="ObjectSpaceBase"/>.
/// </summary>
/// <remarks>Not thread-safe by design</remarks>
internal class InstanceTracker {

private readonly Dictionary<Type, BijectiveDictionary<long, object>> _objects;
private readonly Dictionary<Type, BijectiveDictionary<long, object>> _objectsByType;

public InstanceTracker() {
_objects = new Dictionary<Type, BijectiveDictionary<long, object>>(TypeEquivalenceComparer.Instance);
_objectsByType = new Dictionary<Type, BijectiveDictionary<long, object>>(TypeEquivalenceComparer.Instance);
}


Expand All @@ -40,7 +41,7 @@ public bool TryGet<TItem>(long index, out TItem item) {
}

public bool TryGet(Type itemType, long index, out object item) {
if (!_objects.TryGetValue(itemType, out var instances)) {
if (!_objectsByType.TryGetValue(itemType, out var instances)) {
item = default;
return false;
}
Expand All @@ -51,13 +52,20 @@ public bool TryGet(Type itemType, long index, out object item) {
return true;
}

public IEnumerable<object> GetInstances()
=> _objectsByType.Values.SelectMany(instances => instances.Values);


public IEnumerable<object> GetInstances(Type itemType)
=> _objectsByType.TryGetValue(itemType, out var instances) ? instances.Values : Array.Empty<object>();


public void Track(object item, long index) {
var itemType = item.GetType();

if (!_objects.TryGetValue(itemType, out var instances)) {
if (!_objectsByType.TryGetValue(itemType, out var instances)) {
instances = CreateInstanceDictionary();
_objects.Add(itemType, instances);
_objectsByType.Add(itemType, instances);
}

if (instances.TryGetValue(index, out var _))
Expand All @@ -69,12 +77,12 @@ public void Track(object item, long index) {
public void Untrack(object item) {
var itemType = item.GetType();

if (!_objects.TryGetValue(itemType, out var instances) || !instances.TryGetKey(item, out var index))
if (!_objectsByType.TryGetValue(itemType, out var instances) || !instances.TryGetKey(item, out var index))
throw new InvalidOperationException("Object instance was not tracked");

instances.Remove(index);
if (instances.Count == 0)
_objects.Remove(itemType);
_objectsByType.Remove(itemType);
}

public long GetIndexOf(object item) {
Expand All @@ -86,7 +94,7 @@ public long GetIndexOf(object item) {
public bool TryGetIndexOf(object item, out long index) {
var itemType = item.GetType();

if (!_objects.TryGetValue(itemType, out var instances)) {
if (!_objectsByType.TryGetValue(itemType, out var instances)) {
index = default;
return false;
}
Expand All @@ -98,7 +106,7 @@ public bool TryGetIndexOf(object item, out long index) {
}

public void Clear() {
_objects.Clear();
_objectsByType.Clear();
}

private BijectiveDictionary<long, object> CreateInstanceDictionary() => new(EqualityComparer<long>.Default, ReferenceEqualityComparer.Instance);
Expand Down
2 changes: 1 addition & 1 deletion src/Hydrogen/ObjectSpaces/MemoryObjectSpace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public MemoryObjectSpace(
int clusterSize = HydrogenDefaults.ClusterSize,
ClusteredStreamsPolicy clusteredStreamsPolicy = HydrogenDefaults.ContainerPolicy,
Endianness endianness = HydrogenDefaults.Endianness
) : base(CreateStreams(memoryStream, clusterSize, clusteredStreamsPolicy, endianness, objectSpaceDefinition.Merkleized), objectSpaceDefinition, serializerFactory, comparerFactory) {
) : base(CreateStreams(memoryStream, clusterSize, clusteredStreamsPolicy, endianness, objectSpaceDefinition.Traits.HasFlag(ObjectSpaceTraits.Merkleized)), objectSpaceDefinition, serializerFactory, comparerFactory) {
Guard.ArgumentNotNull(objectSpaceDefinition, nameof(objectSpaceDefinition));
Guard.ArgumentNotNull(serializerFactory, nameof(serializerFactory));
Guard.ArgumentNotNull(comparerFactory, nameof(comparerFactory));
Expand Down
44 changes: 44 additions & 0 deletions src/Hydrogen/ObjectSpaces/ObjectChangeTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using Hydrogen.Mapping;

namespace Hydrogen.ObjectSpaces;

internal class ObjectChangeTracker {

public ObjectChangeTracker() {
HasChanged = _ => false;;
SetChanged = (_, _) => { };
SupportsChangeTracking = false;
}

public ObjectChangeTracker(Member member)
: this(Guard.EnsureCast<PropertyMember>(member, $"Must be a {nameof(PropertyMember)}")) {
}

public ObjectChangeTracker(PropertyMember member) {
Guard.ArgumentNotNull(member, nameof(member));
Guard.Ensure(member.CanRead, "Member must have public getter");
Guard.Ensure(member.CanWrite, "Member must have public setter");
Guard.Ensure(member.PropertyType == typeof(bool), "Member must be of type bool");
HasChanged = obj => (bool)member.GetValue(obj);
SetChanged = (obj, val) => member.SetValue(obj, val);
SupportsChangeTracking = true;
}

public ObjectChangeTracker(Func<object, bool> hasChanged, Action<object, bool> setChanged) {
Guard.ArgumentNotNull(hasChanged, nameof(hasChanged));
Guard.ArgumentNotNull(setChanged, nameof(setChanged));
HasChanged = hasChanged;
SetChanged = setChanged;
SupportsChangeTracking = true;
}

public bool SupportsChangeTracking { get; }

public Func<object, bool> HasChanged { get; }

public Action<object, bool> SetChanged { get; }

public static ObjectChangeTracker Default { get; } = new ();

}
Loading

0 comments on commit 8827b75

Please sign in to comment.