Skip to content

Commit

Permalink
Start of the standard entity context
Browse files Browse the repository at this point in the history
  • Loading branch information
halgari committed Dec 14, 2023
1 parent e3d5241 commit f812802
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void Setup()
for (var e = 0; e < EntityCount; e++)
{
var evt = new CreateLoadout(EntityId<Loadout>.NewId(), $"Loadout {e}");
_eventStore.Add(evt).GetAwaiter().GetResult();
_eventStore.Add(evt);
_ids[e] = evt.Id;
}

Expand All @@ -75,7 +75,7 @@ public void Setup()
{
for (var e = 0; e < EntityCount; e++)
{
_eventStore.Add(new RenameLoadout(_ids[e], $"Loadout {e} {ev}")).GetAwaiter().GetResult();
_eventStore.Add(new RenameLoadout(_ids[e], $"Loadout {e} {ev}"));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public async Task WriteEvents()
for (var i = 0; i < EventCount; i++)
{
var evnt = _events[i % _events.Length];
await _eventStore.Add(evnt);
_eventStore.Add(evnt);
}
}
}
2 changes: 1 addition & 1 deletion src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public interface IEntityContext
/// <param name="entity"></param>
/// <typeparam name="TEvent"></typeparam>
/// <returns></returns>
public ValueTask Add<TEvent>(TEvent entity) where TEvent : IEvent;
public TransactionId Add<TEvent>(TEvent entity) where TEvent : IEvent;


/// <summary>
Expand Down
1 change: 1 addition & 0 deletions src/NexusMods.EventSourcing.Abstractions/IEventContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace NexusMods.EventSourcing.Abstractions;
/// </summary>
public interface IEventContext
{

/// <summary>
/// Emits a new value for the given attribute on the given entity
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/NexusMods.EventSourcing.Abstractions/IEventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace NexusMods.EventSourcing.Abstractions;

public interface IEventStore
{
public ValueTask Add<T>(T eventEntity) where T : IEvent;
public TransactionId Add<T>(T eventEntity) where T : IEvent;

public void EventsForEntity<TIngester>(EntityId entityId, TIngester ingester)
where TIngester : IEventIngester;
Expand Down
11 changes: 8 additions & 3 deletions src/NexusMods.EventSourcing.FasterKV/FasterKVEventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,15 @@ public async ValueTask Flush()
await _kvStore.CompleteCheckpointAsync();
}

public async ValueTask Add<T>(T eventEntity) where T : IEvent
public TransactionId Add<T>(T eventEntity) where T : IEvent
{
using var session = _kvStore.NewSession(_functions);
WriteInner(eventEntity, session);
lock (this)
{
_tx = _tx.Next();
using var session = _kvStore.NewSession(_functions);
WriteInner(eventEntity, session);
return _tx;
}
}

private void WriteInner<T>(T eventEntity, ClientSession<byte[], byte[], byte[], byte[], Empty, IFunctions<byte[], byte[], byte[], byte[], Empty>> session) where T : IEvent
Expand Down
82 changes: 74 additions & 8 deletions src/NexusMods.EventSourcing/EntityContext.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,94 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NexusMods.EventSourcing.Abstractions;

namespace NexusMods.EventSourcing;

public class EntityContext : IEntityContext
public class EntityContext(IEventStore store) : IEntityContext
{
private TransactionId asOf = TransactionId.From(0);
private object _lock = new object();

private ConcurrentDictionary<EntityId, IEntity> _entities = new();
private ConcurrentDictionary<EntityId, Dictionary<IAttribute, IAccumulator>> _values = new();


public TEntity Get<TEntity>(EntityId<TEntity> id) where TEntity : IEntity
{
throw new System.NotImplementedException();
if (_entities.TryGetValue(id.Value, out var entity))
{
return (TEntity) entity;
}

var values = GetValues(id.Value);
var type = (Type)values[IEntity.TypeAttribute].Get();

var newEntity = (TEntity)Activator.CreateInstance(type, this, id)!;
if (_entities.TryAdd(id.Value, newEntity))
{
return newEntity;
}

return (TEntity)_entities[id.Value];
}

public TEntity Get<TEntity>() where TEntity : ISingletonEntity
private Dictionary<IAttribute, IAccumulator> GetValues(EntityId id)
{
throw new System.NotImplementedException();
if (_values.TryGetValue(id, out var values))
{
return values;
}
var newValues = LoadValues(id);
return _values.TryAdd(id, newValues) ? newValues : _values[id];
}

public ValueTask Add<TEvent>(TEvent entity) where TEvent : IEvent

private Dictionary<IAttribute, IAccumulator> LoadValues(EntityId id)
{
throw new System.NotImplementedException();
var values = new Dictionary<IAttribute, IAccumulator>();
var ingester = new EntityContextIngester(values, id);
store.EventsForEntity(id, ingester);
return values;
}

public IAccumulator GetAccumulator<TOwner, TAttribute>(EntityId ownerId, TAttribute attributeDefinition) where TOwner : IEntity where TAttribute : IAttribute
public TEntity Get<TEntity>() where TEntity : ISingletonEntity
{
throw new System.NotImplementedException();
var id = TEntity.SingletonId;
if (_entities.TryGetValue(id, out var entity))
{
return (TEntity) entity;
}

var newEntity = (TEntity)Activator.CreateInstance(typeof(TEntity), this, id)!;
if (_entities.TryAdd(id, newEntity))
{
return newEntity;
}

return (TEntity)_entities[id];
}

public TransactionId Add<TEvent>(TEvent newEvent) where TEvent : IEvent
{
lock (_lock)
{
var newId = store.Add(newEvent);
asOf = newId;

var ingester = new ForwardEventContext(_values);
newEvent.Apply(ingester);

return newId;
}
}
public IAccumulator GetAccumulator<TOwner, TAttribute>(EntityId ownerId, TAttribute attributeDefinition)
where TOwner : IEntity where TAttribute : IAttribute
{
var values = GetValues(ownerId);
return values[attributeDefinition];

}
}
69 changes: 69 additions & 0 deletions src/NexusMods.EventSourcing/EntityContextIngester.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using NexusMods.EventSourcing.Abstractions;

namespace NexusMods.EventSourcing;

public struct EntityContextIngester(Dictionary<IAttribute, IAccumulator> values, EntityId id) : IEventContext, IEventIngester
{
public void Ingest(IEvent @event)
{
@event.Apply(this);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IAccumulator GetAccumulator<TAttribute>(TAttribute attributeDefinition)
where TAttribute : IAttribute
{
if (values.TryGetValue(attributeDefinition, out var accumulator))
{
return accumulator;
}

accumulator = attributeDefinition.CreateAccumulator();
values.Add(attributeDefinition, accumulator);
return accumulator;
}

public void Emit<TOwner, TVal>(EntityId<TOwner> entity, AttributeDefinition<TOwner, TVal> attr, TVal value) where TOwner : IEntity
{
if (entity.Value != id) return;

var accumulator = GetAccumulator(attr);
accumulator.Add(value!);
}

public void Emit<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr, EntityId<TVal> value) where TOwner : IEntity where TVal : IEntity
{
if (entity.Value != id) return;

var accumulator = GetAccumulator(attr);
accumulator.Add(value!);
}

public void Retract<TOwner, TVal>(EntityId<TOwner> entity, AttributeDefinition<TOwner, TVal> attr, TVal value) where TOwner : IEntity
{
if (entity.Value != id) return;

var accumulator = GetAccumulator(attr);
accumulator.Retract(value!);
}

public void Retract<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr, EntityId<TVal> value) where TOwner : IEntity where TVal : IEntity
{
if (entity.Value != id) return;

var accumulator = GetAccumulator(attr);
accumulator.Retract(value!);
}

public void New<TType>(EntityId<TType> newId) where TType : IEntity
{
if (newId.Value != id) return;

var accumulator = GetAccumulator(IEntity.TypeAttribute);
accumulator.Add(id);
}

}
53 changes: 53 additions & 0 deletions src/NexusMods.EventSourcing/ForwardEventContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using NexusMods.EventSourcing.Abstractions;

