Files
ShrinkEventBus/Runtime/EventBus.cs
T

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