201 lines
7.2 KiB
C#
201 lines
7.2 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using Cysharp.Threading.Tasks;
|
|
|
|
namespace ShrinkEventBus
|
|
{
|
|
internal readonly struct ShrinkEventTrace
|
|
{
|
|
public ShrinkEventTrace(DateTime timestampUtc, Type eventType, ShrinkBusKey busKey,
|
|
ShrinkBusOptions options, int threadId, long elapsedTimestampTicks,
|
|
ShrinkPostResult result, bool isAsync)
|
|
{
|
|
TimestampUtc = timestampUtc;
|
|
EventType = eventType;
|
|
BusKey = busKey;
|
|
Scheduler = options.Scheduler;
|
|
DispatchMode = options.DispatchMode;
|
|
ThreadId = threadId;
|
|
ElapsedTimestampTicks = elapsedTimestampTicks;
|
|
Result = result;
|
|
IsAsync = isAsync;
|
|
}
|
|
|
|
public DateTime TimestampUtc { get; }
|
|
public Type EventType { get; }
|
|
public ShrinkBusKey BusKey { get; }
|
|
public ShrinkBusSchedulerKind Scheduler { get; }
|
|
public ShrinkDispatchMode DispatchMode { get; }
|
|
public int ThreadId { get; }
|
|
public long ElapsedTimestampTicks { get; }
|
|
public ShrinkPostResult Result { get; }
|
|
public bool IsAsync { get; }
|
|
}
|
|
|
|
public static class EventBus
|
|
{
|
|
private sealed class GlobalBusResolver : IShrinkBusResolver
|
|
{
|
|
public IShrinkEventBus GetBus(ShrinkBusKey key)
|
|
{
|
|
if (TryGetBus(key, out var bus))
|
|
return bus;
|
|
throw new KeyNotFoundException($"Bus '{key}' is not registered.");
|
|
}
|
|
|
|
public bool TryGetBus(ShrinkBusKey key, out IShrinkEventBus bus) =>
|
|
EventBus.TryGetBus(key, out bus);
|
|
}
|
|
|
|
private static readonly ConcurrentDictionary<ShrinkBusKey, IShrinkEventBus> Buses = new();
|
|
internal static readonly IShrinkBusResolver Resolver = new GlobalBusResolver();
|
|
private static readonly IShrinkEventBus DefaultBus = CreateDefaultBus();
|
|
private static Action<IShrinkEvent, Type, ShrinkBusKey>? _posted;
|
|
private static Action<ShrinkEventTrace>? _detailedPosted;
|
|
|
|
internal static event Action<IShrinkEvent, Type, ShrinkBusKey> Posted
|
|
{
|
|
add
|
|
{
|
|
_posted += value;
|
|
}
|
|
remove
|
|
{
|
|
_posted -= value;
|
|
}
|
|
}
|
|
|
|
internal static bool HasPostedObservers
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => Volatile.Read(ref _posted) != null;
|
|
}
|
|
|
|
internal static event Action<ShrinkEventTrace> DetailedPosted
|
|
{
|
|
add => _detailedPosted += value;
|
|
remove => _detailedPosted -= value;
|
|
}
|
|
|
|
internal static bool HasDetailedPostedObservers
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
get => Volatile.Read(ref _detailedPosted) != null;
|
|
}
|
|
|
|
public static IShrinkEventBus Default => DefaultBus;
|
|
|
|
public static IShrinkEventBus CreateBus(ShrinkBusKey key, ShrinkBusOptions options)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException(nameof(options));
|
|
|
|
var bus = BuildBus(key, options);
|
|
if (!Buses.TryAdd(key, bus))
|
|
{
|
|
bus.Dispose();
|
|
throw new InvalidOperationException($"Bus '{key}' is already registered.");
|
|
}
|
|
ShrinkStaticBindingRegistry.AttachForBus(key, Resolver);
|
|
return bus;
|
|
}
|
|
|
|
public static IShrinkEventBus GetOrCreateBus(ShrinkBusKey key, ShrinkBusOptions options)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException(nameof(options));
|
|
var bus = Buses.GetOrAdd(key, busKey => BuildBus(busKey, options));
|
|
ShrinkStaticBindingRegistry.AttachForBus(key, Resolver);
|
|
return bus;
|
|
}
|
|
|
|
public static bool TryGetBus(ShrinkBusKey key, out IShrinkEventBus bus) =>
|
|
Buses.TryGetValue(key, out bus!);
|
|
|
|
public static bool RemoveBus(ShrinkBusKey key)
|
|
{
|
|
if (key == ShrinkBusKey.Game || !Buses.TryRemove(key, out var bus))
|
|
return false;
|
|
ShrinkStaticBindingRegistry.DetachBus(key);
|
|
bus.Dispose();
|
|
return true;
|
|
}
|
|
|
|
public static ShrinkPostResult Post<TEvent>(in TEvent eventData)
|
|
where TEvent : IShrinkEvent => DefaultBus.Post(in eventData);
|
|
|
|
public static UniTask<ShrinkPostResult> PostAsync<TEvent>(TEvent eventData,
|
|
CancellationToken cancellationToken = default)
|
|
where TEvent : IShrinkEvent => DefaultBus.PostAsync(eventData, cancellationToken);
|
|
|
|
public static IDisposable Attach(object target, ShrinkBusKey? defaultBus = null)
|
|
{
|
|
if (target == null)
|
|
throw new ArgumentNullException(nameof(target));
|
|
if (target is not IShrinkGeneratedSubscriber generated)
|
|
throw new InvalidOperationException(
|
|
$"Type {target.GetType().FullName} has no generated [ShrinkSubscribe] binding.");
|
|
return generated.AttachGenerated(Resolver, defaultBus ?? ShrinkBusKey.Game);
|
|
}
|
|
|
|
internal static IReadOnlyList<(ShrinkBusKey Key, ShrinkBusOptions Options, int Subscribers)> Snapshot()
|
|
{
|
|
var snapshot = new List<(ShrinkBusKey, ShrinkBusOptions, int)>(Buses.Count);
|
|
foreach (var pair in Buses)
|
|
{
|
|
var subscribers = pair.Value is ShrinkEventBusInstance instance
|
|
? instance.SubscriberCount
|
|
: 0;
|
|
snapshot.Add((pair.Key, pair.Value.Options, subscribers));
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
private static IShrinkEventBus CreateDefaultBus()
|
|
{
|
|
var bus = BuildBus(ShrinkBusKey.Game, ShrinkBusOptions.MainThread());
|
|
Buses[ShrinkBusKey.Game] = bus;
|
|
ShrinkStaticBindingRegistry.AttachForBus(ShrinkBusKey.Game, Resolver);
|
|
return bus;
|
|
}
|
|
|
|
private static IShrinkEventBus BuildBus(ShrinkBusKey key, ShrinkBusOptions options) =>
|
|
new ShrinkEventBusBuilder()
|
|
.WithKey(key)
|
|
.WithOptions(options)
|
|
.WithPostObserver(NotifyPostedObject)
|
|
.Build();
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static void NotifyPostedObject(IShrinkEvent eventData, Type eventType, ShrinkBusKey key)
|
|
{
|
|
_posted?.Invoke(eventData, eventType, key);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static void NotifyDetailedPosted(ShrinkEventTrace trace)
|
|
{
|
|
var observers = Volatile.Read(ref _detailedPosted);
|
|
if (observers == null)
|
|
return;
|
|
try
|
|
{
|
|
observers(trace);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
#if UNITY_5_3_OR_NEWER
|
|
UnityEngine.Debug.LogException(exception);
|
|
#else
|
|
System.Diagnostics.Trace.TraceError(exception.ToString());
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|