namespace NexusMods.EventSourcing;

public readonly struct ForwardEventContext(ConcurrentDictionary<EntityId, Dictionary<IAttribute, IAccumulator>> trackedValues) : IEventContext
{

private IAccumulator? GetAccumulator<TAttribute>(EntityId id, TAttribute attributeDefinition)
where TAttribute : IAttribute
{
if (!trackedValues.TryGetValue(id, out var values)) return null;

if (values.TryGetValue(attributeDefinition, out var accumulator))
{
return accumulator;
}

var newAccumulator = attributeDefinition.CreateAccumulator();
values.Add(attributeDefinition, newAccumulator);
return newAccumulator;
}

public void Emit<TOwner, TVal>(EntityId<TOwner> entity, AttributeDefinition<TOwner, TVal> attr, TVal value) where TOwner : IEntity
{
var accumulator = GetAccumulator(entity.Value, attr);
accumulator?.Add(value!);
}

public void Emit<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr, EntityId<TVal> value) where TOwner : IEntity where TVal : IEntity
{
var accumulator = GetAccumulator(entity.Value, attr);
accumulator?.Add(value!);
}

public void Retract<TOwner, TVal>(EntityId<TOwner> entity, AttributeDefinition<TOwner, TVal> attr, TVal value) where TOwner : IEntity
{
var accumulator = GetAccumulator(entity.Value, attr);
accumulator?.Retract(value!);
}

public void Retract<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr, EntityId<TVal> value) where TOwner : IEntity where TVal : IEntity
{
var accumulator = GetAccumulator(entity.Value, attr);
accumulator?.Retract(value!);
}

public void New<TType>(EntityId<TType> id) where TType : IEntity
{
// Do nothing, as this entity should be pulled fresh from the store when needed
}
}
15 changes: 10 additions & 5 deletions tests/NexusMods.EventSourcing.TestModel/InMemoryEventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,29 @@ namespace NexusMods.EventSourcing.TestModel;
public class InMemoryEventStore<TSerializer>(TSerializer serializer) : IEventStore
where TSerializer : IEventSerializer
{
private TransactionId _tx = TransactionId.From(0);
private readonly Dictionary<EntityId,IList<byte[]>> _events = new();

public async ValueTask Add<T>(T entity) where T : IEvent
public TransactionId Add<T>(T entity) where T : IEvent
{
var data = serializer.Serialize(entity);
var logger = new ModifiedEntityLogger();
entity.Apply(logger);
lock (_events)
lock (this)
{
_tx = _tx.Next();
var data = serializer.Serialize(entity);
var logger = new ModifiedEntityLogger();
entity.Apply(logger);
foreach (var id in logger.Entities)
{
if (!_events.TryGetValue(id, out var value))
{
value = new List<byte[]>();
_events.Add(id, value);
}

value.Add(data);
}

return _tx;
}
}

Expand Down
Loading

0 comments on commit f812802

Please sign in to comment.