Skip to content
Open
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
7 changes: 7 additions & 0 deletions StarMap.API/Events/IEventPublisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace StarMap.API.Events
{
public interface IEventPublisher<T> : IDisposable
{
void Publish(T message);
}
}
6 changes: 6 additions & 0 deletions StarMap.API/Events/ISubscription.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace StarMap.API.Events
{
public interface ISubscription<T> : IDisposable
{
}
}
8 changes: 0 additions & 8 deletions StarMap.API/Mediation/IEventPublisher.cs

This file was deleted.

6 changes: 0 additions & 6 deletions StarMap.API/Mediation/IStarMapEvent.cs

This file was deleted.

6 changes: 0 additions & 6 deletions StarMap.API/Mediation/ISubscription.cs

This file was deleted.

13 changes: 13 additions & 0 deletions StarMap.API/Services/IEventBus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using StarMap.API.Events;

namespace StarMap.API.Services
{
public interface IEventBus
{
IEventPublisher<T> GetPublisher<T>();
IEventPublisher<T> GetPublisher<T>(string eventName);

ISubscription<T> Subscribe<T>(Action<T> handler);
ISubscription<T> Subscribe<T>(Action<T> handler, string eventName);
}
}
10 changes: 0 additions & 10 deletions StarMap.API/Services/IMediationService.cs

This file was deleted.

39 changes: 39 additions & 0 deletions StarMap.Core/API/EventBus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using StarMap.API.Events;
using StarMap.API.Services;
using StarMap.Core.ModRepository;
using StarMap.Core.Services;

namespace StarMap.Core.API
{
internal class EventBus : IEventBus
{
private readonly RuntimeMod _mod;
private readonly IMediator _mediator;

public EventBus(IModContext modContext, IMediator mediator)
{
_mod = modContext.Mod;
_mediator = mediator;
}

public IEventPublisher<T> GetPublisher<T>()
{
return _mediator.GetPublisher<T>();
}

public IEventPublisher<T> GetPublisher<T>(string eventName)
{
return _mediator.GetPublisher<T>(eventName);
}

public ISubscription<T> Subscribe<T>(Action<T> handler)
{
return _mediator.Subscribe(handler);
}

public ISubscription<T> Subscribe<T>(Action<T> handler, string eventName)
{
return _mediator.Subscribe(handler, eventName);
}
}
}
29 changes: 0 additions & 29 deletions StarMap.Core/API/MediationService.cs

This file was deleted.

194 changes: 102 additions & 92 deletions StarMap.Core/Services/Mediator.cs
Original file line number Diff line number Diff line change
@@ -1,154 +1,164 @@
using StarMap.API.Mediation;
using StarMap.Core.ModRepository;
using StarMap.API.Events;
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;
}
IEventPublisher<T> GetPublisher<T>();
IEventPublisher<T> GetPublisher<T>(string eventName);

public interface IEventPublisher { }
ISubscription<T> Subscribe<T>(Action<T> handler);
ISubscription<T> Subscribe<T>(Action<T> handler, string eventName);
}

internal class SubscriptionHandle<T> : ISubscription<T> where T : IStarMapEvent
internal class Mediator : IMediator
{
private readonly EventPublisher<T> _publisher;
private interface IEventBus { }

private readonly Action<T> _action;
private readonly ConcurrentDictionary<Type, IEventBus> _busses = [];

public SubscriptionHandle(EventPublisher<T> publisher, Action<T> action)
private TypeEventBus<T> GetTypeBus<T>()
{
_publisher = publisher;
_action = action;
var bus = _busses.GetOrAdd(typeof(T), _ => new TypeEventBus<T>());

if (bus is TypeEventBus<T> typeBus)
return typeBus;

throw new InvalidOperationException("Something went wrong in bus management");
}

public void Invoke(T message)
public IEventPublisher<T> GetPublisher<T>()
{
_action.Invoke(message);
return GetTypeBus<T>().CreatePublisher();
}

public void Dispose()
public IEventPublisher<T> GetPublisher<T>(string eventName)
{
_publisher.Unsubscribe(this);
return GetTypeBus<T>().GetNamedBus(eventName).CreatePublisher();
}
}

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)
public ISubscription<T> Subscribe<T>(Action<T> handler)
{
var handle = new SubscriptionHandle<T>(this, action);

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

return handle;
return GetTypeBus<T>().Subscribe(handler);
}

public void Unsubscribe(SubscriptionHandle<T> handle)
public ISubscription<T> Subscribe<T>(Action<T> handler, string eventName)
{
lock (_lock)
{
if (_isDisposed)
return;

_handlers.Remove(handle);
}
return GetTypeBus<T>().GetNamedBus(eventName).Subscribe(handler);
}

