Skip to content

Commit

Permalink
Can retract attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
halgari committed Dec 13, 2023
1 parent 4c689c2 commit 3687b0c
Show file tree
Hide file tree
Showing 16 changed files with 189 additions and 27 deletions.
22 changes: 21 additions & 1 deletion src/NexusMods.EventSourcing.Abstractions/ACollectionAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace NexusMods.EventSourcing.Abstractions;

Expand All @@ -10,7 +11,26 @@ public class ACollectionAttribute<TOwner, TType>(string name) : IAttribute
public string Name => name;
public IAccumulator CreateAccumulator()
{
throw new NotImplementedException();
return new Accumulator();
}

protected class Accumulator : IAccumulator
{
private HashSet<TType> _values = new();
public void Add(object value)
{
_values.Add((TType) value);
}

public void Retract(object value)
{
_values.Remove((TType) value);
}

public object Get()
{
return _values;
}
}

public Type Type => typeof(TType);
Expand Down
5 changes: 5 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/AScalarAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public void Add(object value)
_value = (TVal) value;
}

public void Retract(object value)
{
_value = default!;
}

public object Get()
{
return _value!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ public class AttributeDefinition<TOwner, TType>(string attrName) : AScalarAttrib
/// <param name="owner"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public TType Get(TOwner owner) => (TType)owner.Context.GetAccumulator(owner.Id, this).Get();
public TType Get(TOwner owner) => (TType)owner.Context.GetAccumulator<TOwner, AttributeDefinition<TOwner, TType>>(owner.Id, this).Get();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ public class EntityAttributeDefinition<TOwner, TType>(string attrName) : Attribu
where TOwner : AEntity
where TType : IEntity
{
public TType GetEntity(TOwner owner) => throw new NotImplementedException();
public TType GetEntity(TOwner owner)
{
var accumulator = owner.Context.GetAccumulator<TOwner, EntityAttributeDefinition<TOwner, TType>>(owner.Id, this);
var entityId = (EntityId<TType>) accumulator.Get();
return owner.Context.Get(entityId);
}
}
6 changes: 6 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/IAccumulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ public interface IAccumulator
/// <param name="value"></param>
public void Add(object value);

/// <summary>
/// Retracts a value from the accumulator.
/// </summary>
/// <param name="value"></param>
public void Retract(object value);

/// <summary>
/// Gets the accumulated value.
/// </summary>
Expand Down
5 changes: 4 additions & 1 deletion src/NexusMods.EventSourcing.Abstractions/IEntityContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ public interface IEntityContext
/// <param name="attributeDefinition"></param>
/// <typeparam name="TType"></typeparam>
/// <typeparam name="TOwner"></typeparam>
/// <typeparam name="TAttribute"></typeparam>
/// <returns></returns>
IAccumulator GetAccumulator<TType, TOwner>(EntityId ownerId, AttributeDefinition<TOwner,TType> attributeDefinition) where TOwner : IEntity;
IAccumulator GetAccumulator<TOwner, TAttribute>(EntityId ownerId, TAttribute attributeDefinition)
where TOwner : IEntity
where TAttribute : IAttribute;

}
25 changes: 25 additions & 0 deletions src/NexusMods.EventSourcing.Abstractions/IEventContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,31 @@ public void Emit<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefi
where TOwner : IEntity
where TVal : IEntity;


/// <summary>
/// Retracts a value for the given attribute on the given entity
/// </summary>
/// <param name="entity"></param>
/// <param name="attr"></param>
/// <param name="value"></param>
/// <typeparam name="TOwner"></typeparam>
/// <typeparam name="TVal"></typeparam>
public void Retract<TOwner, TVal>(EntityId<TOwner> entity, AttributeDefinition<TOwner, TVal> attr, TVal value)
where TOwner : IEntity;

/// <summary>
/// Retracts a member value for the given attribute on the given entity
/// </summary>
/// <param name="entity"></param>
/// <param name="attr"></param>
/// <param name="value"></param>
/// <typeparam name="TOwner"></typeparam>
/// <typeparam name="TVal"></typeparam>
public void Retract<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr,
EntityId<TVal> value)
where TOwner : IEntity
where TVal : IEntity;

