214 lines
8.0 KiB
C#
214 lines
8.0 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace ShrinkEventBus
|
|
{
|
|
/// <summary>Marker shared by managed, Unity and ECS event payloads.</summary>
|
|
public interface IShrinkEvent
|
|
{
|
|
}
|
|
|
|
public interface IShrinkCancelableEvent : IShrinkEvent
|
|
{
|
|
bool IsCanceled { get; }
|
|
void SetCanceled(bool value);
|
|
}
|
|
|
|
public interface IShrinkResultEvent<TResult> : IShrinkEvent
|
|
{
|
|
TResult Result { get; }
|
|
void SetResult(TResult result);
|
|
}
|
|
|
|
public enum ShrinkEventPriority
|
|
{
|
|
Highest = 0,
|
|
High = 1,
|
|
Normal = 2,
|
|
Low = 3,
|
|
Lowest = 4,
|
|
Monitor = 5
|
|
}
|
|
|
|
public enum ShrinkDispatchMode
|
|
{
|
|
Ordered = 0,
|
|
Parallel = 1
|
|
}
|
|
|
|
public enum ShrinkBusSchedulerKind
|
|
{
|
|
Inline = 0,
|
|
MainThread = 1,
|
|
DedicatedThread = 2,
|
|
TaskPool = 3
|
|
}
|
|
|
|
public enum ShrinkQueueOverflowPolicy
|
|
{
|
|
Reject = 0,
|
|
DropNewest = 1,
|
|
DropOldest = 2,
|
|
Wait = 3
|
|
}
|
|
|
|
public enum ShrinkPostFailure
|
|
{
|
|
None = 0,
|
|
BusStopped = 1,
|
|
QueueFull = 2,
|
|
Canceled = 3,
|
|
HandlerException = 4,
|
|
InvalidEvent = 5
|
|
}
|
|
|
|
public readonly struct ShrinkPostResult
|
|
{
|
|
private const int AcceptedMask = 1 << 0;
|
|
private const int HandledMask = 1 << 1;
|
|
private const int CanceledMask = 1 << 2;
|
|
private const int FailureShift = 8;
|
|
private readonly int _value;
|
|
|
|
public ShrinkPostResult(bool accepted, bool handled, bool canceled,
|
|
ShrinkPostFailure failure = ShrinkPostFailure.None)
|
|
{
|
|
_value = (accepted ? AcceptedMask : 0) |
|
|
(handled ? HandledMask : 0) |
|
|
(canceled ? CanceledMask : 0) |
|
|
((int)failure << FailureShift);
|
|
}
|
|
|
|
public bool Accepted => (_value & AcceptedMask) != 0;
|
|
public bool Handled => (_value & HandledMask) != 0;
|
|
public bool Canceled => (_value & CanceledMask) != 0;
|
|
public ShrinkPostFailure Failure => (ShrinkPostFailure)((uint)_value >> FailureShift);
|
|
public bool Succeeded => Accepted && Failure == ShrinkPostFailure.None;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ShrinkPostResult Rejected(ShrinkPostFailure failure) =>
|
|
new(false, false, false, failure);
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static ShrinkPostResult Completed(bool handled, bool canceled = false) =>
|
|
new(true, handled, canceled);
|
|
}
|
|
|
|
public readonly struct ShrinkBusKey : IEquatable<ShrinkBusKey>
|
|
{
|
|
public ShrinkBusKey(string scope, string name)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(scope))
|
|
throw new ArgumentException("Bus scope must not be empty.", nameof(scope));
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
throw new ArgumentException("Bus name must not be empty.", nameof(name));
|
|
|
|
Scope = scope.Trim().ToLowerInvariant();
|
|
Name = name.Trim();
|
|
}
|
|
|
|
public string Scope { get; }
|
|
public string Name { get; }
|
|
public string Id => Scope == "game" && Name == "default" ? "game" : $"{Scope}:{Name}";
|
|
|
|
public static ShrinkBusKey Game => new("game", "default");
|
|
public static ShrinkBusKey Server => new("server", "default");
|
|
public static ShrinkBusKey Scene(string sceneId) => new("scene", sceneId);
|
|
public static ShrinkBusKey Mod(string modId) => new("mod", modId);
|
|
public static ShrinkBusKey World(string worldId) => new("world", worldId);
|
|
|
|
public static ShrinkBusKey Parse(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value) || string.Equals(value.Trim(), "game", StringComparison.OrdinalIgnoreCase))
|
|
return Game;
|
|
|
|
var normalized = value.Trim();
|
|
var separator = normalized.IndexOf(':');
|
|
return separator <= 0 || separator == normalized.Length - 1
|
|
? new ShrinkBusKey("custom", normalized)
|
|
: new ShrinkBusKey(normalized.Substring(0, separator), normalized.Substring(separator + 1));
|
|
}
|
|
|
|
public bool Equals(ShrinkBusKey other) =>
|
|
string.Equals(Scope, other.Scope, StringComparison.OrdinalIgnoreCase) &&
|
|
string.Equals(Name, other.Name, StringComparison.Ordinal);
|
|
|
|
public override bool Equals(object? obj) => obj is ShrinkBusKey other && Equals(other);
|
|
public override int GetHashCode() => HashCode.Combine(Scope.ToLowerInvariant(), Name);
|
|
public override string ToString() => Id;
|
|
public static bool operator ==(ShrinkBusKey left, ShrinkBusKey right) => left.Equals(right);
|
|
public static bool operator !=(ShrinkBusKey left, ShrinkBusKey right) => !left.Equals(right);
|
|
}
|
|
|
|
public sealed class ShrinkBusOptions
|
|
{
|
|
public ShrinkBusSchedulerKind Scheduler { get; set; } = ShrinkBusSchedulerKind.Inline;
|
|
public ShrinkDispatchMode DispatchMode { get; set; } = ShrinkDispatchMode.Ordered;
|
|
public int QueueCapacity { get; set; } = 4096;
|
|
public int MaxConcurrency { get; set; } = Math.Max(1, Environment.ProcessorCount);
|
|
public ShrinkQueueOverflowPolicy OverflowPolicy { get; set; } = ShrinkQueueOverflowPolicy.Reject;
|
|
public bool DrainOnShutdown { get; set; } = true;
|
|
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
|
|
|
|
public static ShrinkBusOptions Inline(ShrinkDispatchMode mode = ShrinkDispatchMode.Ordered) =>
|
|
new() { Scheduler = ShrinkBusSchedulerKind.Inline, DispatchMode = mode };
|
|
|
|
public static ShrinkBusOptions MainThread(int queueCapacity = 4096) =>
|
|
new() { Scheduler = ShrinkBusSchedulerKind.MainThread, QueueCapacity = queueCapacity };
|
|
|
|
public static ShrinkBusOptions DedicatedThread(int queueCapacity = 4096) =>
|
|
new() { Scheduler = ShrinkBusSchedulerKind.DedicatedThread, QueueCapacity = queueCapacity };
|
|
|
|
public static ShrinkBusOptions TaskPool(int maxConcurrency = 0, int queueCapacity = 4096) =>
|
|
new()
|
|
{
|
|
Scheduler = ShrinkBusSchedulerKind.TaskPool,
|
|
DispatchMode = ShrinkDispatchMode.Parallel,
|
|
QueueCapacity = queueCapacity,
|
|
MaxConcurrency = maxConcurrency > 0 ? maxConcurrency : Math.Max(1, Environment.ProcessorCount)
|
|
};
|
|
|
|
internal ShrinkBusOptions CloneValidated()
|
|
{
|
|
if (QueueCapacity <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(QueueCapacity));
|
|
if (MaxConcurrency <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(MaxConcurrency));
|
|
if (ShutdownTimeout < TimeSpan.Zero)
|
|
throw new ArgumentOutOfRangeException(nameof(ShutdownTimeout));
|
|
if (OverflowPolicy == ShrinkQueueOverflowPolicy.Wait && Scheduler == ShrinkBusSchedulerKind.Inline)
|
|
throw new InvalidOperationException("Inline buses do not have a queue and cannot use Wait overflow policy.");
|
|
|
|
return new ShrinkBusOptions
|
|
{
|
|
Scheduler = Scheduler,
|
|
DispatchMode = DispatchMode,
|
|
QueueCapacity = QueueCapacity,
|
|
MaxConcurrency = MaxConcurrency,
|
|
OverflowPolicy = OverflowPolicy,
|
|
DrainOnShutdown = DrainOnShutdown,
|
|
ShutdownTimeout = ShutdownTimeout
|
|
};
|
|
}
|
|
}
|
|
|
|
public readonly struct ShrinkSubscribeDescriptor
|
|
{
|
|
public ShrinkSubscribeDescriptor(string? bus, ShrinkEventPriority priority,
|
|
int numericPriority, bool receiveCanceled)
|
|
{
|
|
Bus = bus ?? string.Empty;
|
|
Priority = priority;
|
|
NumericPriority = numericPriority;
|
|
ReceiveCanceled = receiveCanceled;
|
|
}
|
|
|
|
public string Bus { get; }
|
|
public ShrinkEventPriority Priority { get; }
|
|
public int NumericPriority { get; }
|
|
public bool ReceiveCanceled { get; }
|
|
}
|
|
}
|