public void Publish(T message)
private class TypeEventBus<T>() : EventBus<T>()
{
foreach (var handler in GetHandlersSnapshot())
private readonly ConcurrentDictionary<string, NamedEventBus<T>> _namedBusses = [];

public EventBus<T> GetNamedBus(string eventName)
{
handler.Invoke(message);
return _namedBusses.GetOrAdd(eventName, (_) => new NamedEventBus<T>());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently busses remain in memory forever to reduce any thread risk coming with disposing and creation. In a future update this can be added to reduce memory, but the retained memory should be minimal. With no disposal, busses cannot be disconnected so no issues should arise

}
}

public Task PublishParallel(T message)
private class NamedEventBus<T>() : EventBus<T>() { }

private abstract class EventBus<T>() : IEventBus
{
var tasks = new List<Task>();
private readonly Lock _lock = new();


foreach (var handler in GetHandlersSnapshot())
private HashSet<SubscriptionHandle<T>> _subscription = [];
private HashSet<IEventPublisher<T>> _publishers = [];

public IEventPublisher<T> CreatePublisher()
{
tasks.Add(Task.Run(() => handler.Invoke(message)));
lock (_lock)
{
var publisher = new Publisher<T>(this);
_publishers.Add(publisher);
return publisher;
}
}

return Task.WhenAll(tasks);
}
public void RemovePublisher(IEventPublisher<T> publisher)
{
lock (_lock)
{
_publishers.Remove(publisher);
}
}

private SubscriptionHandle<T>[] GetHandlersSnapshot()
{
lock (_lock)
public ISubscription<T> Subscribe(Action<T> action)
{
if (_isDisposed)
return [];
lock (_lock)
{
var subHandle = new SubscriptionHandle<T>(this, action);
_subscription.Add(subHandle);
return subHandle;
}
}

return [.. _handlers];
public void Unsubscribe(SubscriptionHandle<T> handle)
{
lock (_lock)
{
_subscription.Remove(handle);
}
}
}

public void Dispose()
{
lock (_lock)
public void Publish(T message)
{
if (_isDisposed)
return;
SubscriptionHandle<T>[] subscriptions;

_isDisposed = true;
lock (_lock)
{
subscriptions = [.. _subscription];
}

mediator.UnregisterPublisher<T>(this);
_handlers.Clear();
foreach (var sub in subscriptions)
{
sub.Invoke(message);
}
}

}
}

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

public IEventPublisher<T> RegisterPublisher<T>(RuntimeMod mod) where T : IStarMapEvent
private class Publisher<T>(EventBus<T> bus) : IEventPublisher<T>
{
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");
private bool _isDisposed;

var publisher = new EventPublisher<T>(this);
public void Publish(T message)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _isDisposed), this);
bus.Publish(message);
}

if (!_publishers.TryAdd(type, publisher))
throw new Exception($"A publisher for type '{typeof(T)}' was already registered");
public void Dispose()
{
if (Interlocked.Exchange(ref _isDisposed, true))
return;

return publisher;
bus.RemovePublisher(this);
}
}

public void UnregisterPublisher<T>(IEventPublisher publisher)
private class SubscriptionHandle<T> : ISubscription<T>
{
_publishers.TryRemove(new(typeof(T), publisher));
}
private readonly EventBus<T> _bus;
private readonly Action<T> _action;

public ISubscription<T> Subscribe<T>(Action<T> action) where T : IStarMapEvent
{
if (!_publishers.TryGetValue(typeof(T), out var publisher))
public SubscriptionHandle(EventBus<T> bus, Action<T> action)
{
throw new Exception("A publisher for this event was not found");
_bus = bus;
_action = action;
}

if (publisher is not EventPublisher<T> typedPublisher)
throw new Exception("Something went wrong in the type of the publisher");
public void Invoke(T message)
{
_action.Invoke(message);
}

return typedPublisher.Subscribe(action);
public void Dispose()
{
_bus.Unsubscribe(this);
}
}
}
}
2 changes: 1 addition & 1 deletion StarMap.Core/StarMapCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public StarMapCore(AssemblyLoadContext coreAssemblyLoadContext)
serviceCollection.AddScoped<IModContext, ModContext>();
serviceCollection.AddScoped<IPatchService, PatchService>();
serviceCollection.AddScoped<IModMenuService, ModMenuService>();
serviceCollection.AddScoped<IMediationService, MediationService>();
serviceCollection.AddScoped<IEventBus, EventBus>();
serviceCollection.AddScoped<IModServiceRegistry, ModServiceRegistry>();

serviceCollection.AddSingleton<ICoreService, CoreService>();
Expand Down
Loading
Loading