/// <summary>
/// Emits the type attribute for the given entity so that polymorphic queries can be performed
/// </summary>
Expand Down
3 changes: 1 addition & 2 deletions src/NexusMods.EventSourcing.Abstractions/IEventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ public interface IEventStore
{
public ValueTask Add<T>(T eventEntity) where T : IEvent;

public void EventsForEntity<TEntity, TIngester>(EntityId<TEntity> entityId, TIngester ingester)
where TEntity : IEntity
public void EventsForEntity<TIngester>(EntityId entityId, TIngester ingester)
where TIngester : IEventIngester;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,13 @@ public class MultiEntityAttributeDefinition<TOwner, TType>(string name) :
ACollectionAttribute<TOwner, EntityId<TType>>(name) where TOwner : IEntity
where TType : IEntity
{
public IEnumerable<TType> GetAll(TOwner owner) => throw new NotImplementedException();
public IEnumerable<TType> GetAll(TOwner owner)
{
var tmp = owner.Context.GetAccumulator<TOwner, MultiEntityAttributeDefinition<TOwner, TType>>(owner.Id, this);
var ids = (HashSet<EntityId<TType>>) tmp.Get();
foreach (var id in ids)
{
yield return owner.Context.Get(id);
}
}
}
2 changes: 1 addition & 1 deletion src/NexusMods.EventSourcing/EntityContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public ValueTask Add<TEvent>(TEvent entity) where TEvent : IEvent
throw new System.NotImplementedException();
}

public IAccumulator GetAccumulator<TType, TOwner>(EntityId ownerId, AttributeDefinition<TOwner, TType> attributeDefinition) where TOwner : IEntity
public IAccumulator GetAccumulator<TOwner, TAttribute>(EntityId ownerId, TAttribute attributeDefinition) where TOwner : IEntity where TAttribute : IAttribute
{
throw new System.NotImplementedException();
}
Expand Down
3 changes: 2 additions & 1 deletion tests/NexusMods.EventSourcing.TestModel/Events/AddMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public async ValueTask Apply<T>(T context) where T : IEventContext
context.Emit(Id, Mod._enabled, Enabled);
context.Emit(Id, Mod._loadout, LoadoutId);
context.Emit(LoadoutId, Loadout._mods, Id);

}

public static AddMod Create(string name, EntityId<Loadout> loadoutId) => new() { Name = name, Enabled = true, Id = EntityId<Mod>.NewId(), LoadoutId = loadoutId };
}
17 changes: 17 additions & 0 deletions tests/NexusMods.EventSourcing.TestModel/Events/DeleteMod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using MemoryPack;
using NexusMods.EventSourcing.Abstractions;
using NexusMods.EventSourcing.TestModel.Model;

namespace NexusMods.EventSourcing.TestModel.Events;

[EventId("5CD171BF-4FFE-40E5-819B-987C48A20DF6")]
[MemoryPackable]
public partial record DeleteMod(EntityId<Mod> ModId, EntityId<Loadout> LoadoutId) : IEvent
{
public ValueTask Apply<T>(T context) where T : IEventContext
{
context.Retract(LoadoutId, Loadout._mods, ModId);
context.Retract(ModId, Mod._loadout, LoadoutId);
return ValueTask.CompletedTask;
}
}
1 change: 1 addition & 0 deletions tests/NexusMods.EventSourcing.TestModel/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static IServiceCollection AddEvents(this IServiceCollection coll)
coll.AddEvent<Events.AddMod>();
coll.AddEvent<Events.SwapModEnabled>();
coll.AddEvent<Events.RenameLoadout>();
coll.AddEvent<Events.DeleteMod>();
return coll;
}

Expand Down
39 changes: 39 additions & 0 deletions tests/NexusMods.EventSourcing.Tests/BasicFunctionalityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,43 @@ public async void ChangingPropertyChangesTheValue()
await _ctx.Add(RenameLoadout.Create(createEvent.Id, "New Name"));
loadout.Name.Should().Be("New Name");
}

[Fact]
public async void CanLinkEntities()
{
var loadoutEvent = CreateLoadout.Create("Test");
await _ctx.Add(loadoutEvent);
var loadout = _ctx.Get(loadoutEvent.Id);
loadout.Name.Should().Be("Test");

var modEvent = AddMod.Create("First Mod", loadoutEvent.Id);
await _ctx.Add(modEvent);

loadout.Mods.First().Name.Should().Be("First Mod");
loadout.Mods.First().Loadout.Should().BeSameAs(loadout);
}


