feat: 事件复制

This commit is contained in:
cneicy committed 2026-04-07 18:13:14 +08:00
1 parent 8c6c464ae1
commit 2cea8a7ff5
11 files changed
+546 -116

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("ShrinkNetwork.Integration.EventBus")]
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43e85f78c24f1d94ba7d06a4fa37e937
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+24 -2
View File
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Reflection;
using System.Runtime.Serialization;
namespace ShrinkEventBus
{
@@ -10,16 +11,26 @@ namespace ShrinkEventBus
private bool _isCanceled;
private EventResult _result = EventResult.DEFAULT;
[IgnoreDataMember]
public EventHandlerInfo? CurrentHandler { get; internal set; }
[IgnoreDataMember]
public DateTime EventTime { get; private set; } = DateTime.UtcNow;
[IgnoreDataMember]
public Guid EventId { get; private set; } = Guid.NewGuid();
[IgnoreDataMember]
public bool IsCancelable { get; }
[IgnoreDataMember]
public bool HasResult { get; }
[IgnoreDataMember]
public EventPriority? Phase { get; private set; }
internal bool IsInPool { get; set; }
internal Action<EventBase> ReleaseAction { get; set; }
internal Action<EventBase>? ReleaseAction { get; set; }
protected EventBase()
{
@@ -44,6 +55,16 @@ namespace ShrinkEventBus
OnReset();
}
internal void PrepareForDispatch()
{
CurrentHandler = null;
Phase = null;
EventTime = DateTime.UtcNow;
EventId = Guid.NewGuid();
_listenerList.Clear();
}
[IgnoreDataMember]
public bool IsCanceled
{
get => _isCanceled;
@@ -54,6 +75,7 @@ namespace ShrinkEventBus
}
}
[IgnoreDataMember]
public EventResult Result
{
get => _result;
@@ -82,4 +104,4 @@ namespace ShrinkEventBus
ReleaseAction?.Invoke(this);
}
}
}
}
+25 -19
View File
@@ -9,6 +9,8 @@ namespace ShrinkEventBus
{
public static class EventBus
{
public static event Action<EventBase, Type> OnEventTriggered;
#if UNITY_EDITOR
public static bool EnableDebugRecord { get; set; }
public static event Action<EventBase, string, string> OnEventTriggeredForEditor;
@@ -189,16 +191,11 @@ namespace ShrinkEventBus
public static async UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
EnsureAutoManagerInitialized();
eventArgs.PrepareForDispatch();
var collection = EventCache<TEvent>.List;
if (collection == null) return false;
var handlers = collection.GetHandlers();
var handlers = collection?.GetHandlers() ?? Array.Empty<EventHandlerInfo>();
var wasHandled = false;
#if UNITY_EDITOR
RecordEditorTrace(handlers, eventArgs, typeof(TEvent));
#endif
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
@@ -225,6 +222,7 @@ namespace ShrinkEventBus
}
eventArgs.CurrentHandler = null;
CompleteEventDispatch(eventArgs, typeof(TEvent), handlers);
return wasHandled;
}
@@ -232,16 +230,11 @@ namespace ShrinkEventBus
public static bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
EnsureAutoManagerInitialized();
eventArgs.PrepareForDispatch();
var collection = EventCache<TEvent>.List;
if (collection == null) return false;
var handlers = collection.GetHandlers();
var handlers = collection?.GetHandlers() ?? Array.Empty<EventHandlerInfo>();
var wasHandled = false;
#if UNITY_EDITOR
RecordEditorTrace(handlers, eventArgs, typeof(TEvent));
#endif
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
@@ -256,7 +249,8 @@ namespace ShrinkEventBus
}
else if (handlerInfo.Handler is Func<TEvent, UniTask> asyncHandler)
{
FireAndForgetSafe(() => asyncHandler(eventArgs),
var detachedEvent = EventCloneUtility.CloneForDetachedDispatch(eventArgs);
FireAndForgetSafe(() => asyncHandler(detachedEvent),
$"{handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName}");
wasHandled = true;
}
@@ -269,6 +263,7 @@ namespace ShrinkEventBus
}
eventArgs.CurrentHandler = null;
CompleteEventDispatch(eventArgs, typeof(TEvent), handlers);
return wasHandled;
}
@@ -281,15 +276,26 @@ namespace ShrinkEventBus
});
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CompleteEventDispatch(EventBase eventArgs, Type eventType, EventHandlerInfo[] handlers)
{
OnEventTriggered?.Invoke(eventArgs, eventType);
#if UNITY_EDITOR
private static void RecordEditorTrace<TEvent>(EventHandlerInfo[] handlers, TEvent eventArgs, Type eventType)
where TEvent : EventBase
if (!EnableDebugRecord) return;
PrepareEditorTrace(handlers, eventArgs);
OnEventTriggeredForEditor?.Invoke(eventArgs, eventType.Name, GetSenderInfo());
#endif
}
#if UNITY_EDITOR
private static void PrepareEditorTrace(EventHandlerInfo[] handlers, EventBase eventArgs)
{
if (!EnableDebugRecord) return;
var listenerList = eventArgs.GetListenerList();
listenerList.Clear();
for (var i = 0; i < handlers.Length; i++)
listenerList.Add(handlers[i]);
OnEventTriggeredForEditor?.Invoke(eventArgs, eventType.Name, GetSenderInfo());
}
private static string GetSenderInfo()
@@ -389,4 +395,4 @@ namespace ShrinkEventBus
}
}
}
}
}
+85
View File
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace ShrinkEventBus
{
internal static class EventCloneUtility
{
private static readonly object CacheLock = new();
private static readonly Dictionary<Type, FieldInfo[]> FieldCache = new();
public static TEvent CloneForDetachedDispatch<TEvent>(TEvent source) where TEvent : EventBase
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (Activator.CreateInstance(source.GetType()) is not TEvent clone)
throw new InvalidOperationException(
$"Cannot clone event type {source.GetType().FullName}. A public parameterless constructor is required.");
CopyFields(source, clone);
CopyListenerSnapshot(source, clone);
clone.ReleaseAction = null;
clone.IsInPool = false;
return clone;
}
private static void CopyFields(EventBase source, EventBase target)
{
var fields = GetCopyableFields(source.GetType());
for (var i = 0; i < fields.Length; i++)
fields[i].SetValue(target, fields[i].GetValue(source));
}
private static void CopyListenerSnapshot(EventBase source, EventBase target)
{
var targetListeners = target.GetListenerList();
targetListeners.Clear();
var sourceHandlers = source.GetListenerList().GetHandlers();
for (var i = 0; i < sourceHandlers.Length; i++)
targetListeners.Add(sourceHandlers[i]);
}
private static FieldInfo[] GetCopyableFields(Type type)
{
lock (CacheLock)
{
if (FieldCache.TryGetValue(type, out var cached))
return cached;
var fields = new List<FieldInfo>();
var currentType = type;
while (currentType != null && currentType != typeof(object))
{
var declaredFields = currentType.GetFields(BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
for (var i = 0; i < declaredFields.Length; i++)
{
var field = declaredFields[i];
if (field.IsStatic)
continue;
if (ShouldSkipField(field))
continue;
fields.Add(field);
}
currentType = currentType.BaseType;
}
cached = fields.ToArray();
FieldCache[type] = cached;
return cached;
}
}
private static bool ShouldSkipField(FieldInfo field)
{
return field.Name is "_listenerList"
or "<ReleaseAction>k__BackingField"
or "<IsInPool>k__BackingField";
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 876564c111495b2489870b29ab75da5e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: