From 2bc82154e65ed2dc4b67683f2efe16130df5a5a4 Mon Sep 17 00:00:00 2001 From: KlaasWhite Date: Tue, 20 Oct 2026 17:27:53 +0200 Subject: [PATCH 1/3] Event publishing part 1 --- StarMap.API/Mediation/IEventPublisher.cs | 9 ++ .../Mediation/IParallelStarMapEvent.cs | 6 + StarMap.API/Mediation/ISerialStarMapEvent.cs | 6 + StarMap.API/Mediation/IStarMapEvent.cs | 6 + StarMap.API/Mediation/ISubscription.cs | 6 + StarMap.API/Services/IMediationService.cs | 10 ++ StarMap.Core/API/MediationService.cs | 29 ++++ StarMap.Core/Services/Mediator.cs | 127 ++++++++++++++++++ StarMap.Core/StarMapCore.cs | 2 + 9 files changed, 201 insertions(+) create mode 100644 StarMap.API/Mediation/IEventPublisher.cs create mode 100644 StarMap.API/Mediation/IParallelStarMapEvent.cs create mode 100644 StarMap.API/Mediation/ISerialStarMapEvent.cs create mode 100644 StarMap.API/Mediation/IStarMapEvent.cs create mode 100644 StarMap.API/Mediation/ISubscription.cs create mode 100644 StarMap.API/Services/IMediationService.cs create mode 100644 StarMap.Core/API/MediationService.cs create mode 100644 StarMap.Core/Services/Mediator.cs diff --git a/StarMap.API/Mediation/IEventPublisher.cs b/StarMap.API/Mediation/IEventPublisher.cs new file mode 100644 index 0000000..0729961 --- /dev/null +++ b/StarMap.API/Mediation/IEventPublisher.cs @@ -0,0 +1,9 @@ +namespace StarMap.API.Mediation +{ + public interface IEventPublisher { } + + public interface IEventPublisher : IEventPublisher where T : IStarMapEvent + { + void Publish(T message); + } +} diff --git a/StarMap.API/Mediation/IParallelStarMapEvent.cs b/StarMap.API/Mediation/IParallelStarMapEvent.cs new file mode 100644 index 0000000..5c5a57c --- /dev/null +++ b/StarMap.API/Mediation/IParallelStarMapEvent.cs @@ -0,0 +1,6 @@ +namespace StarMap.API.Mediation +{ + public interface IParallelStarMapEvent + { + } +} diff --git a/StarMap.API/Mediation/ISerialStarMapEvent.cs b/StarMap.API/Mediation/ISerialStarMapEvent.cs new file mode 100644 index 0000000..881169a --- /dev/null +++ b/StarMap.API/Mediation/ISerialStarMapEvent.cs @@ -0,0 +1,6 @@ +namespace StarMap.API.Mediation +{ + public interface ISerialStarMapEvent : IStarMapEvent + { + } +} diff --git a/StarMap.API/Mediation/IStarMapEvent.cs b/StarMap.API/Mediation/IStarMapEvent.cs new file mode 100644 index 0000000..fbbfbf5 --- /dev/null +++ b/StarMap.API/Mediation/IStarMapEvent.cs @@ -0,0 +1,6 @@ +namespace StarMap.API.Mediation +{ + public interface IStarMapEvent + { + } +} diff --git a/StarMap.API/Mediation/ISubscription.cs b/StarMap.API/Mediation/ISubscription.cs new file mode 100644 index 0000000..47f02d1 --- /dev/null +++ b/StarMap.API/Mediation/ISubscription.cs @@ -0,0 +1,6 @@ +namespace StarMap.API.Mediation +{ + public interface ISubscription : IDisposable where T : IStarMapEvent + { + } +} diff --git a/StarMap.API/Services/IMediationService.cs b/StarMap.API/Services/IMediationService.cs new file mode 100644 index 0000000..f9a55ca --- /dev/null +++ b/StarMap.API/Services/IMediationService.cs @@ -0,0 +1,10 @@ +using StarMap.API.Mediation; + +namespace StarMap.API.Services +{ + public interface IMediationService + { + ISubscription Subscribe(Action handler) where T : IStarMapEvent; + IEventPublisher GetPublisher() where T : IStarMapEvent; + } +} diff --git a/StarMap.Core/API/MediationService.cs b/StarMap.Core/API/MediationService.cs new file mode 100644 index 0000000..fabac0d --- /dev/null +++ b/StarMap.Core/API/MediationService.cs @@ -0,0 +1,29 @@ +using StarMap.API.Mediation; +using StarMap.API.Services; +using StarMap.Core.ModRepository; +using StarMap.Core.Services; + +namespace StarMap.Core.API +{ + internal class MediationService : IMediationService + { + private readonly RuntimeMod _mod; + private readonly IMediator _mediator; + + public MediationService(IModContext modContext, IMediator mediator) + { + _mod = modContext.Mod; + _mediator = mediator; + } + + public IEventPublisher GetPublisher() where T : IStarMapEvent + { + return _mediator.GetPublisher(_mod); + } + + public ISubscription Subscribe(Action handler) where T : IStarMapEvent + { + return _mediator.Subscribe(handler); + } + } +} diff --git a/StarMap.Core/Services/Mediator.cs b/StarMap.Core/Services/Mediator.cs new file mode 100644 index 0000000..68ebd7b --- /dev/null +++ b/StarMap.Core/Services/Mediator.cs @@ -0,0 +1,127 @@ +using StarMap.API.Mediation; +using StarMap.Core.ModRepository; +using System.Collections.Concurrent; +using System.Runtime.Loader; + +namespace StarMap.Core.Services +{ + internal interface IMediator + { + IEventPublisher GetPublisher(RuntimeMod mod) where T : IStarMapEvent; + ISubscription Subscribe(Action action) where T : IStarMapEvent; + } + + internal class SubscriptionHandle : ISubscription where T : IStarMapEvent + { + private readonly EventPublisher _publisher; + + private readonly Action _action; + + public SubscriptionHandle(EventPublisher publisher, Action action) + { + _publisher = publisher; + _action = action; + } + + public void Invoke(T message) + { + _action.Invoke(message); + } + + public void Dispose() + { + _publisher.Unsubscribe(this); + } + } + + internal abstract class EventPublisher() : IEventPublisher where T : IStarMapEvent + { + protected readonly HashSet> _handlers = []; + + public ISubscription Subscribe(Action action) + { + var handle = new SubscriptionHandle(this, action); + + lock (_handlers) + { + _handlers.Add(handle); + } + + return handle; + } + + public void Unsubscribe(SubscriptionHandle handle) + { + lock (_handlers) + { + _handlers.Remove(handle); + } + } + + public void Publish(T message) + { + PublishInner(message); + } + + public abstract void PublishInner(T message); + } + + internal class SerialEventPublisher() : EventPublisher where T : IStarMapEvent + { + public override void PublishInner(T message) + { + foreach (var handler in _handlers) + { + handler.Invoke(message); + } + } + } + + internal class ParallelEventPublisher() : EventPublisher where T : IStarMapEvent + { + public override void PublishInner(T message) + { + foreach (var handler in _handlers) + { + _ = Task.Run(() => handler.Invoke(message)); + } + } + } + + internal class Mediator : IMediator + { + private readonly ConcurrentDictionary _publishers = []; + + public IEventPublisher GetPublisher(RuntimeMod mod) where T : IStarMapEvent + { + var type = typeof(T); + if (AssemblyLoadContext.GetLoadContext(typeof(T).Assembly) != mod.ModAssemblyLoadContext) + throw new Exception("Events are only allowed to be published by the owner of the event type"); + + EventPublisher publisher; + + if (type.IsAssignableTo(typeof(ISerialStarMapEvent))) + publisher = new SerialEventPublisher(); + else + publisher = new ParallelEventPublisher(); + + if (!_publishers.TryAdd(type, publisher)) + throw new Exception($"A publisher for type '{typeof(T)}' was already registered"); + + return publisher; + } + + public ISubscription Subscribe(Action action) where T : IStarMapEvent + { + if (!_publishers.TryGetValue(typeof(T), out var publisher)) + { + throw new Exception("A publisher for this event was not found"); + } + + if (publisher is not EventPublisher typedPublisher) + throw new Exception("Something went wrong in the type of the publisher"); + + return typedPublisher.Subscribe(action); + } + } +} diff --git a/StarMap.Core/StarMapCore.cs b/StarMap.Core/StarMapCore.cs index 5bda76a..05a0dd7 100644 --- a/StarMap.Core/StarMapCore.cs +++ b/StarMap.Core/StarMapCore.cs @@ -22,10 +22,12 @@ public StarMapCore(AssemblyLoadContext coreAssemblyLoadContext) serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); serviceCollection.AddSingleton(); From ddb4f36b7307aad22ff0f0bd7ca0e560d346c388 Mon Sep 17 00:00:00 2001 From: KlaasWhite Date: Sat, 24 Oct 2026 16:39:06 +0200 Subject: [PATCH 2/3] Changes --- StarMap.API/Mediation/IEventPublisher.cs | 5 +- .../Mediation/IParallelStarMapEvent.cs | 6 -- StarMap.API/Mediation/ISerialStarMapEvent.cs | 6 -- StarMap.Core/API/MediationService.cs | 2 +- StarMap.Core/Services/Mediator.cs | 79 +++++++++++++------ 5 files changed, 56 insertions(+), 42 deletions(-) delete mode 100644 StarMap.API/Mediation/IParallelStarMapEvent.cs delete mode 100644 StarMap.API/Mediation/ISerialStarMapEvent.cs diff --git a/StarMap.API/Mediation/IEventPublisher.cs b/StarMap.API/Mediation/IEventPublisher.cs index 0729961..6a2f057 100644 --- a/StarMap.API/Mediation/IEventPublisher.cs +++ b/StarMap.API/Mediation/IEventPublisher.cs @@ -1,9 +1,8 @@ namespace StarMap.API.Mediation { - public interface IEventPublisher { } - - public interface IEventPublisher : IEventPublisher where T : IStarMapEvent + public interface IEventPublisher : IDisposable where T : IStarMapEvent { void Publish(T message); + Task PublishParallel(T message); } } diff --git a/StarMap.API/Mediation/IParallelStarMapEvent.cs b/StarMap.API/Mediation/IParallelStarMapEvent.cs deleted file mode 100644 index 5c5a57c..0000000 --- a/StarMap.API/Mediation/IParallelStarMapEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StarMap.API.Mediation -{ - public interface IParallelStarMapEvent - { - } -} diff --git a/StarMap.API/Mediation/ISerialStarMapEvent.cs b/StarMap.API/Mediation/ISerialStarMapEvent.cs deleted file mode 100644 index 881169a..0000000 --- a/StarMap.API/Mediation/ISerialStarMapEvent.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StarMap.API.Mediation -{ - public interface ISerialStarMapEvent : IStarMapEvent - { - } -} diff --git a/StarMap.Core/API/MediationService.cs b/StarMap.Core/API/MediationService.cs index fabac0d..4848b7d 100644 --- a/StarMap.Core/API/MediationService.cs +++ b/StarMap.Core/API/MediationService.cs @@ -18,7 +18,7 @@ public MediationService(IModContext modContext, IMediator mediator) public IEventPublisher GetPublisher() where T : IStarMapEvent { - return _mediator.GetPublisher(_mod); + return _mediator.RegisterPublisher(_mod); } public ISubscription Subscribe(Action handler) where T : IStarMapEvent diff --git a/StarMap.Core/Services/Mediator.cs b/StarMap.Core/Services/Mediator.cs index 68ebd7b..b760c3c 100644 --- a/StarMap.Core/Services/Mediator.cs +++ b/StarMap.Core/Services/Mediator.cs @@ -7,10 +7,12 @@ namespace StarMap.Core.Services { internal interface IMediator { - IEventPublisher GetPublisher(RuntimeMod mod) where T : IStarMapEvent; + IEventPublisher RegisterPublisher(RuntimeMod mod) where T : IStarMapEvent; ISubscription Subscribe(Action action) where T : IStarMapEvent; } + public interface IEventPublisher { } + internal class SubscriptionHandle : ISubscription where T : IStarMapEvent { private readonly EventPublisher _publisher; @@ -34,16 +36,21 @@ public void Dispose() } } - internal abstract class EventPublisher() : IEventPublisher where T : IStarMapEvent + internal class EventPublisher(Mediator mediator) : IEventPublisher, IEventPublisher where T : IStarMapEvent { - protected readonly HashSet> _handlers = []; + private readonly Lock _lock = new(); + + private bool _isDisposed; + + private readonly HashSet> _handlers = []; public ISubscription Subscribe(Action action) { var handle = new SubscriptionHandle(this, action); - lock (_handlers) + lock (_lock) { + ObjectDisposedException.ThrowIf(_isDisposed, this); _handlers.Add(handle); } @@ -52,39 +59,59 @@ public ISubscription Subscribe(Action action) public void Unsubscribe(SubscriptionHandle handle) { - lock (_handlers) + lock (_lock) { + if (_isDisposed) + return; + _handlers.Remove(handle); } } public void Publish(T message) { - PublishInner(message); + foreach (var handler in GetHandlersSnapshot()) + { + handler.Invoke(message); + } } - public abstract void PublishInner(T message); - } + public Task PublishParallel(T message) + { + var tasks = new List(); - internal class SerialEventPublisher() : EventPublisher where T : IStarMapEvent - { - public override void PublishInner(T message) + foreach (var handler in GetHandlersSnapshot()) + { + tasks.Add(Task.Run(() => handler.Invoke(message))); + } + + return Task.WhenAll(tasks); + } + + private SubscriptionHandle[] GetHandlersSnapshot() { - foreach (var handler in _handlers) + lock (_lock) { - handler.Invoke(message); + if (_isDisposed) + return []; + + return [.. _handlers]; } } - } - internal class ParallelEventPublisher() : EventPublisher where T : IStarMapEvent - { - public override void PublishInner(T message) + public void Dispose() { - foreach (var handler in _handlers) + lock (_lock) { - _ = Task.Run(() => handler.Invoke(message)); + if (_isDisposed) + return; + + _isDisposed = true; + + mediator.UnregisterPublisher(this); + _handlers.Clear(); } + } } @@ -92,18 +119,13 @@ internal class Mediator : IMediator { private readonly ConcurrentDictionary _publishers = []; - public IEventPublisher GetPublisher(RuntimeMod mod) where T : IStarMapEvent + public IEventPublisher RegisterPublisher(RuntimeMod mod) where T : IStarMapEvent { var type = typeof(T); if (AssemblyLoadContext.GetLoadContext(typeof(T).Assembly) != mod.ModAssemblyLoadContext) throw new Exception("Events are only allowed to be published by the owner of the event type"); - EventPublisher publisher; - - if (type.IsAssignableTo(typeof(ISerialStarMapEvent))) - publisher = new SerialEventPublisher(); - else - publisher = new ParallelEventPublisher(); + var publisher = new EventPublisher(this); if (!_publishers.TryAdd(type, publisher)) throw new Exception($"A publisher for type '{typeof(T)}' was already registered"); @@ -111,6 +133,11 @@ public IEventPublisher GetPublisher(RuntimeMod mod) where T : IStarMapEven return publisher; } + public void UnregisterPublisher(IEventPublisher publisher) + { + _publishers.TryRemove(new(typeof(T), publisher)); + } + public ISubscription Subscribe(Action action) where T : IStarMapEvent { if (!_publishers.TryGetValue(typeof(T), out var publisher)) From 2a453e8fb8bdc247c70389b4aba5c53ed938d35d Mon Sep 17 00:00:00 2001 From: KlaasWhite Date: Sat, 24 Oct 2026 16:45:13 +0200 Subject: [PATCH 3/3] Rename API method --- StarMap.API/Services/IMediationService.cs | 2 +- StarMap.Core/API/MediationService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/StarMap.API/Services/IMediationService.cs b/StarMap.API/Services/IMediationService.cs index f9a55ca..d8f3450 100644 --- a/StarMap.API/Services/IMediationService.cs +++ b/StarMap.API/Services/IMediationService.cs @@ -5,6 +5,6 @@ namespace StarMap.API.Services public interface IMediationService { ISubscription Subscribe(Action handler) where T : IStarMapEvent; - IEventPublisher GetPublisher() where T : IStarMapEvent; + IEventPublisher RegisterPublisher() where T : IStarMapEvent; } } diff --git a/StarMap.Core/API/MediationService.cs b/StarMap.Core/API/MediationService.cs index 4848b7d..4767293 100644 --- a/StarMap.Core/API/MediationService.cs +++ b/StarMap.Core/API/MediationService.cs @@ -16,7 +16,7 @@ public MediationService(IModContext modContext, IMediator mediator) _mediator = mediator; } - public IEventPublisher GetPublisher() where T : IStarMapEvent + public IEventPublisher RegisterPublisher() where T : IStarMapEvent { return _mediator.RegisterPublisher(_mod); }