[Fact]
public async void CanDeleteEntities()
{
var loadoutEvent = CreateLoadout.Create("Test");
await _ctx.Add(loadoutEvent);
var loadout = _ctx.Get(loadoutEvent.Id);
loadout.Name.Should().Be("Test");

var modEvent1 = AddMod.Create("First Mod", loadoutEvent.Id);
await _ctx.Add(modEvent1);

var modEvent2 = AddMod.Create("Second Mod", loadoutEvent.Id);
await _ctx.Add(modEvent2);

loadout.Mods.Count().Should().Be(2);

await _ctx.Add(new DeleteMod(modEvent1.Id, loadoutEvent.Id));

loadout.Mods.Count().Should().Be(1);

loadout.Mods.First().Name.Should().Be("Second Mod");
}
}
17 changes: 13 additions & 4 deletions tests/NexusMods.EventSourcing.Tests/Contexts/InMemoryEventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,26 @@ public void Emit<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefi
Entities.Add(entity.Value);
}

public void Retract<TOwner, TVal>(EntityId<TOwner> entity, AttributeDefinition<TOwner, TVal> attr, TVal value) where TOwner : IEntity
{
Entities.Add(entity.Value);
}

public void Retract<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr, EntityId<TVal> value) where TOwner : IEntity where TVal : IEntity
{
Entities.Add(entity.Value);
}

public void New<TType>(EntityId<TType> id) where TType : IEntity
{
Entities.Add(id.Value);
}
}


public void EventsForEntity<TEntity, TIngester>(EntityId<TEntity> entityId, TIngester ingester)
where TEntity : IEntity where TIngester : IEventIngester
public void EventsForEntity<TIngester>(EntityId entityId, TIngester ingester)
where TIngester : IEventIngester
{
foreach (var data in _events[entityId.Value])
foreach (var data in _events[entityId])
{
var @event = serializer.Deserialize(data)!;
ingester.Ingest(@event);
Expand Down
52 changes: 38 additions & 14 deletions tests/NexusMods.EventSourcing.Tests/Contexts/TestContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,38 @@ public TEntity Get<TEntity>(EntityId<TEntity> id) where TEntity : IEntity
return (TEntity)entity;
}

var ingester = new Ingester(id.Value);

_store.EventsForEntity(id, ingester);

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

var type = (Type)values[IEntity.TypeAttribute].Get();
var createdEntity = (TEntity)Activator.CreateInstance(type, this, id.Value)!;
_entities.Add(id.Value, createdEntity);
_values.Add(id.Value, ingester.Values);

return createdEntity;
}

private Dictionary<IAttribute, IAccumulator> LoadValues(EntityId id)
{
var ingester = new Ingester(id);
_store.EventsForEntity(id, ingester);
_values.Add(id, ingester.Values);
return ingester.Values;
}

public async ValueTask Add<TEvent>(TEvent entity) where TEvent : IEvent
{
_entities.Clear();
_values.Clear();
await _store.Add(entity);
}

public IAccumulator GetAccumulator<TType, TOwner>(EntityId ownerId, AttributeDefinition<TOwner, TType> attributeDefinition)
where TOwner : IEntity
public IAccumulator GetAccumulator<TOwner, TAttribute>(EntityId ownerId, TAttribute attributeDefinition)
where TOwner: IEntity
where TAttribute : IAttribute
{
if (_values.TryGetValue(ownerId, out var values))
return values[attributeDefinition];

Get(EntityId<TOwner>.From(ownerId.Value));
values = _values[ownerId];

return values[attributeDefinition];
var loadedValues = LoadValues(ownerId);
return loadedValues[attributeDefinition];
}

private readonly struct Ingester(EntityId id) : IEventIngester, IEventContext
Expand Down Expand Up @@ -94,7 +96,29 @@ public void Emit<TOwner, TVal>(EntityId entity, AttributeDefinition<TOwner, TVal

public void Emit<TOwner, TVal>(EntityId<TOwner> entity, MultiEntityAttributeDefinition<TOwner, TVal> attr, EntityId<TVal> value) where TOwner : IEntity where TVal : IEntity
{
throw new NotImplementedException();
if (entity.Value != id.Value)
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.Value)
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.Value)
return;

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

public void New<TType>(EntityId<TType> newId) where TType : IEntity
Expand Down

0 comments on commit 3687b0c

Please sign in to comment.