diff --git a/src/NexusMods.EventSourcing.Abstractions/ACollectionAttribute.cs b/src/NexusMods.EventSourcing.Abstractions/ACollectionAttribute.cs index 6d840310..884db0a3 100644 --- a/src/NexusMods.EventSourcing.Abstractions/ACollectionAttribute.cs +++ b/src/NexusMods.EventSourcing.Abstractions/ACollectionAttribute.cs @@ -6,18 +6,29 @@ namespace NexusMods.EventSourcing.Abstractions; +/// +/// Abstract base class for an attribute that is a collection of entities. +/// +/// +/// +/// public class ACollectionAttribute(string name) : IAttribute where TOwner : IEntity { - public bool IsScalar => false; + /// public Type Owner => typeof(TOwner); + + /// public string Name => name; + + /// public IAccumulator CreateAccumulator() { return new Accumulator(); } - protected class Accumulator : IAccumulator + /// + private class Accumulator : IAccumulator { private HashSet _values = new(); public void Add(object value) @@ -50,5 +61,8 @@ public int ReadFrom(ref ReadOnlySpan reader, ISerializationRegistry regist } } + /// + /// The type of the items in the collection. + /// public Type Type => typeof(TType); } diff --git a/src/NexusMods.EventSourcing.Abstractions/AttributeDefinitions/TypeAttributeDefinition.cs b/src/NexusMods.EventSourcing.Abstractions/AttributeDefinitions/TypeAttributeDefinition.cs index 067706f2..f83d464f 100644 --- a/src/NexusMods.EventSourcing.Abstractions/AttributeDefinitions/TypeAttributeDefinition.cs +++ b/src/NexusMods.EventSourcing.Abstractions/AttributeDefinitions/TypeAttributeDefinition.cs @@ -9,7 +9,6 @@ namespace NexusMods.EventSourcing.Abstractions.AttributeDefinitions; /// /// An attribute for the type of an entity. /// -/// public class TypeAttributeDefinition : IAttribute> { /// diff --git a/src/NexusMods.EventSourcing.Abstractions/DependencyInjectionExtensions.cs b/src/NexusMods.EventSourcing.Abstractions/DependencyInjectionExtensions.cs index f5b31edb..417d9243 100644 --- a/src/NexusMods.EventSourcing.Abstractions/DependencyInjectionExtensions.cs +++ b/src/NexusMods.EventSourcing.Abstractions/DependencyInjectionExtensions.cs @@ -84,7 +84,7 @@ public static IServiceCollection AddEntity(this IServiceCollection collection } - EntityStructureRegistry.Register(new EntityDefinition(type, entityAttribute.UUID, entityAttribute.Revision)); + EntityStructureRegistry.Register(new EntityDefinition(type, entityAttribute.Uuid, entityAttribute.Revision)); return collection; } diff --git a/src/NexusMods.EventSourcing.Abstractions/EntityAttribute.cs b/src/NexusMods.EventSourcing.Abstractions/EntityAttribute.cs index b1624097..12c62feb 100644 --- a/src/NexusMods.EventSourcing.Abstractions/EntityAttribute.cs +++ b/src/NexusMods.EventSourcing.Abstractions/EntityAttribute.cs @@ -3,6 +3,9 @@ namespace NexusMods.EventSourcing.Abstractions; +/// +/// Marks a class as an entity, and sets the UUID and revision of the entity. +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class EntityAttribute : Attribute { @@ -11,13 +14,13 @@ public class EntityAttribute : Attribute /// the revision will cause the entity snapshots to be discarded, and regenerated /// during the next load. /// - /// + /// /// public EntityAttribute(string guid, ushort revision) { Span span = stackalloc byte[16]; Guid.Parse(guid).TryWriteBytes(span); - UUID = BinaryPrimitives.ReadUInt128BigEndian(span); + Uuid = BinaryPrimitives.ReadUInt128BigEndian(span); Revision = revision; } @@ -29,5 +32,5 @@ public EntityAttribute(string guid, ushort revision) /// /// The unique identifier of the entity *Type*. /// - public UInt128 UUID { get; } + public UInt128 Uuid { get; } } diff --git a/src/NexusMods.EventSourcing.Abstractions/EntityAttributeDefinition.cs b/src/NexusMods.EventSourcing.Abstractions/EntityAttributeDefinition.cs index 8d7c8e36..ce8cfaa0 100644 --- a/src/NexusMods.EventSourcing.Abstractions/EntityAttributeDefinition.cs +++ b/src/NexusMods.EventSourcing.Abstractions/EntityAttributeDefinition.cs @@ -4,7 +4,7 @@ namespace NexusMods.EventSourcing.Abstractions; /// /// A attribute that contains a lazy reference to another entity. The link can be updated via setting the entity Id, and -/// retrieved via the method. Think of this attribute like a foreign key in a database. +/// retrieved via the method. Think of this attribute like a foreign key in a database. /// /// /// @@ -51,9 +51,7 @@ public void Unlink(TContext context, EntityId owner) /// /// Gets the value of the attribute for the given entity. /// - /// /// - /// /// public TOther Get(TOwner owner) { @@ -63,7 +61,10 @@ public TOther Get(TOwner owner) throw new InvalidOperationException($"Attribute not found for {Name} on {Owner.Name} with id {owner.Id}"); } + /// public Type Owner => typeof(TOwner); + + /// public string Name => attrName; IAccumulator IAttribute.CreateAccumulator() { diff --git a/src/NexusMods.EventSourcing.Abstractions/EntityCollectionAttributeDefinition.cs b/src/NexusMods.EventSourcing.Abstractions/EntityCollectionAttributeDefinition.cs index a4ba52fa..b53d51cf 100644 --- a/src/NexusMods.EventSourcing.Abstractions/EntityCollectionAttributeDefinition.cs +++ b/src/NexusMods.EventSourcing.Abstractions/EntityCollectionAttributeDefinition.cs @@ -1,5 +1,6 @@ namespace NexusMods.EventSourcing.Abstractions; +/// public class EntityCollectionAttributeDefinition(string name) : ACollectionAttribute(name) where TOwner : IEntity where TEntity : IEntity { diff --git a/src/NexusMods.EventSourcing.Abstractions/EntityId.cs b/src/NexusMods.EventSourcing.Abstractions/EntityId.cs index 5757a33f..1d37f88d 100644 --- a/src/NexusMods.EventSourcing.Abstractions/EntityId.cs +++ b/src/NexusMods.EventSourcing.Abstractions/EntityId.cs @@ -5,11 +5,23 @@ namespace NexusMods.EventSourcing.Abstractions; +/// +/// A unique identifier for an . +/// [ValueObject] public readonly partial struct EntityId { + /// + /// Casts this to a of the specified type. + /// + /// + /// public EntityId Cast() where T : IEntity => new(this); + /// + /// Creates a random . + /// + /// public static EntityId NewId() { var guid = Guid.NewGuid(); @@ -19,8 +31,17 @@ public static EntityId NewId() return From(value); } + /// + /// Reads the from the specified . + /// + /// + /// public static EntityId From(ReadOnlySpan data) => new(BinaryPrimitives.ReadUInt128BigEndian(data)); + /// + /// Writes the to the specified . + /// + /// public void TryWriteBytes(Span span) { BinaryPrimitives.WriteUInt128BigEndian(span, Value); @@ -95,7 +116,7 @@ public override string ToString() /// /// Converts the to a of another type. /// - /// + /// /// - public EntityId Cast() where T : IEntity => new(Value); + public EntityId Cast() where TTo : IEntity => new(Value); } diff --git a/src/NexusMods.EventSourcing.Abstractions/EntityIdDefinition.cs b/src/NexusMods.EventSourcing.Abstractions/EntityIdDefinition.cs index d9e6f042..723e1114 100644 --- a/src/NexusMods.EventSourcing.Abstractions/EntityIdDefinition.cs +++ b/src/NexusMods.EventSourcing.Abstractions/EntityIdDefinition.cs @@ -6,6 +6,9 @@ namespace NexusMods.EventSourcing.Abstractions; +/// +/// A internal class used for creating secondary indexes on entity Ids, +/// public class EntityIdDefinition : IAttribute, IIndexableAttribute { /// @@ -31,6 +34,7 @@ public void WriteTo(Span span, EntityId value) BinaryPrimitives.WriteUInt128BigEndian(span, value.Value); } + /// public bool Equal(IAccumulator accumulator, EntityId val) { return ((EntityIdDefinitionAccumulator)accumulator).Id == val; @@ -61,10 +65,17 @@ public void WriteTo(Span span, IAccumulator accumulator) } } +/// +/// Accumulator for the attribute. +/// public class EntityIdDefinitionAccumulator : IAccumulator { + /// + /// The Id of the entity. + /// public EntityId Id; + /// public void WriteTo(IBufferWriter writer, ISerializationRegistry registry) { var span = writer.GetSpan(16); @@ -72,6 +83,7 @@ public void WriteTo(IBufferWriter writer, ISerializationRegistry registry) writer.Advance(16); } + /// public int ReadFrom(ref ReadOnlySpan data, ISerializationRegistry registry) { Id = EntityId.From(data); diff --git a/src/NexusMods.EventSourcing.Abstractions/EntityStructureRegistry.cs b/src/NexusMods.EventSourcing.Abstractions/EntityStructureRegistry.cs index 6088c4bd..f1a77297 100644 --- a/src/NexusMods.EventSourcing.Abstractions/EntityStructureRegistry.cs +++ b/src/NexusMods.EventSourcing.Abstractions/EntityStructureRegistry.cs @@ -60,9 +60,6 @@ public static IEnumerable AllIndexableAttributes() /// /// Registers an entity type in the global registry. /// - /// - /// - /// public static void Register(EntityDefinition definition) { _entityDefinitionsByType.TryAdd(definition.Type, definition); @@ -73,6 +70,7 @@ public static void Register(EntityDefinition definition) /// Returns all attributes for the given entity type. /// /// + /// /// public static bool TryGetAttributes(Type owner, [NotNullWhen(true)] out ConcurrentDictionary? result) { diff --git a/src/NexusMods.EventSourcing.Abstractions/IAttribute.cs b/src/NexusMods.EventSourcing.Abstractions/IAttribute.cs index b6392465..c550c88f 100644 --- a/src/NexusMods.EventSourcing.Abstractions/IAttribute.cs +++ b/src/NexusMods.EventSourcing.Abstractions/IAttribute.cs @@ -36,6 +36,6 @@ public interface IAttribute : IAttribute where TAccumulator : IAcc /// to lazily create accumulators. /// /// - public TAccumulator CreateAccumulator(); + public new TAccumulator CreateAccumulator(); } diff --git a/src/NexusMods.EventSourcing.Abstractions/IEntity.cs b/src/NexusMods.EventSourcing.Abstractions/IEntity.cs index 7bafdf93..95ddb0f6 100644 --- a/src/NexusMods.EventSourcing.Abstractions/IEntity.cs +++ b/src/NexusMods.EventSourcing.Abstractions/IEntity.cs @@ -21,7 +21,7 @@ public interface IEntity : INotifyPropertyChanged /// - /// The type descriptor for all entities. Emitted by the method. + /// The type descriptor for all entities. Emitted by .New on the EntityContext /// public static readonly TypeAttributeDefinition TypeAttribute = new(); diff --git a/src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs b/src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs index 0aea13b3..5ec1069b 100644 --- a/src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs +++ b/src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs @@ -46,7 +46,6 @@ public interface IEntityContext /// /// /// - /// /// /// /// diff --git a/src/NexusMods.EventSourcing.Abstractions/IEventContext.cs b/src/NexusMods.EventSourcing.Abstractions/IEventContext.cs index 5490cb6d..4df5bb56 100644 --- a/src/NexusMods.EventSourcing.Abstractions/IEventContext.cs +++ b/src/NexusMods.EventSourcing.Abstractions/IEventContext.cs @@ -13,7 +13,6 @@ public interface IEventContext /// Gets the accumulator for the given attribute definition, if the accumulator does not exist it will be created. If /// the context is not setup for this entityId then false will be returned and the accumulator should be ignored. /// - /// /// /// /// diff --git a/src/NexusMods.EventSourcing.Abstractions/IIndexableAttribute.cs b/src/NexusMods.EventSourcing.Abstractions/IIndexableAttribute.cs index ac621e14..f495ab4a 100644 --- a/src/NexusMods.EventSourcing.Abstractions/IIndexableAttribute.cs +++ b/src/NexusMods.EventSourcing.Abstractions/IIndexableAttribute.cs @@ -30,6 +30,7 @@ public interface IIndexableAttribute : IAttribute /// Writes the accumulator to the given span, which will be the size returned by . /// /// + /// public void WriteTo(Span span, IAccumulator accumulator); } @@ -42,7 +43,7 @@ public interface IIndexableAttribute : IAttribute public interface IIndexableAttribute : IIndexableAttribute { /// - /// Writes the accumulator to the given span, which will be the size returned by . + /// Writes the accumulator to the given span, which will be the size returned by . /// /// /// @@ -54,7 +55,6 @@ public interface IIndexableAttribute : IIndexableAttribute /// /// /// - /// /// bool Equal(IAccumulator accumulator, TVal val); } diff --git a/src/NexusMods.EventSourcing.Abstractions/ISingletonEntity.cs b/src/NexusMods.EventSourcing.Abstractions/ISingletonEntity.cs index 82d90bc9..30059338 100644 --- a/src/NexusMods.EventSourcing.Abstractions/ISingletonEntity.cs +++ b/src/NexusMods.EventSourcing.Abstractions/ISingletonEntity.cs @@ -5,5 +5,8 @@ namespace NexusMods.EventSourcing.Abstractions; /// public interface ISingletonEntity : IEntity { + /// + /// The singleton id of the entity. + /// public static virtual EntityId SingletonId { get; } } diff --git a/src/NexusMods.EventSourcing.Abstractions/IndexedMultiEntityAttributeDefinition.cs b/src/NexusMods.EventSourcing.Abstractions/IndexedMultiEntityAttributeDefinition.cs index 138655a4..ae6163b0 100644 --- a/src/NexusMods.EventSourcing.Abstractions/IndexedMultiEntityAttributeDefinition.cs +++ b/src/NexusMods.EventSourcing.Abstractions/IndexedMultiEntityAttributeDefinition.cs @@ -20,11 +20,20 @@ public class IndexedMultiEntityAttributeDefinition(string /// public string Name => name; + /// public IndexedMultiEntityAccumulator CreateAccumulator() { return new IndexedMultiEntityAccumulator(); } + /// + /// Adds a new entity to the collection. + /// + /// + /// + /// + /// + /// public void Add(TContext context, EntityId owner, TKey key, EntityId value) where TContext : IEventContext { @@ -61,6 +70,11 @@ public Dictionary> Get(TOwner entity) } } +/// +/// Accumulator for the attribute. +/// +/// +/// public class IndexedMultiEntityAccumulator : IAccumulator where TOther : AEntity where TKey : notnull @@ -68,6 +82,7 @@ public class IndexedMultiEntityAccumulator : IAccumulator internal Dictionary> _values = new(); internal Dictionary, TKey> _keys = new(); + /// public void WriteTo(IBufferWriter writer, ISerializationRegistry registry) { var getSpan = writer.GetSpan(2); @@ -81,6 +96,7 @@ public void WriteTo(IBufferWriter writer, ISerializationRegistry registry) } } + /// public int ReadFrom(ref ReadOnlySpan input, ISerializationRegistry registry) { var originalSize = input.Length; diff --git a/src/NexusMods.EventSourcing.Abstractions/MultiEntityAttributeDefinition.cs b/src/NexusMods.EventSourcing.Abstractions/MultiEntityAttributeDefinition.cs index c6f6a9ff..c21665c5 100644 --- a/src/NexusMods.EventSourcing.Abstractions/MultiEntityAttributeDefinition.cs +++ b/src/NexusMods.EventSourcing.Abstractions/MultiEntityAttributeDefinition.cs @@ -25,8 +25,6 @@ public class MultiEntityAttributeDefinition : IAttribute /// - /// - /// public MultiEntityAttributeDefinition(string name) { _name = name; @@ -58,7 +56,7 @@ public void Add(TContext context, EntityId owner, EntityId /// /// - /// + /// /// public void AddAll(TContext context, EntityId owner, EntityId[] values) where TContext : IEventContext @@ -129,6 +127,10 @@ public class MultiEntityAccumulator : IAccumulator /// public ReadOnlyObservableCollection? TransformedReadOnly = null; + /// + /// Initializes the accumulator + /// + /// public void Init(IEntityContext context) { if (Transformed != null) diff --git a/src/NexusMods.EventSourcing.Abstractions/ScalarAttribute.cs b/src/NexusMods.EventSourcing.Abstractions/ScalarAttribute.cs index 6f23b7df..d3b72cb5 100644 --- a/src/NexusMods.EventSourcing.Abstractions/ScalarAttribute.cs +++ b/src/NexusMods.EventSourcing.Abstractions/ScalarAttribute.cs @@ -59,9 +59,7 @@ public void Unset(TContext context, EntityId owner) /// /// Gets the value of the attribute for the given entity. /// - /// /// - /// /// public TType Get(TOwner owner) { diff --git a/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializationRegistry.cs b/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializationRegistry.cs index f6b91bd7..42913bf3 100644 --- a/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializationRegistry.cs +++ b/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializationRegistry.cs @@ -36,6 +36,7 @@ public interface ISerializationRegistry /// Deserializes the given bytes into the given type, returning the number of bytes read. /// /// + /// /// /// public int Deserialize(ReadOnlySpan bytes, out TVal value); diff --git a/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializer.cs b/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializer.cs index a1e5f46b..acb592cd 100644 --- a/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializer.cs +++ b/src/NexusMods.EventSourcing.Abstractions/Serialization/ISerializer.cs @@ -35,7 +35,7 @@ public interface IFixedSizeSerializer : ISerializer { /// /// Serialize the given value into the given span. The span will be exactly the size of the value returned - /// by . + /// by . /// /// /// diff --git a/src/NexusMods.EventSourcing/AEventStore.cs b/src/NexusMods.EventSourcing/AEventStore.cs index 66b41e01..14ae995a 100644 --- a/src/NexusMods.EventSourcing/AEventStore.cs +++ b/src/NexusMods.EventSourcing/AEventStore.cs @@ -7,6 +7,9 @@ namespace NexusMods.EventSourcing; +/// +/// Abstract event store implementation, provides helpers for serialization and deserialization. +/// public abstract class AEventStore : IEventStore { private readonly IVariableSizeSerializer _stringSerializer; @@ -14,6 +17,10 @@ public abstract class AEventStore : IEventStore private readonly ISerializationRegistry _serializationRegistry; private readonly PooledMemoryBufferWriter _writer; + /// + /// Abstract event store implementation, provides helpers for serialization and deserialization. + /// + /// public AEventStore(ISerializationRegistry serializationRegistry) { _stringSerializer = (serializationRegistry.GetSerializer(typeof(string)) as IVariableSizeSerializer)!; @@ -23,6 +30,14 @@ public AEventStore(ISerializationRegistry serializationRegistry) } + /// + /// Deserializes a snapshot into a definition and a list of attributes. + /// + /// + /// + /// + /// + /// protected bool DeserializeSnapshot(out IAccumulator loadedDefinition, out (IAttribute Attribute, IAccumulator Accumulator)[] loadedAttributes, ReadOnlySpan snapshot) { @@ -72,6 +87,12 @@ protected bool DeserializeSnapshot(out IAccumulator loadedDefinition, return true; } + /// + /// Serializes a snapshot into a byte span, the span will be valid until the next call to a serialize method. + /// + /// + /// + /// protected ReadOnlySpan SerializeSnapshot(EntityId id, IDictionary attributes) { _writer.Reset(); @@ -105,12 +126,17 @@ public abstract TransactionId Add(TEntity eventEntity, TColl ind where TEntity : IEvent where TColl : IList<(IIndexableAttribute, IAccumulator)>; + /// public abstract void EventsForIndex(IIndexableAttribute attr, TVal value, TIngester ingester, TransactionId fromTx, TransactionId toTx) where TIngester : IEventIngester; + /// public abstract TransactionId GetSnapshot(TransactionId asOf, EntityId entityId, out IAccumulator loadedDefinition, out (IAttribute Attribute, IAccumulator Accumulator)[] loadedAttributes); + /// public abstract void SetSnapshot(TransactionId txId, EntityId id, IDictionary attributes); + + /// public abstract TransactionId TxId { get; } } diff --git a/src/NexusMods.EventSourcing/EntityContext.cs b/src/NexusMods.EventSourcing/EntityContext.cs index d2afa966..30f1ab98 100644 --- a/src/NexusMods.EventSourcing/EntityContext.cs +++ b/src/NexusMods.EventSourcing/EntityContext.cs @@ -8,21 +8,29 @@ namespace NexusMods.EventSourcing; +/// +/// The primary way of interacting with the events. This class is thread safe, and can be used to lookup entities +/// and insert new events. +/// +/// public class EntityContext(IEventStore store) : IEntityContext { - public const int MaxEventsBeforeSnapshotting = 250; + /// + /// The maximum number of events that can be processed before a snapshot is taken. + /// + private const int MaxEventsBeforeSnapshotting = 250; - private TransactionId asOf = store.TxId; - private object _lock = new(); + private TransactionId _asOf = store.TxId; + private readonly object _lock = new(); - private IndexerIngester _indexerIngester = new(); - private List<(IIndexableAttribute, IAccumulator)> _indexUpdaters = new(); - private HashSet<(EntityId, string)> _updatedAttributes = new(); + private readonly IndexerIngester _indexerIngester = new(); + private readonly List<(IIndexableAttribute, IAccumulator)> _indexUpdaters = new(); + private readonly HashSet<(EntityId, string)> _updatedAttributes = new(); - private ConcurrentDictionary _entities = new(); - private ConcurrentDictionary> _values = new(); + private readonly ConcurrentDictionary _entities = new(); + private readonly ConcurrentDictionary> _values = new(); - private ObjectPool _definitionAccumulatorPool = + private readonly ObjectPool _definitionAccumulatorPool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); /// @@ -71,7 +79,7 @@ private Dictionary LoadAccumulators(EntityId id) { var values = new Dictionary(); - var snapshotTxId = store.GetSnapshot(asOf, id, out var loadedDefinition, out var loadedAttributes); + var snapshotTxId = store.GetSnapshot(_asOf, id, out var loadedDefinition, out var loadedAttributes); if (snapshotTxId != TransactionId.Min) { @@ -81,7 +89,7 @@ private Dictionary LoadAccumulators(EntityId id) } var ingester = new EntityContextIngester(values, id); - store.EventsForIndex(IEntity.EntityIdAttribute, id, ingester, snapshotTxId, asOf); + store.EventsForIndex(IEntity.EntityIdAttribute, id, ingester, snapshotTxId, _asOf); if (ingester.ProcessedEvents > MaxEventsBeforeSnapshotting) { @@ -115,6 +123,13 @@ public TEntity Get() where TEntity : ISingletonEntity return (TEntity)_entities[id]; } + /// + /// Adds a new event to the store, and updates the cache. This method is thread safe as the system is considered + /// single-writer, multiple-reader. + /// + /// + /// + /// public TransactionId Add(TEvent newEvent) where TEvent : IEvent { lock (_lock) @@ -155,7 +170,7 @@ public TransactionId Add(TEvent newEvent) where TEvent : IEvent } // Update the asOf transaction id - asOf = newId; + _asOf = newId; // Clear the updated attributes _updatedAttributes.Clear(); @@ -221,11 +236,19 @@ public void EmptyCaches() _values.Clear(); } + /// + /// Gets all the entities that have the given attribute set to the given value. + /// + /// + /// + /// + /// + /// public IEnumerable EntitiesForIndex(IIndexableAttribute attr, TVal val) where TEntity : IEntity { var foundEntities = new HashSet>(); - store.EventsForIndex(attr, val, new SecondaryIndexIngester(foundEntities), TransactionId.Min, asOf); + store.EventsForIndex(attr, val, new SecondaryIndexIngester(foundEntities), TransactionId.Min, _asOf); foreach (var entityId in foundEntities) { @@ -243,7 +266,7 @@ public IEnumerable EntitiesForIndex(IIndexableAttribute< } } - private struct SecondaryIndexIngester(HashSet> foundEntities) : IEventIngester, + private readonly struct SecondaryIndexIngester(HashSet> foundEntities) : IEventIngester, IEventContext where TType : IEntity { @@ -263,7 +286,5 @@ public bool GetAccumulator(EntityId en accumulator = default!; return false; } - - } } diff --git a/src/NexusMods.EventSourcing/EntityContextIngester.cs b/src/NexusMods.EventSourcing/EntityContextIngester.cs index b2ce94ad..cfe13b13 100644 --- a/src/NexusMods.EventSourcing/EntityContextIngester.cs +++ b/src/NexusMods.EventSourcing/EntityContextIngester.cs @@ -5,6 +5,11 @@ namespace NexusMods.EventSourcing; +/// +/// An ingester that can be used to ingest events into an entity context. +/// +/// +/// public class EntityContextIngester(Dictionary values, EntityId id) : IEventContext, IEventIngester { /// diff --git a/src/NexusMods.EventSourcing/ForwardEventContext.cs b/src/NexusMods.EventSourcing/ForwardEventContext.cs index 5a7982f1..b921cc25 100644 --- a/src/NexusMods.EventSourcing/ForwardEventContext.cs +++ b/src/NexusMods.EventSourcing/ForwardEventContext.cs @@ -9,7 +9,6 @@ namespace NexusMods.EventSourcing; /// An event context that forwards events to a dictionary of accumulators, and is tuned for adding new values to existing /// accumulators and entities. In other words is for moving a /// -/// public readonly struct ForwardEventContext(ConcurrentDictionary> trackedEntities, HashSet<(EntityId, string)> updatedAttributes) : IEventContext { /// diff --git a/src/NexusMods.EventSourcing/IndexerIngester.cs b/src/NexusMods.EventSourcing/IndexerIngester.cs index 80855a62..0e5dd4b0 100644 --- a/src/NexusMods.EventSourcing/IndexerIngester.cs +++ b/src/NexusMods.EventSourcing/IndexerIngester.cs @@ -4,19 +4,24 @@ namespace NexusMods.EventSourcing; +/// +/// An ingester that indexes all the attributes of all the entities in the transaction. +/// public class IndexerIngester : IEventIngester, IEventContext { - public Dictionary> IndexedAttributes = new(); + public readonly Dictionary> IndexedAttributes = new(); - public HashSet Ids = new(); + public readonly HashSet Ids = new(); + /// public bool Ingest(TransactionId id, IEvent @event) { @event.Apply(this); return true; } + /// public bool GetAccumulator(EntityId entityId, TAttribute attributeDefinition, out TAccumulator accumulator) where TOwner : IEntity where TAttribute : IAttribute where TAccumulator : IAccumulator { diff --git a/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs b/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs index cd1305ee..629c6b94 100644 --- a/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs +++ b/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs @@ -5,6 +5,9 @@ namespace NexusMods.EventSourcing; +/// +/// A IBufferWriter that uses pooled memory to reduce allocations. +/// public sealed class PooledMemoryBufferWriter : IBufferWriter { private IMemoryOwner _owner; @@ -12,6 +15,10 @@ public sealed class PooledMemoryBufferWriter : IBufferWriter private int _idx; private int _size; + /// + /// Constructs a new pooled memory buffer writer, with the given initial capacity. + /// + /// public PooledMemoryBufferWriter(int initialCapacity = 1024) { _owner = MemoryPool.Shared.Rent(initialCapacity); @@ -20,11 +27,18 @@ public PooledMemoryBufferWriter(int initialCapacity = 1024) _size = initialCapacity; } + /// + /// Resets the buffer writer, allowing it to be reused. + /// public void Reset() { _idx = 0; } + /// + /// Gets the written span. + /// + /// public ReadOnlySpan GetWrittenSpan() => _data.Span.SliceFast(0, _idx); private void Expand() @@ -39,6 +53,7 @@ private void Expand() } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int count) { @@ -47,6 +62,7 @@ public void Advance(int count) _idx += count; } + /// public Memory GetMemory(int sizeHint = 0) { if (_idx + sizeHint > _size) @@ -54,6 +70,7 @@ public Memory GetMemory(int sizeHint = 0) return _data[_idx..]; } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetSpan(int sizeHint = 0) { diff --git a/src/NexusMods.EventSourcing/Serialization/ArraySerializer.cs b/src/NexusMods.EventSourcing/Serialization/ArraySerializer.cs index fcc6528e..c1a1e386 100644 --- a/src/NexusMods.EventSourcing/Serialization/ArraySerializer.cs +++ b/src/NexusMods.EventSourcing/Serialization/ArraySerializer.cs @@ -7,19 +7,25 @@ namespace NexusMods.EventSourcing.Serialization; +/// +/// A serializer for arrays of any type supported by the registry. +/// public class GenericArraySerializer : IGenericSerializer { + /// public bool CanSerialize(Type valueType) { return false; } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = 0; return false; } + /// public bool TrySpecialize(Type baseType, Type[] argTypes, Func serializerFinder, [NotNullWhen(true)] out ISerializer? serializer) { if (!baseType.IsArray) @@ -40,16 +46,24 @@ public bool TrySpecialize(Type baseType, Type[] argTypes, Func).MakeGenericType(itemType, itemSerializer.GetType()); - serializer = (ISerializer) Activator.CreateInstance(type, itemSerializer, itemSize)!; + serializer = (ISerializer) Activator.CreateInstance(type, itemSerializer)!; return true; } } } +/// +/// Specialized serializer for arrays of a given type, where the type is of a fixed size. +/// +/// +/// +/// +/// public class FixedItemSizeArraySerializer(TItemSerializer itemSerializer, int itemSize) : IVariableSizeSerializer where TItemSerializer : IFixedSizeSerializer { + /// public bool CanSerialize(Type valueType) { if (!valueType.IsArray) @@ -60,12 +74,14 @@ public bool CanSerialize(Type valueType) return itemSerializer.CanSerialize(valueType.GetElementType()!); } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = 0; return false; } + /// public void Serialize(TItem[] value, TWriter output) where TWriter : IBufferWriter { var totalSize = sizeof(ushort) + (itemSize * value.Length); @@ -81,6 +97,7 @@ public void Serialize(TItem[] value, TWriter output) where TWriter : IB output.Advance(totalSize); } + /// public int Deserialize(ReadOnlySpan from, out TItem[] value) { var size = BinaryPrimitives.ReadUInt16BigEndian(from); @@ -97,10 +114,16 @@ public int Deserialize(ReadOnlySpan from, out TItem[] value) } } - -public class VariableItemSizeSerializer(TItemSerializer itemSerializer, int itemSize) : IVariableSizeSerializer +/// +/// Specialized serializer for arrays of a given type, where the type is of a variable size. +/// +/// +/// +/// +public class VariableItemSizeSerializer(TItemSerializer itemSerializer) : IVariableSizeSerializer where TItemSerializer : IVariableSizeSerializer { + /// public bool CanSerialize(Type valueType) { if (!valueType.IsArray) @@ -111,12 +134,14 @@ public bool CanSerialize(Type valueType) return itemSerializer.CanSerialize(valueType.GetElementType()!); } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = 0; return false; } + /// public void Serialize(TItem[] value, TWriter output) where TWriter : IBufferWriter { var span = output.GetSpan(sizeof(ushort)); @@ -129,6 +154,7 @@ public void Serialize(TItem[] value, TWriter output) where TWriter : IB } } + /// public int Deserialize(ReadOnlySpan from, out TItem[] value) { var size = BinaryPrimitives.ReadUInt16BigEndian(from); diff --git a/src/NexusMods.EventSourcing/Serialization/BinaryEventSerializer.cs b/src/NexusMods.EventSourcing/Serialization/BinaryEventSerializer.cs index d670a943..2a62499f 100644 --- a/src/NexusMods.EventSourcing/Serialization/BinaryEventSerializer.cs +++ b/src/NexusMods.EventSourcing/Serialization/BinaryEventSerializer.cs @@ -15,6 +15,9 @@ namespace NexusMods.EventSourcing.Serialization; using MemberDefinition = (ParameterInfo Ref, ParameterInfo Base, ParameterExpression Variable, ISerializer Serializer); +/// +/// Serializer for events, this assumes all events inherit from IEvent, and are records. Polymorphism is supported. +/// public sealed class BinaryEventSerializer : IEventSerializer, IVariableSizeSerializer { private readonly PooledMemoryBufferWriter _writer; @@ -30,6 +33,11 @@ public sealed class BinaryEventSerializer : IEventSerializer, IVariableSizeSeria private delegate int EventDeserializerDelegate(ReadOnlySpan data, out IEvent @event); + /// + /// DI Constructor. + /// + /// + /// public BinaryEventSerializer(ISerializationRegistry registry, IEnumerable eventDefinitions) { _serializerRegistry = registry; @@ -49,6 +57,7 @@ private void PopulateSerializers(EventDefinition[] eventDefinitions) } + /// public ReadOnlySpan Serialize(IEvent @event) { _writer.Reset(); @@ -56,6 +65,7 @@ public ReadOnlySpan Serialize(IEvent @event) return _writer.GetWrittenSpan(); } + /// public IEvent Deserialize(ReadOnlySpan data) { var id = BinaryPrimitives.ReadUInt128BigEndian(data); @@ -178,8 +188,6 @@ private EventDeserializerDelegate BuildVariableSizeDeserializer(EventDefinition var spanParam = Expression.Parameter(typeof(ReadOnlySpan)); - var ctorExpressions = new List(); - var offsetVariable = Expression.Variable(typeof(int), "offset"); var blockExprs = new List @@ -406,22 +414,26 @@ private EventSerializerDelegate BuildFixedSizeSerializer(EventDefinition eventDe } + /// public bool CanSerialize(Type valueType) { return valueType == typeof(IEvent); } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = 0; return false; } + /// public void Serialize(IEvent value, TWriter output) where TWriter : IBufferWriter { _serializerDelegates[value.GetType()](value); } + /// public int Deserialize(ReadOnlySpan from, out IEvent value) { var used = _deserializerDelegates[BinaryPrimitives.ReadUInt128BigEndian(from)](SliceFastStart(from, 16), out value); diff --git a/src/NexusMods.EventSourcing/Serialization/BoolSerializer.cs b/src/NexusMods.EventSourcing/Serialization/BoolSerializer.cs index dcd097a6..8a809fa9 100644 --- a/src/NexusMods.EventSourcing/Serialization/BoolSerializer.cs +++ b/src/NexusMods.EventSourcing/Serialization/BoolSerializer.cs @@ -3,24 +3,31 @@ namespace NexusMods.EventSourcing.Serialization; +/// +/// Serializer for bools. +/// public class BoolSerializer : IFixedSizeSerializer { + /// public bool CanSerialize(Type valueType) { return valueType == typeof(bool); } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = sizeof(bool); return valueType == typeof(bool); } + /// public void Serialize(bool value, Span output) { output[0] = value ? (byte)1 : (byte)0; } + /// public bool Deserialize(ReadOnlySpan from) { return from[0] == 1; diff --git a/src/NexusMods.EventSourcing/Serialization/EntityIdSerializer.cs b/src/NexusMods.EventSourcing/Serialization/EntityIdSerializer.cs index 04b547fa..3f544594 100644 --- a/src/NexusMods.EventSourcing/Serialization/EntityIdSerializer.cs +++ b/src/NexusMods.EventSourcing/Serialization/EntityIdSerializer.cs @@ -6,43 +6,56 @@ namespace NexusMods.EventSourcing.Serialization; +/// +/// Serializer for EntityIds. +/// public class EntityIdSerializer : IFixedSizeSerializer { + /// public bool CanSerialize(Type valueType) { return valueType == typeof(EntityId); } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = 16; return true; } + /// public void Serialize(EntityId value, Span output) { value.TryWriteBytes(output); } + /// public EntityId Deserialize(ReadOnlySpan from) { return EntityId.From(from); } } +/// +/// Serializer for typed EntityIds. +/// public class GenericEntityIdSerializer : IGenericSerializer { + /// public bool CanSerialize(Type valueType) { return false; } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = 0; return false; } + /// public bool TrySpecialize(Type baseType, Type[] argTypes, Func serializerFinder, [NotNullWhen(true)] out ISerializer? serializer) { if (baseType != typeof(EntityId<>) || argTypes.Length != 1) diff --git a/src/NexusMods.EventSourcing/Serialization/GuidSerializer.cs b/src/NexusMods.EventSourcing/Serialization/GuidSerializer.cs index 31d9c205..9b73cb30 100644 --- a/src/NexusMods.EventSourcing/Serialization/GuidSerializer.cs +++ b/src/NexusMods.EventSourcing/Serialization/GuidSerializer.cs @@ -3,24 +3,31 @@ namespace NexusMods.EventSourcing.Serialization; +/// +/// Serializer for Guids. +/// public sealed class GuidSerializer : IFixedSizeSerializer { + /// public bool CanSerialize(Type valueType) { return valueType == typeof(Guid); } + /// public bool TryGetFixedSize(Type valueType, out int size) { size = 16; return true; } + /// public void Serialize(Guid value, Span output) { value.TryWriteBytes(output); } + /// public Guid Deserialize(ReadOnlySpan from) { return new(from); diff --git a/src/NexusMods.EventSourcing/Serialization/SerializationRegistry.cs b/src/NexusMods.EventSourcing/Serialization/SerializationRegistry.cs index b3b448a2..7d83eeda 100644 --- a/src/NexusMods.EventSourcing/Serialization/SerializationRegistry.cs +++ b/src/NexusMods.EventSourcing/Serialization/SerializationRegistry.cs @@ -33,7 +33,6 @@ public SerializationRegistry(IEnumerable diInjectedSerializers) /// Gets a serializer that can serialize the given type. /// /// - /// Called when the cache needs to recursively create another type serializer /// /// public ISerializer GetSerializer(Type type)