This commit is contained in:
cneicy committed 2026-03-08 00:40:13 +08:00
1 parent 3dfbccc9d6
commit 8549ba5c80
38 files changed
+2187

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
using System;
namespace ShrinkEventBus
{
[AttributeUsage(AttributeTargets.Class)]
public class CancelableAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Class)]
public class HasResultAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Class)]
public class EventBusSubscriberAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
public class EventSubscribeAttribute : Attribute
{
public EventPriority Priority { get; set; }
public bool ReceiveCanceled { get; set; }
public int NumericPriority { get; set; }
public EventSubscribeAttribute()
{
Priority = EventPriority.NORMAL;
ReceiveCanceled = false;
NumericPriority = 0;
}
public EventSubscribeAttribute(EventPriority priority, bool receiveCanceled = false)
{
Priority = priority;
ReceiveCanceled = receiveCanceled;
NumericPriority = 0;
}
public EventSubscribeAttribute(int priority)
{
NumericPriority = priority;
Priority = PriorityHelper.ConvertToEventPriority(priority);
ReceiveCanceled = false;
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9bb9d82b04a04f3085b9e68ab4f1ba60
timeCreated: 1760098802
+136
View File
@@ -0,0 +1,136 @@
using System;
using System.Collections.Concurrent;
using System.Reflection;
using UnityEngine;
namespace ShrinkEventBus
{
public static class EventAutoRegHelper
{
private static readonly ConcurrentDictionary<Type, bool> SubscriberTypes = new();
private static readonly object InitLock = new();
public static bool IsInitialized { get; private set; }
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void PreInitializeReflection()
{
if (SubscriberTypes.Count == 0) ScanEventBusSubscribers();
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void InitializeAfterSceneLoad()
{
EnsureInitialized();
}
public static void EnsureInitialized()
{
if (IsInitialized) return;
lock (InitLock)
{
if (IsInitialized) return;
ScanEventBusSubscribers();
ScanExistingObjects();
IsInitialized = true;
}
}
private static void ScanExistingObjects()
{
if (SubscriberTypes.Count == 0)
{
#if UNITY_2023_1_OR_NEWER
var allMonoBehaviours =
UnityEngine.Object.FindObjectsByType<MonoBehaviour>(UnityEngine.FindObjectsInactive.Include,
UnityEngine.FindObjectsSortMode.None);
#else
var allMonoBehaviours = UnityEngine.Object.FindObjectsOfType<MonoBehaviour>(true);
#endif
foreach (var mb in allMonoBehaviours) ProcessNodeRegistration(mb);
return;
}
foreach (var targetType in SubscriberTypes.Keys)
{
#if UNITY_2023_1_OR_NEWER
var objects = UnityEngine.Object.FindObjectsByType(targetType, UnityEngine.FindObjectsInactive.Include,
UnityEngine.FindObjectsSortMode.None);
#else
var objects = UnityEngine.Object.FindObjectsOfType(targetType, true);
#endif
foreach (var obj in objects) ProcessNodeRegistration(obj);
}
}
private static void ScanEventBusSubscribers()
{
if (SubscriberTypes.Count > 0) return;
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
if (IsSystemAssembly(assembly)) continue;
try
{
foreach (var type in assembly.GetTypes())
{
if (type.GetCustomAttribute<EventBusSubscriberAttribute>() != null &&
typeof(MonoBehaviour).IsAssignableFrom(type))
SubscriberTypes.TryAdd(type, true);
}
}
catch (ReflectionTypeLoadException ex)
{
foreach (var type in ex.Types)
{
if (type?.GetCustomAttribute<EventBusSubscriberAttribute>() != null &&
typeof(MonoBehaviour).IsAssignableFrom(type))
SubscriberTypes.TryAdd(type, true);
}
}
catch
{
// ignored
}
}
}
private static bool IsSystemAssembly(Assembly assembly)
{
var name = assembly.FullName ?? "";
return name.StartsWith("System.") || name.StartsWith("Microsoft.") || name.StartsWith("mscorlib") ||
name.StartsWith("UnityEngine.") || name.StartsWith("UnityEditor.");
}
private static bool ProcessNodeRegistration(object node)
{
if (node == null) return false;
var nodeType = node.GetType();
if (SubscriberTypes.Count == 0 && nodeType.GetCustomAttribute<EventBusSubscriberAttribute>() != null)
SubscriberTypes.TryAdd(nodeType, true);
if (!SubscriberTypes.ContainsKey(nodeType)) return false;
if (EventBus.IsInstanceRegistered(node)) return false;
EventBus.AutoRegister(node);
return true;
}
public static void UnregisterNode(object node)
{
if (node == null) return;
EventBus.UnregisterInstance(node);
}
public static void ForceScanCurrentScene() => ScanExistingObjects();
public static void Cleanup()
{
lock (InitLock)
{
SubscriberTypes.Clear();
IsInitialized = false;
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f4d79f65274145cab7614950a64a92f5
timeCreated: 1760099405
+84
View File
@@ -0,0 +1,84 @@
using System;
using System.Reflection;
namespace ShrinkEventBus
{
public abstract class EventBase : IDisposable
{
private readonly ListenerList _listenerList = new();
private bool _isCanceled;
private EventResult _result = EventResult.DEFAULT;
public EventHandlerInfo? CurrentHandler { get; internal set; }
public DateTime EventTime { get; private set; } = DateTime.UtcNow;
public Guid EventId { get; private set; } = Guid.NewGuid();
public bool IsCancelable { get; }
public bool HasResult { get; }
public EventPriority? Phase { get; private set; }
internal bool IsInPool { get; set; }
internal Action<EventBase> ReleaseAction { get; set; }
protected EventBase()
{
IsCancelable = GetType().GetCustomAttribute<CancelableAttribute>() != null;
HasResult = GetType().GetCustomAttribute<HasResultAttribute>() != null;
Setup();
}
protected virtual void Setup() { }
protected virtual void OnReset() { }
internal void ResetInternal()
{
_isCanceled = false;
_result = EventResult.DEFAULT;
CurrentHandler = null;
Phase = null;
EventTime = DateTime.UtcNow;
EventId = Guid.NewGuid();
_listenerList.Clear();
OnReset();
}
public bool IsCanceled
{
get => _isCanceled;
set
{
if (!IsCancelable) throw new UnsupportedOperationException();
_isCanceled = value;
}
}
public EventResult Result
{
get => _result;
set
{
if (!HasResult) throw new InvalidOperationException();
_result = value;
}
}
internal void SetPhase(EventPriority value)
{
if (Phase == value) return;
if (Phase != null && Phase.Value.CompareTo(value) > 0)
throw new ArgumentException();
Phase = value;
}
public void SetCanceled(bool canceled) => IsCanceled = canceled;
public void SetResult(EventResult result) => Result = result;
public ListenerList GetListenerList() => _listenerList;
public EventHandlerInfo[] GetSubscribers() => _listenerList.GetHandlers();
public void Dispose()
{
ReleaseAction?.Invoke(this);
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0b978c5174cf44d893a41950e7a504e3
timeCreated: 1760098714
+451
View File
@@ -0,0 +1,451 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace ShrinkEventBus
{
public static class EventBus
{
public static bool EnableDebugRecord { get; set; }
#if UNITY_EDITOR
public static event Action<EventBase, string, string> OnEventTriggeredForEditor;
#endif
private static readonly Dictionary<Type, ListenerList> EventHandlers = new();
private static readonly HashSet<object> RegisteredInstances = new();
private static readonly object InstanceLock = new();
static EventBus()
{
EventBusRegHelper.RegStaticEventHandler();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureAutoManagerInitialized() => EventAutoRegHelper.EnsureInitialized();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Action<TEvent> handler, string methodName, Type declaringType,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase
{
var methodInfo = declaringType.GetMethod(methodName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
RegisterEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Sync Handler (Priority: {priority})", methodInfo);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler, int priority = 0)
where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
RegisterEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Async Handler (Priority: {priority})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Action<TEvent> handler, EventPriority priority = EventPriority.NORMAL,
bool receiveCanceled = false) where TEvent : EventBase
{
RegisterEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Sync Handler (Priority: {priority})", handler.Method);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Action<TEvent> handler, int priority = 0) where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
RegisterEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Sync Handler (Priority: {priority})", handler.Method);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase
{
RegisterEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Async Handler (Priority: {priority})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void RegisterEventInternal(Type eventType, Delegate handler, EventPriority priority,
int numericPriority, bool receiveCanceled, string debugInfo = "", MethodInfo originalMethod = null)
{
lock (EventHandlers)
{
if (!EventHandlers.TryGetValue(eventType, out var collection))
{
collection = new ListenerList();
EventHandlers[eventType] = collection;
var cacheType = typeof(EventCache<>).MakeGenericType(eventType);
cacheType.GetField("List", BindingFlags.Static | BindingFlags.Public)?.SetValue(null, collection);
}
collection.Add(handler, priority, numericPriority, receiveCanceled, debugInfo, originalMethod);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterEvent<TEvent>(Func<TEvent, UniTask> handler) where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
if (!EventHandlers.TryGetValue(eventType, out var collection)) return;
collection.Remove(handler);
if (collection.Count == 0)
{
EventHandlers.Remove(eventType);
EventCache<TEvent>.List = null;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterEvent<TEvent>(Action<TEvent> handler) where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
if (!EventHandlers.TryGetValue(eventType, out var collection)) return;
collection.Remove(handler);
if (collection.Count == 0)
{
EventHandlers.Remove(eventType);
EventCache<TEvent>.List = null;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ClearAllSubscribersForEvent<TEvent>() where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
if (EventHandlers.Remove(eventType))
{
EventCache<TEvent>.List = null;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterAllEventsForObject(object targetObject)
{
if (targetObject is null) return;
lock (EventHandlers)
{
var eventTypesToRemove = new List<Type>();
foreach (var kvp in EventHandlers)
{
kvp.Value.RemoveTarget(targetObject);
if (kvp.Value.Count == 0) eventTypesToRemove.Add(kvp.Key);
}
foreach (var eventType in eventTypesToRemove)
{
EventHandlers.Remove(eventType);
var cacheType = typeof(EventCache<>).MakeGenericType(eventType);
cacheType.GetField("List", BindingFlags.Static | BindingFlags.Public)?.SetValue(null, null);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterInstance(object targetObject)
{
if (targetObject is null) return;
lock (InstanceLock)
{
RegisteredInstances.Remove(targetObject);
}
UnregisterAllEventsForObject(targetObject);
if (targetObject is not MonoBehaviour mb || !mb) return;
try
{
if (!mb.gameObject ||
!mb.gameObject.TryGetComponent<EventBusDestroyListener>(out var listener)) return;
if (listener)
{
UnityEngine.Object.Destroy(listener);
}
}
catch (MissingReferenceException)
{
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterAllEvents()
{
lock (EventHandlers)
{
foreach (var type in EventHandlers.Keys)
{
var cacheType = typeof(EventCache<>).MakeGenericType(type);
cacheType.GetField("List", BindingFlags.Static | BindingFlags.Public).SetValue(null, null);
}
EventHandlers.Clear();
}
lock (InstanceLock)
{
RegisteredInstances.Clear();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
EnsureAutoManagerInitialized();
var collection = EventCache<TEvent>.List;
if (collection == null) return false;
var handlers = collection.GetHandlers();
var wasHandled = false;
#if UNITY_EDITOR
RecordEditorTrace(handlers, eventArgs, typeof(TEvent));
#endif
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
eventArgs.CurrentHandler = handlerInfo;
eventArgs.SetPhase(handlerInfo.Priority);
if (eventArgs is { IsCancelable: true, IsCanceled: true } && !handlerInfo.ReceiveCanceled) continue;
try
{
if (handlerInfo.Handler is Action<TEvent> syncHandler)
{
syncHandler(eventArgs);
wasHandled = true;
}
else if (handlerInfo.Handler is Func<TEvent, UniTask> asyncHandler)
{
await asyncHandler(eventArgs);
wasHandled = true;
}
}
catch (Exception ex)
{
Debug.LogException(ex);
Debug.LogError(
$"[EventBus] {typeof(TEvent).Name} Async Handler {handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName} Exception: {ex.Message}");
}
}
eventArgs.CurrentHandler = null;
return wasHandled;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
EnsureAutoManagerInitialized();
var collection = EventCache<TEvent>.List;
if (collection == null) return false;
var handlers = collection.GetHandlers();
var wasHandled = false;
#if UNITY_EDITOR
RecordEditorTrace(handlers, eventArgs, typeof(TEvent));
#endif
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
eventArgs.CurrentHandler = handlerInfo;
eventArgs.SetPhase(handlerInfo.Priority);
if (eventArgs is { IsCancelable: true, IsCanceled: true } && !handlerInfo.ReceiveCanceled) continue;
try
{
if (handlerInfo.Handler is Action<TEvent> syncHandler)
{
syncHandler(eventArgs);
wasHandled = true;
}
else if (handlerInfo.Handler is Func<TEvent, UniTask> asyncHandler)
{
FireAndForgetSafe(() => asyncHandler(eventArgs),
$"{handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName}");
wasHandled = true;
}
}
catch (Exception ex)
{
Debug.LogException(ex);
Debug.LogError(
$"[EventBus] {typeof(TEvent).Name} Sync Handler {handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName} Exception: {ex.Message}");
}
}
eventArgs.CurrentHandler = null;
return wasHandled;
}
private static void FireAndForgetSafe(Func<UniTask> action, string handlerName)
{
action().Forget(e =>
{
Debug.LogException(e);
Debug.LogError($"[EventBus] Async Handler {handlerName} threw exception: {e.Message}");
});
}
#if UNITY_EDITOR
private static void RecordEditorTrace<TEvent>(EventHandlerInfo[] handlers, TEvent eventArgs, Type eventType)
where TEvent : EventBase
{
if (!EnableDebugRecord) return;
for (var i = 0; i < handlers.Length; i++)
{
var handler = handlers[i];
MethodInfo originalMethod = null;
var type = handler.GetType();
var prop = type.GetProperty("OriginalMethod");
if (prop != null) originalMethod = prop.GetValue(handler) as MethodInfo;
else
{
var field = type.GetField("OriginalMethod");
if (field != null) originalMethod = field.GetValue(handler) as MethodInfo;
}
eventArgs.GetListenerList().Add(handler.Handler, handler.Priority, handler.NumericPriority,
handler.ReceiveCanceled, handler.DebugInfo, originalMethod);
}
OnEventTriggeredForEditor?.Invoke(eventArgs, eventType.Name, GetSenderInfo());
}
private static string GetSenderInfo()
{
var senderInfo = "Unknown";
try
{
var trace = new System.Diagnostics.StackTrace(0, false);
for (var i = 0; i < trace.FrameCount; i++)
{
var method = trace.GetFrame(i)?.GetMethod();
var declaringType = method?.DeclaringType;
if (declaringType == null) continue;
if (declaringType == typeof(EventBus) ||
declaringType.DeclaringType == typeof(EventBus)) continue;
var ns = declaringType.Namespace ?? "";
if (ns.StartsWith("System") || ns.StartsWith("Cysharp") || ns.StartsWith("UnityEngine"))
continue;
var className = declaringType.Name;
var methodName = method.Name;
if (declaringType.DeclaringType != null && className.StartsWith("<") &&
className.Contains(">"))
{
className = declaringType.DeclaringType.Name;
var startIndex = declaringType.Name.IndexOf('<') + 1;
var endIndex = declaringType.Name.IndexOf('>');
if (startIndex > 0 && endIndex > startIndex)
methodName = declaringType.Name.Substring(startIndex, endIndex - startIndex);
}
return $"{className}.{methodName}()";
}
}
catch
{
// ignored
}
return senderInfo;
}
#endif
public static EventHandlerInfo[] GetEventSubscribers<TEvent>() where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
return EventHandlers.TryGetValue(eventType, out var collection)
? collection.GetHandlers()
: Array.Empty<EventHandlerInfo>();
}
}
public static ListenerList GetListenerList<TEvent>() where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
if (EventHandlers.TryGetValue(eventType, out var collection)) return collection;
return new ListenerList();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AutoRegister(object target)
{
if (target == null) return;
if (IsInstanceRegistered(target)) return;
EventBusRegHelper.RegisterEventHandlers(target);
lock (InstanceLock)
{
if (!RegisteredInstances.Add(target)) return;
}
if (target is MonoBehaviour mb && mb && mb.gameObject)
{
if (!mb.gameObject.TryGetComponent<EventBusDestroyListener>(out _))
{
var listener = mb.gameObject.AddComponent<EventBusDestroyListener>();
listener.Target = target;
listener.hideFlags = HideFlags.HideAndDontSave;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInstanceRegistered(object target)
{
if (target == null) return false;
lock (InstanceLock)
{
return RegisteredInstances.Contains(target);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetRegisteredInstanceCount()
{
lock (InstanceLock)
{
return RegisteredInstances.Count;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetRegisteredEventTypeCount()
{
lock (EventHandlers)
{
return EventHandlers.Count;
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b995a65fd1324255a5ade1ae1f0adf32
timeCreated: 1760099076
+18
View File
@@ -0,0 +1,18 @@
using UnityEngine;
namespace ShrinkEventBus
{
[DisallowMultipleComponent]
public class EventBusDestroyListener : MonoBehaviour
{
public object Target { get; set; }
private void OnDestroy()
{
if (Target != null)
{
EventBus.UnregisterInstance(Target);
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0fd1c9ba32a842199e4285383d83b3b3
timeCreated: 1772889501
+162
View File
@@ -0,0 +1,162 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace ShrinkEventBus
{
public static class EventBusRegHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegStaticEventHandler() => RegisterEventHandlersInternal(null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEventHandlers(object target) => RegisterEventHandlersInternal(target);
private static void RegisterEventHandlersInternal(object target)
{
var isStatic = target == null;
if (isStatic)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
foreach (var type in assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(EventBusSubscriberAttribute), false).Length == 0)
continue;
ScanMethodsAndRegister(null,
type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
}
}
catch
{
// ignored
}
}
}
else
{
var type = target.GetType();
if (type.GetCustomAttributes(typeof(EventBusSubscriberAttribute), false).Length == 0) return;
ScanMethodsAndRegister(target,
type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
}
}
private static void ScanMethodsAndRegister(object target, MethodInfo[] methods)
{
foreach (var method in methods)
{
if (target != null && method.IsStatic) continue;
var attributes = method.GetCustomAttributes(typeof(EventSubscribeAttribute), false);
if (attributes.Length == 0) continue;
var subscribeAttr = (EventSubscribeAttribute)attributes[0];
var parameters = method.GetParameters();
if (parameters.Length != 1) continue;
var parameterType = parameters[0].ParameterType;
if (!typeof(EventBase).IsAssignableFrom(parameterType)) continue;
ProcessMethodRegistration(target, method, subscribeAttr, parameterType);
}
}
private static void ProcessMethodRegistration(object target, MethodInfo method,
EventSubscribeAttribute subscribeAttr, Type parameterType)
{
try
{
string scope = target == null ? "Static" : "Instance";
string typeName = target == null ? method.DeclaringType?.Name : target.GetType().Name;
if (method.ReturnType == typeof(UniTask))
{
var funcType = typeof(Func<,>).MakeGenericType(parameterType, typeof(UniTask));
var handlerDelegate = target == null
? Delegate.CreateDelegate(funcType, method)
: Delegate.CreateDelegate(funcType, target, method);
EventBus.RegisterEventInternal(parameterType, handlerDelegate, subscribeAttr.Priority,
subscribeAttr.NumericPriority, subscribeAttr.ReceiveCanceled,
$"{scope} {typeName}.{method.Name} (UniTask)", method);
}
else if (method.ReturnType == typeof(Task))
{
var funcType = typeof(Func<,>).MakeGenericType(parameterType, typeof(Task));
var handlerDelegate = target == null
? Delegate.CreateDelegate(funcType, method)
: Delegate.CreateDelegate(funcType, target, method);
var wrappedHandler = WrapTaskHandlerWithMethodInfo(parameterType, handlerDelegate, method);
EventBus.RegisterEventInternal(parameterType, wrappedHandler, subscribeAttr.Priority,
subscribeAttr.NumericPriority, subscribeAttr.ReceiveCanceled,
$"{scope} {typeName}.{method.Name} (Task->UniTask)", method);
}
else if (method.ReturnType == typeof(void))
{
var actionType = typeof(Action<>).MakeGenericType(parameterType);
var actionDelegate = target == null
? Delegate.CreateDelegate(actionType, method)
: Delegate.CreateDelegate(actionType, target, method);
EventBus.RegisterEventInternal(parameterType, actionDelegate, subscribeAttr.Priority,
subscribeAttr.NumericPriority, subscribeAttr.ReceiveCanceled,
$"{scope} {typeName}.{method.Name} (Sync)", method);
}
}
catch (Exception ex)
{
#if UNITY_EDITOR
Debug.LogWarning($"[EventBus] Registration failed for {method.Name}: {ex.Message}");
#endif
}
}
private static Delegate WrapTaskHandlerWithMethodInfo(Type eventType, Delegate taskHandler,
MethodInfo originalMethod)
{
var method = typeof(EventBusRegHelper)
.GetMethod(nameof(WrapTaskActionWithMethodInfo), BindingFlags.NonPublic | BindingFlags.Static)
?.MakeGenericMethod(eventType);
return (Delegate)method.Invoke(null, new object[] { taskHandler, originalMethod })!;
}
private static Func<T, UniTask> WrapTaskActionWithMethodInfo<T>(Delegate actionDelegate,
MethodInfo originalMethod)
{
var typedAction = (Func<T, Task>)actionDelegate;
var wrapper = new MethodInfoPreservingTaskWrapper<T>(typedAction, originalMethod);
return wrapper.ExecuteAsync;
}
private class MethodInfoPreservingTaskWrapper<T>
{
private readonly Func<T, Task> _originalAction;
public readonly MethodInfo OriginalMethod;
public MethodInfoPreservingTaskWrapper(Func<T, Task> originalAction, MethodInfo originalMethod)
{
_originalAction = originalAction;
OriginalMethod = originalMethod;
}
public async UniTask ExecuteAsync(T arg)
{
try
{
await _originalAction(arg);
}
catch (Exception ex)
{
Debug.LogException(ex);
Debug.LogError($"[EventBus] Task {OriginalMethod.Name}: {ex.Message}");
}
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a237a407a3a42394eba27bc8e4dbdce2
+7
View File
@@ -0,0 +1,7 @@
namespace ShrinkEventBus
{
internal static class EventCache<TEvent> where TEvent : EventBase
{
public static ListenerList List;
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0c41a516998341adbddce082cec09938
timeCreated: 1772889485
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Reflection;
#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。
namespace ShrinkEventBus
{
public class EventHandlerInfo
{
public Delegate Handler { get; }
public EventPriority Priority { get; }
public int NumericPriority { get; }
public bool ReceiveCanceled { get; }
public object? Target { get; }
public MethodInfo Method { get; }
public string DebugInfo { get; }
public Type DeclaringType { get; }
public string MethodName { get; }
public MethodInfo? OriginalMethod { get; }
public string OriginalMethodName { get; }
public Type? OriginalDeclaringType { get; }
public EventHandlerInfo(Delegate handler, EventPriority priority, int numericPriority,
bool receiveCanceled, string debugInfo = "", MethodInfo? originalMethod = null)
{
Handler = handler;
Priority = priority;
NumericPriority = numericPriority;
ReceiveCanceled = receiveCanceled;
Target = handler.Target;
Method = handler.Method;
DebugInfo = debugInfo;
DeclaringType = Method.DeclaringType ?? typeof(object);
MethodName = Method.Name;
OriginalMethod = ExtractOriginalMethodFromWrapper(handler) ?? originalMethod;
if (OriginalMethod != null)
{
OriginalMethodName = OriginalMethod.Name;
OriginalDeclaringType = OriginalMethod.DeclaringType;
}
else
{
OriginalMethodName = MethodName;
OriginalDeclaringType = DeclaringType;
}
}
private static MethodInfo? ExtractOriginalMethodFromWrapper(Delegate handler)
{
if (handler.Target == null) return null;
var targetType = handler.Target.GetType();
if (!targetType.Name.Contains("MethodInfoPreserving")) return null;
try
{
var field = targetType.GetField("OriginalMethod",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null) return field.GetValue(handler.Target) as MethodInfo;
var prop = targetType.GetProperty("OriginalMethod",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (prop != null) return prop.GetValue(handler.Target) as MethodInfo;
}
catch
{
// ignored
}
return null;
}
public string DisplayMethodName => OriginalMethodName;
public Type DisplayDeclaringType => OriginalDeclaringType ?? DeclaringType;
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 19972a32cbf54c7f94d6214fde5635c2
timeCreated: 1760098840
+41
View File
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
namespace ShrinkEventBus
{
public static class EventPool<T> where T : EventBase, new()
{
private static readonly Stack<T> Pool = new(32);
private static readonly object Lock = new();
public static T Get()
{
lock (Lock)
{
if (Pool.Count > 0)
{
var evt = Pool.Pop();
evt.IsInPool = false;
return evt;
}
}
var newEvt = new T();
newEvt.ReleaseAction = e => Release((T)e);
return newEvt;
}
public static void Release(T evt)
{
if (evt == null || evt.IsInPool) return;
evt.ResetInternal();
evt.IsInPool = true;
lock (Lock)
{
Pool.Push(evt);
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c59d817792dd40f08ffed2f1a8d65cb8
timeCreated: 1772891540
+27
View File
@@ -0,0 +1,27 @@
namespace ShrinkEventBus
{
public enum EventPriority
{
HIGHEST = 0,
HIGH = 1,
NORMAL = 2,
LOW = 3,
LOWEST = 4,
MONITOR = 5
}
internal static class PriorityHelper
{
public static EventPriority ConvertToEventPriority(int numericPriority)
{
return numericPriority switch
{
>= 100 => EventPriority.HIGHEST,
>= 50 => EventPriority.HIGH,
> 0 => EventPriority.NORMAL,
>= -50 => EventPriority.LOW,
_ => EventPriority.LOWEST
};
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c77c5178c66948f29a7c87c9ae203004
timeCreated: 1760098774
+9
View File
@@ -0,0 +1,9 @@
namespace ShrinkEventBus
{
public enum EventResult
{
DEFAULT,
ALLOW,
DENY
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d5213a68ce8f433a98f272904a5d7bd2
timeCreated: 1760098791
+20
View File
@@ -0,0 +1,20 @@
using System;
namespace ShrinkEventBus
{
public class UnsupportedOperationException : InvalidOperationException
{
public UnsupportedOperationException() : base()
{
}
public UnsupportedOperationException(string message) : base(message)
{
}
public UnsupportedOperationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 893fdc450e7a4a8e8e1b675f0ce084a7
timeCreated: 1760098825
+96
View File
@@ -0,0 +1,96 @@
#nullable enable
using System;
using System.Reflection;
namespace ShrinkEventBus
{
public class ListenerList
{
private readonly object _lock = new();
private EventHandlerInfo[] _handlers = Array.Empty<EventHandlerInfo>();
public void Add(Delegate handler, EventPriority priority, int numericPriority,
bool receiveCanceled, string debugInfo = "", MethodInfo? originalMethod = null)
{
lock (_lock)
{
var newInfo = new EventHandlerInfo(handler, priority, numericPriority,
receiveCanceled, debugInfo, originalMethod);
var newArray = new EventHandlerInfo[_handlers.Length + 1];
Array.Copy(_handlers, newArray, _handlers.Length);
newArray[^1] = newInfo;
Array.Sort(newArray, CompareHandlers);
_handlers = newArray;
}
}
public bool Remove(Delegate handler)
{
lock (_lock)
{
var index = -1;
for (var i = 0; i < _handlers.Length; i++)
{
if (_handlers[i].Handler.Equals(handler))
{
index = i;
break;
}
}
if (index == -1) return false;
var newArray = new EventHandlerInfo[_handlers.Length - 1];
if (index > 0) Array.Copy(_handlers, 0, newArray, 0, index);
if (index < _handlers.Length - 1)
Array.Copy(_handlers, index + 1, newArray, index, _handlers.Length - index - 1);
_handlers = newArray;
return true;
}
}
public void RemoveTarget(object target)
{
lock (_lock)
{
var removeCount = 0;
for (var i = 0; i < _handlers.Length; i++)
{
if (_handlers[i].Target == target) removeCount++;
}
if (removeCount == 0) return;
var newArray = new EventHandlerInfo[_handlers.Length - removeCount];
var dst = 0;
for (var i = 0; i < _handlers.Length; i++)
{
if (_handlers[i].Target != target)
{
newArray[dst++] = _handlers[i];
}
}
_handlers = newArray;
}
}
public EventHandlerInfo[] GetHandlers() => _handlers;
public int Count => _handlers.Length;
public void Clear()
{
lock (_lock)
{
_handlers = Array.Empty<EventHandlerInfo>();
}
}
private static int CompareHandlers(EventHandlerInfo a, EventHandlerInfo b)
{
var priorityCompare = a.Priority.CompareTo(b.Priority);
return priorityCompare != 0 ? priorityCompare : b.NumericPriority.CompareTo(a.NumericPriority);
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 864eb3e7bf8f443c86472995e408c580
timeCreated: 1760098877
+12
View File
@@ -0,0 +1,12 @@
{
"name": "ShrinkEventBus.Runtime",
"rootNamespace": "ShrinkEventBus",
"references": [
"UniTask"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": true
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f9a7e109b2ecd4946ba9202d2288af15
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: