feat(sdk): migrate to EventBus 2.0
Replace the legacy EventBase runtime with generated multi-bus bindings and explicit scheduling. Migrate app, data, network, demo, and mod consumers; add generated network-event registration and owner-scoped mod content overrides.
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user