Files
ShrinkEventBus/Runtime/ShrinkEventBindings.cs
cneicy 1c921f5aec
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:18 +08:00

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);
}
}
}