294 lines
11 KiB
C#
294 lines
11 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using Cysharp.Threading.Tasks;
|
|
|
|
namespace ShrinkEventBus
|
|
{
|
|
public interface IShrinkBusResolver
|
|
{
|
|
IShrinkEventBus GetBus(ShrinkBusKey key);
|
|
bool TryGetBus(ShrinkBusKey key, out IShrinkEventBus bus);
|
|
}
|
|
|
|
public interface IShrinkGeneratedSubscriber
|
|
{
|
|
IDisposable AttachGenerated(IShrinkBusResolver resolver, ShrinkBusKey? defaultBus = null);
|
|
}
|
|
|
|
public sealed class ShrinkEventBinding : IDisposable
|
|
{
|
|
private readonly List<IDisposable> _items = new();
|
|
private bool _disposed;
|
|
|
|
public void Add(IDisposable subscription)
|
|
{
|
|
if (subscription == null)
|
|
throw new ArgumentNullException(nameof(subscription));
|
|
if (_disposed)
|
|
{
|
|
subscription.Dispose();
|
|
throw new ObjectDisposedException(nameof(ShrinkEventBinding));
|
|
}
|
|
_items.Add(subscription);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
_disposed = true;
|
|
for (var i = _items.Count - 1; i >= 0; i--)
|
|
_items[i].Dispose();
|
|
_items.Clear();
|
|
}
|
|
}
|
|
|
|
public static class ShrinkStaticBindingRegistry
|
|
{
|
|
private sealed class Entry
|
|
{
|
|
public long Id;
|
|
public ShrinkBusKey Key;
|
|
public Func<IShrinkBusResolver, IDisposable> Factory = null!;
|
|
}
|
|
|
|
private static readonly object Gate = new();
|
|
private static readonly List<Entry> Entries = new();
|
|
private static readonly Dictionary<long, IDisposable> Bindings = new();
|
|
private static long _nextId;
|
|
|
|
public static void Register(ShrinkBusKey key,
|
|
Func<IShrinkBusResolver, IDisposable> factory)
|
|
{
|
|
if (factory == null)
|
|
throw new ArgumentNullException(nameof(factory));
|
|
|
|
lock (Gate)
|
|
{
|
|
var entry = new Entry
|
|
{
|
|
Id = Interlocked.Increment(ref _nextId),
|
|
Key = key,
|
|
Factory = factory
|
|
};
|
|
Entries.Add(entry);
|
|
TryAttachLocked(entry, EventBus.Resolver);
|
|
}
|
|
}
|
|
|
|
public static void Register<TEvent>(string bus, Action<TEvent> handler,
|
|
ShrinkEventPriority priority, int numericPriority, bool receiveCanceled)
|
|
where TEvent : IShrinkEvent
|
|
{
|
|
var key = ShrinkBusKey.Parse(bus);
|
|
Register(key, resolver => ShrinkGeneratedBinding.Subscribe(
|
|
resolver, null, bus, null, handler, priority, numericPriority, receiveCanceled));
|
|
}
|
|
|
|
public static void RegisterAsync<TEvent>(string bus, ShrinkAsyncEventHandler<TEvent> handler,
|
|
ShrinkEventPriority priority, int numericPriority, bool receiveCanceled)
|
|
where TEvent : IShrinkEvent
|
|
{
|
|
var key = ShrinkBusKey.Parse(bus);
|
|
Register(key, resolver => ShrinkGeneratedBinding.SubscribeAsync(
|
|
resolver, null, bus, null, handler, priority, numericPriority, receiveCanceled));
|
|
}
|
|
|
|
public static void RegisterAsyncLegacy<TEvent>(string bus, Func<TEvent, UniTask> handler,
|
|
ShrinkEventPriority priority, int numericPriority, bool receiveCanceled)
|
|
where TEvent : IShrinkEvent
|
|
{
|
|
var key = ShrinkBusKey.Parse(bus);
|
|
Register(key, resolver => ShrinkGeneratedBinding.SubscribeAsyncLegacy(
|
|
resolver, null, bus, null, handler, priority, numericPriority, receiveCanceled));
|
|
}
|
|
|
|
internal static void AttachForBus(ShrinkBusKey key, IShrinkBusResolver resolver)
|
|
{
|
|
lock (Gate)
|
|
{
|
|
for (var i = 0; i < Entries.Count; i++)
|
|
{
|
|
if (Entries[i].Key == key)
|
|
TryAttachLocked(Entries[i], resolver);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void DetachBus(ShrinkBusKey key)
|
|
{
|
|
lock (Gate)
|
|
{
|
|
for (var i = 0; i < Entries.Count; i++)
|
|
{
|
|
var entry = Entries[i];
|
|
if (entry.Key != key || !Bindings.TryGetValue(entry.Id, out var binding))
|
|
continue;
|
|
Bindings.Remove(entry.Id);
|
|
binding.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void TryAttachLocked(Entry entry, IShrinkBusResolver resolver)
|
|
{
|
|
if (Bindings.ContainsKey(entry.Id) || !resolver.TryGetBus(entry.Key, out _))
|
|
return;
|
|
Bindings.Add(entry.Id, entry.Factory(resolver));
|
|
}
|
|
}
|
|
|
|
public sealed class ShrinkEventBusHost : IShrinkBusResolver, IDisposable
|
|
{
|
|
private readonly ConcurrentDictionary<ShrinkBusKey, IShrinkEventBus> _buses = new();
|
|
private bool _disposed;
|
|
|
|
public IEnumerable<IShrinkEventBus> Buses => _buses.Values;
|
|
|
|
public IShrinkEventBus CreateBus(ShrinkBusKey key, ShrinkBusOptions options)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(ShrinkEventBusHost));
|
|
if (options == null)
|
|
throw new ArgumentNullException(nameof(options));
|
|
|
|
var bus = new ShrinkEventBusBuilder()
|
|
.WithKey(key)
|
|
.WithOptions(options)
|
|
.Build();
|
|
if (!_buses.TryAdd(key, bus))
|
|
{
|
|
if (bus is IDisposable disposable)
|
|
disposable.Dispose();
|
|
throw new InvalidOperationException($"Bus '{key}' is already registered.");
|
|
}
|
|
return bus;
|
|
}
|
|
|
|
public IShrinkEventBus GetOrCreateBus(ShrinkBusKey key, Func<ShrinkBusOptions> optionsFactory)
|
|
{
|
|
if (_disposed)
|
|
throw new ObjectDisposedException(nameof(ShrinkEventBusHost));
|
|
if (optionsFactory == null)
|
|
throw new ArgumentNullException(nameof(optionsFactory));
|
|
|
|
return _buses.GetOrAdd(key, busKey =>
|
|
new ShrinkEventBusBuilder()
|
|
.WithKey(busKey)
|
|
.WithOptions(optionsFactory())
|
|
.Build());
|
|
}
|
|
|
|
public IShrinkEventBus GetBus(ShrinkBusKey key)
|
|
{
|
|
if (_buses.TryGetValue(key, out var bus))
|
|
return bus;
|
|
throw new KeyNotFoundException($"Bus '{key}' is not registered.");
|
|
}
|
|
|
|
public bool TryGetBus(ShrinkBusKey key, out IShrinkEventBus bus) => _buses.TryGetValue(key, out bus!);
|
|
|
|
public IDisposable Attach(object target, ShrinkBusKey? defaultBus = null)
|
|
{
|
|
if (target == null)
|
|
throw new ArgumentNullException(nameof(target));
|
|
|
|
if (target is IShrinkGeneratedSubscriber generated)
|
|
return generated.AttachGenerated(this, defaultBus);
|
|
|
|
var bus = GetBus(defaultBus ?? ShrinkBusKey.Game);
|
|
return bus.Attach(target);
|
|
}
|
|
|
|
public bool RemoveBus(ShrinkBusKey key)
|
|
{
|
|
if (!_buses.TryRemove(key, out var bus))
|
|
return false;
|
|
if (bus is IDisposable disposable)
|
|
disposable.Dispose();
|
|
return true;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
_disposed = true;
|
|
foreach (var bus in _buses.Values)
|
|
{
|
|
if (bus is IDisposable disposable)
|
|
disposable.Dispose();
|
|
}
|
|
_buses.Clear();
|
|
}
|
|
|
|
public async UniTask ShutdownAsync()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
_disposed = true;
|
|
foreach (var bus in _buses.Values)
|
|
{
|
|
if (bus is ShrinkEventBusInstance instance)
|
|
await instance.ShutdownAsync();
|
|
else
|
|
bus.Dispose();
|
|
}
|
|
_buses.Clear();
|
|
}
|
|
}
|
|
|
|
public static class ShrinkGeneratedBinding
|
|
{
|
|
public static IShrinkBusResolver RuntimeResolver => EventBus.Resolver;
|
|
|
|
public static IDisposable Subscribe<TEvent>(IShrinkBusResolver resolver, ShrinkBusKey? defaultBus,
|
|
string? configuredBus, object? owner, Action<TEvent> handler, ShrinkEventPriority priority,
|
|
int numericPriority, bool receiveCanceled) where TEvent : IShrinkEvent
|
|
{
|
|
var bus = ResolveBus(resolver, defaultBus, configuredBus);
|
|
if (bus is not ShrinkEventBusInstance instance)
|
|
throw new InvalidOperationException("Generated bindings require the built-in ShrinkEventBus implementation.");
|
|
return instance.SubscribeGenerated(handler,
|
|
new ShrinkSubscribeDescriptor(configuredBus, priority, numericPriority, receiveCanceled));
|
|
}
|
|
|
|
public static IDisposable SubscribeAsync<TEvent>(IShrinkBusResolver resolver, ShrinkBusKey? defaultBus,
|
|
string? configuredBus, object? owner, ShrinkAsyncEventHandler<TEvent> handler,
|
|
ShrinkEventPriority priority, int numericPriority, bool receiveCanceled) where TEvent : IShrinkEvent
|
|
{
|
|
var bus = ResolveBus(resolver, defaultBus, configuredBus);
|
|
if (bus is not ShrinkEventBusInstance instance)
|
|
throw new InvalidOperationException("Generated bindings require the built-in ShrinkEventBus implementation.");
|
|
return instance.SubscribeGenerated(handler,
|
|
new ShrinkSubscribeDescriptor(configuredBus, priority, numericPriority, receiveCanceled));
|
|
}
|
|
|
|
public static IDisposable SubscribeAsyncLegacy<TEvent>(IShrinkBusResolver resolver,
|
|
ShrinkBusKey? defaultBus, string? configuredBus, object? owner,
|
|
Func<TEvent, UniTask> handler, ShrinkEventPriority priority,
|
|
int numericPriority, bool receiveCanceled) where TEvent : IShrinkEvent
|
|
{
|
|
if (handler == null)
|
|
throw new ArgumentNullException(nameof(handler));
|
|
return SubscribeAsync<TEvent>(resolver, defaultBus, configuredBus, owner,
|
|
(eventData, _) => handler(eventData), priority, numericPriority, receiveCanceled);
|
|
}
|
|
|
|
private static IShrinkEventBus ResolveBus(IShrinkBusResolver resolver, ShrinkBusKey? defaultBus,
|
|
string? configuredBus)
|
|
{
|
|
if (resolver == null)
|
|
throw new ArgumentNullException(nameof(resolver));
|
|
var key = !string.IsNullOrWhiteSpace(configuredBus)
|
|
? ShrinkBusKey.Parse(configuredBus)
|
|
: defaultBus ?? ShrinkBusKey.Game;
|
|
return resolver.GetBus(key);
|
|
}
|
|
}
|
|
}
|