Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions StarMap.API/Mediation/IEventPublisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace StarMap.API.Mediation
{
public interface IEventPublisher<T> : IDisposable where T : IStarMapEvent
{
void Publish(T message);
Task PublishParallel(T message);
}
}
6 changes: 6 additions & 0 deletions StarMap.API/Mediation/IStarMapEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace StarMap.API.Mediation
{
public interface IStarMapEvent
{
}
}
6 changes: 6 additions & 0 deletions StarMap.API/Mediation/ISubscription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace StarMap.API.Mediation
{
public interface ISubscription<T> : IDisposable where T : IStarMapEvent
{
}
}
10 changes: 10 additions & 0 deletions StarMap.API/Services/IMediationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using StarMap.API.Mediation;

namespace StarMap.API.Services
{
public interface IMediationService
{
ISubscription<T> Subscribe<T>(Action<T> handler) where T : IStarMapEvent;
IEventPublisher<T> RegisterPublisher<T>() where T : IStarMapEvent;
}
}
29 changes: 29 additions & 0 deletions StarMap.Core/API/MediationService.cs
Original file line number Diff line number Diff line change
@@ -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<T> RegisterPublisher<T>() where T : IStarMapEvent
{
return _mediator.RegisterPublisher<T>(_mod);
}

public ISubscription<T> Subscribe<T>(Action<T> handler) where T : IStarMapEvent
{
return _mediator.Subscribe(handler);
}
}
}
154 changes: 154 additions & 0 deletions StarMap.Core/Services/Mediator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using StarMap.API.Mediation;
using StarMap.Core.ModRepository;
using System.Collections.Concurrent;
using System.Runtime.Loader;

namespace StarMap.Core.Services
{
internal interface IMediator
{
IEventPublisher<T> RegisterPublisher<T>(RuntimeMod mod) where T : IStarMapEvent;
ISubscription<T> Subscribe<T>(Action<T> action) where T : IStarMapEvent;
}

public interface IEventPublisher { }

internal class SubscriptionHandle<T> : ISubscription<T> where T : IStarMapEvent
{
private readonly EventPublisher<T> _publisher;

private readonly Action<T> _action;

public SubscriptionHandle(EventPublisher<T> publisher, Action<T> action)
{
_publisher = publisher;
_action = action;
}

public void Invoke(T message)
{
_action.Invoke(message);
}

public void Dispose()
{
_publisher.Unsubscribe(this);
}
}

internal class EventPublisher<T>(Mediator mediator) : IEventPublisher<T>, IEventPublisher where T : IStarMapEvent
{
private readonly Lock _lock = new();

private bool _isDisposed;

private readonly HashSet<SubscriptionHandle<T>> _handlers = [];

public ISubscription<T> Subscribe(Action<T> action)
{
var handle = new SubscriptionHandle<T>(this, action);

lock (_lock)
{
ObjectDisposedException.ThrowIf(_isDisposed, this);
_handlers.Add(handle);
}

return handle;
}

public void Unsubscribe(SubscriptionHandle<T> handle)
{
lock (_lock)
{
if (_isDisposed)
return;

_handlers.Remove(handle);
}
}

public void Publish(T message)
{
foreach (var handler in GetHandlersSnapshot())
{
handler.Invoke(message);
}
}

public Task PublishParallel(T message)
{
var tasks = new List<Task>();

foreach (var handler in GetHandlersSnapshot())
{
tasks.Add(Task.Run(() => handler.Invoke(message)));
}

return Task.WhenAll(tasks);
}

private SubscriptionHandle<T>[] GetHandlersSnapshot()
{
lock (_lock)
{
if (_isDisposed)
return [];

return [.. _handlers];
}
}

public void Dispose()
{
lock (_lock)
{
if (_isDisposed)
return;

_isDisposed = true;

mediator.UnregisterPublisher<T>(this);
_handlers.Clear();
}

}
}

internal class Mediator : IMediator
{
private readonly ConcurrentDictionary<Type, IEventPublisher> _publishers = [];

public IEventPublisher<T> RegisterPublisher<T>(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");

var publisher = new EventPublisher<T>(this);

if (!_publishers.TryAdd(type, publisher))
throw new Exception($"A publisher for type '{typeof(T)}' was already registered");

return publisher;
}

public void UnregisterPublisher<T>(IEventPublisher publisher)
{
_publishers.TryRemove(new(typeof(T), publisher));
}

public ISubscription<T> Subscribe<T>(Action<T> 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<T> typedPublisher)
throw new Exception("Something went wrong in the type of the publisher");

return typedPublisher.Subscribe(action);
}
}
}
2 changes: 2 additions & 0 deletions StarMap.Core/StarMapCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ public StarMapCore(AssemblyLoadContext coreAssemblyLoadContext)
serviceCollection.AddSingleton<IModsLoader, ModsLoader>();
serviceCollection.AddSingleton<IStarMapPatches, StarMapPatches>();
serviceCollection.AddSingleton<IModMenu, ModMenu>();
serviceCollection.AddSingleton<IMediator, Mediator>();

serviceCollection.AddScoped<IModContext, ModContext>();
serviceCollection.AddScoped<IPatchService, PatchService>();
serviceCollection.AddScoped<IModMenuService, ModMenuService>();
serviceCollection.AddScoped<IMediationService, MediationService>();

serviceCollection.AddSingleton<ICoreService, CoreService>();

Expand Down
Loading