generated from Nexus-Mods/NexusMods.App.Template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement some basic serialization and tests
- Loading branch information
Showing
26 changed files
with
421 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
src/NexusMods.EventSourcing.Abstractions/DependencyInjectionExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using System; | ||
using System.Reflection; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// DI extensions for the event sourcing library. | ||
/// </summary> | ||
public static class DependencyInjectionExtensions | ||
{ | ||
/// <summary> | ||
/// Registers an event with the service collection. | ||
/// </summary> | ||
/// <param name="collection"></param> | ||
/// <typeparam name="T"></typeparam> | ||
/// <returns></returns> | ||
public static IServiceCollection AddEvent<T>(this IServiceCollection collection) where T : class, IEvent | ||
{ | ||
var type = typeof(T); | ||
var attribute = type.GetCustomAttribute<EventIdAttribute>(); | ||
if (attribute is null) | ||
{ | ||
throw new ArgumentException($"Event type {type.Name} does not have an EventIdAttribute."); | ||
} | ||
collection.AddSingleton(s => new EventDefinition(attribute.Guid, type)); | ||
return collection; | ||
} | ||
|
||
} |
10 changes: 10 additions & 0 deletions
10
src/NexusMods.EventSourcing.Abstractions/EventDefinition.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// A record that defines an event and the unique GUID that identifies it. | ||
/// </summary> | ||
/// <param name="Guid"></param> | ||
/// <param name="Type"></param> | ||
public record EventDefinition(Guid Guid, Type Type); |
25 changes: 25 additions & 0 deletions
25
src/NexusMods.EventSourcing.Abstractions/EventIdAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
|
||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// Marks a an event as having the given GUID id | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Class)] | ||
public class EventIdAttribute : Attribute | ||
{ | ||
/// <summary> | ||
/// The GUID of the entity type. | ||
/// </summary> | ||
public readonly Guid Guid; | ||
|
||
|
||
/// <summary> | ||
/// Creates a new instance of the <see cref="EventIdAttribute"/> class. | ||
/// </summary> | ||
/// <param name="guid"></param> | ||
public EventIdAttribute(string guid) | ||
{ | ||
Guid = Guid.Parse(guid); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace NexusMods.EventSourcing.Abstractions; | ||
|
||
/// <summary> | ||
/// Marks this entity as a singleton entity, the singleton id is used to retrieve the entity from the cache. | ||
/// </summary> | ||
public interface ISingletonEntity | ||
{ | ||
public static virtual EntityId SingletonId { get; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using MemoryPack; | ||
using MemoryPack.Formatters; | ||
using NexusMods.EventSourcing.Abstractions; | ||
|
||
namespace NexusMods.EventSourcing; | ||
|
||
public class EventFormatter : MemoryPackFormatter<IEvent> | ||
{ | ||
private static Guid _zeroGuid = Guid.Empty; | ||
private readonly Dictionary<Guid,Type> _eventByGuid; | ||
private readonly Dictionary<Type,Guid> _eventsByType; | ||
|
||
public EventFormatter(IEnumerable<EventDefinition> events) | ||
{ | ||
_eventByGuid = events.ToDictionary(e => e.Guid, e => e.Type); | ||
_eventsByType = events.ToDictionary(e => e.Type, e => e.Guid); | ||
} | ||
|
||
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref IEvent? value) | ||
{ | ||
if (value == null) | ||
{ | ||
writer.WriteValue(_zeroGuid); | ||
return; | ||
} | ||
|
||
var type = value.GetType(); | ||
writer.WriteValue(_eventsByType[type]); | ||
writer.WriteValue(type, (object)value); | ||
} | ||
|
||
public override void Deserialize(ref MemoryPackReader reader, scoped ref IEvent? value) | ||
{ | ||
var readValue = reader.ReadValue<Guid>(); | ||
if (readValue == _zeroGuid) | ||
{ | ||
value = null; | ||
return; | ||
} | ||
var mappedType = _eventByGuid[readValue]; | ||
value = (IEvent)reader.ReadValue(mappedType)!; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using MemoryPack; | ||
using MemoryPack.Formatters; | ||
using NexusMods.EventSourcing.Abstractions; | ||
|
||
namespace NexusMods.EventSourcing; | ||
|
||
public class EventSerializer | ||
{ | ||
public EventSerializer(IEnumerable<EventDefinition> events) | ||
{ | ||
var formatter = new EventFormatter(events); | ||
if (!MemoryPackFormatterProvider.IsRegistered<IEvent>()) | ||
MemoryPackFormatterProvider.Register(formatter); | ||
} | ||
|
||
public byte[] Serialize(IEvent @event) | ||
{ | ||
return MemoryPackSerializer.Serialize(@event); | ||
} | ||
|
||
public IEvent Deserialize(byte[] data) | ||
{ | ||
return MemoryPackSerializer.Deserialize<IEvent>(data)!; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace NexusMods.EventSourcing; | ||
|
||
public static class Services | ||
{ | ||
public static IServiceCollection AddEventSourcing(this IServiceCollection services) | ||
{ | ||
return services.AddSingleton<EventSerializer>(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using DynamicData; | ||
using MemoryPack; | ||
using NexusMods.EventSourcing.Abstractions; | ||
using NexusMods.EventSourcing.TestModel.Model; | ||
|
||
namespace NexusMods.EventSourcing.TestModel.Events; | ||
|
||
[EventId("7DC8F80B-50B6-43B7-B805-43450E9F0C2B")] | ||
[MemoryPackable] | ||
public partial class AddMod : IEvent | ||
{ | ||
public required string Name { get; init; } = string.Empty; | ||
public required bool Enabled { get; init; } = true; | ||
public required EntityId<Mod> Id { get; init; } | ||
public required EntityId<Loadout> Loadout { get; init; } | ||
|
||
public async ValueTask Apply<T>(T context) where T : IEventContext | ||
{ | ||
var loadout = await context.Retrieve(Loadout); | ||
var mod = new Mod | ||
{ | ||
Id = Id.Value, | ||
Name = Name, | ||
Enabled = Enabled, | ||
}; | ||
loadout._mods.AddOrUpdate(mod); | ||
context.AttachEntity(Id, mod); | ||
|
||
} | ||
|
||
public void ModifiedEntities(Action<EntityId> handler) | ||
{ | ||
handler(Id.Value); | ||
handler(Loadout.Value); | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
tests/NexusMods.EventSourcing.TestModel/Events/CreateLoadout.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
using DynamicData; | ||
using MemoryPack; | ||
using NexusMods.EventSourcing.Abstractions; | ||
using NexusMods.EventSourcing.TestModel.Model; | ||
|
||
namespace NexusMods.EventSourcing.TestModel.Events; | ||
|
||
[EventId("63A4CB90-27E2-468A-BE94-CB01A38D8C09")] | ||
[MemoryPackable] | ||
public partial class CreateLoadout : IEvent | ||
{ | ||
public required string Name { get; init; } | ||
|
||
public required EntityId<Loadout> Id { get; init; } | ||
|
||
|
||
public async ValueTask Apply<T>(T context) where T : IEventContext | ||
{ | ||
var registry = await context.Retrieve(LoadoutRegistry.StaticId); | ||
var loadout = new Loadout | ||
{ | ||
Id = Id.Value, | ||
Name = Name | ||
}; | ||
registry._loadouts.AddOrUpdate(loadout); | ||
context.AttachEntity(Id, loadout); | ||
} | ||
|
||
public static CreateLoadout Create(string name) => new() { Name = name, Id = EntityId<Loadout>.NewId() }; | ||
|
||
public void ModifiedEntities(Action<EntityId> handler) | ||
{ | ||
handler(Id.Value); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
tests/NexusMods.EventSourcing.TestModel/Events/SwapModEnabled.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using MemoryPack; | ||
using NexusMods.EventSourcing.Abstractions; | ||
using NexusMods.EventSourcing.TestModel.Model; | ||
|
||
namespace NexusMods.EventSourcing.TestModel.Events; | ||
|
||
[EventId("8492075A-DED5-42BF-8D01-B4CDCE2526CF")] | ||
[MemoryPackable] | ||
public partial class SwapModEnabled : IEvent | ||
{ | ||
public required EntityId<Mod> Id { get; init; } | ||
public async ValueTask Apply<T>(T context) where T : IEventContext | ||
{ | ||
var mod = await context.Retrieve(Id); | ||
mod.Enabled = !mod.Enabled; | ||
} | ||
|
||
/// <summary> | ||
/// Helper method to create a new event instance. | ||
/// </summary> | ||
/// <param name="id"></param> | ||
/// <returns></returns> | ||
public static SwapModEnabled Create(EntityId<Mod> id) => new() { Id = id }; | ||
|
||
public void ModifiedEntities(Action<EntityId> handler) | ||
{ | ||
handler(Id.Value); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
tests/NexusMods.EventSourcing.TestModel/Model/Collection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using System.Collections.ObjectModel; | ||
using DynamicData; | ||
using NexusMods.EventSourcing.Abstractions; | ||
using ReactiveUI.Fody.Helpers; | ||
|
||
namespace NexusMods.EventSourcing.TestModel.Model; | ||
|
||
public class Collection | ||
{ | ||
public Loadout Loadout { get; internal set; } = null!; | ||
|
||
internal SourceCache<Mod, EntityId> _mods = new(x => x.Id); | ||
|
||
private ReadOnlyObservableCollection<Mod> _modsConnected = null!; | ||
public ReadOnlyObservableCollection<Mod> Mods { get; internal set; } = null!; | ||
|
||
[Reactive] | ||
public string Name { get; internal set; } = string.Empty; | ||
|
||
public Collection() | ||
{ | ||
_mods.Connect() | ||
.Bind(out _modsConnected) | ||
.Subscribe(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using DynamicData; | ||
using NexusMods.EventSourcing.Abstractions; | ||
|
||
namespace NexusMods.EventSourcing.TestModel.Model; | ||
|
||
public class Loadout : IEntity | ||
{ | ||
public EntityId Id { get; internal set; } | ||
|
||
public string Name { get; internal set; } = string.Empty; | ||
|
||
internal SourceCache<Mod, EntityId> _mods = new(x => x.Id); | ||
} |
24 changes: 24 additions & 0 deletions
24
tests/NexusMods.EventSourcing.TestModel/Model/LoadoutRegistry.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System.Collections.ObjectModel; | ||
using DynamicData; | ||
using NexusMods.EventSourcing.Abstractions; | ||
|
||
namespace NexusMods.EventSourcing.TestModel.Model; | ||
|
||
public class LoadoutRegistry : IEntity | ||
{ | ||
internal readonly SourceCache<Loadout, EntityId> _loadouts = new(x => x.Id); | ||
|
||
private ReadOnlyObservableCollection<Loadout> _loadoutsConnected; | ||
public ReadOnlyObservableCollection<Loadout> Loadouts => _loadoutsConnected; | ||
|
||
public LoadoutRegistry() | ||
{ | ||
_loadouts.Connect() | ||
.Bind(out _loadoutsConnected) | ||
.Subscribe(); | ||
} | ||
|
||
public static EntityId<LoadoutRegistry> StaticId = new(EntityId.From(Guid.Parse("7F3E3745-51B9-44CB-BBDA-B1555191330E"))); | ||
|
||
public EntityId Id { get; } = StaticId.Value; | ||
} |
Oops, something went wrong.