优化EventBus
This commit is contained in:
parent
e7a4150495
commit
9cb3b1e511
@ -170,7 +170,7 @@ namespace AlicizaX.Editor
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会在编辑器中立即抛出异常。",
|
||||
"这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会抛出 InvalidOperationException,Player 中也同样生效。",
|
||||
MessageType.Info);
|
||||
|
||||
if (_snapshotEntries.Count > 0)
|
||||
@ -269,9 +269,9 @@ namespace AlicizaX.Editor
|
||||
string capacityText = row.Initialized ? row.Summary.Capacity.ToString() : row.InitialCapacity.ToString();
|
||||
string status = row.Initialized ? "已初始化" : "未初始化";
|
||||
EditorGUILayout.LabelField(
|
||||
$"订阅 {row.Summary.SubscriberCount} | 峰值 {row.Summary.PeakSubscriberCount} | 容量 {capacityText} | 发布 {row.Summary.PublishCount}",
|
||||
$"订阅 {row.Summary.SubscriberCount} | in {row.Summary.InSubscriberCount} / 值 {row.Summary.ValueSubscriberCount} | 容量 {capacityText} | 发布 {row.Summary.PublishCount}",
|
||||
EditorStyles.miniLabel);
|
||||
EditorGUILayout.LabelField(status, EditorStyles.miniLabel);
|
||||
EditorGUILayout.LabelField($"{status} | 初始容量 {row.InitialCapacity}", EditorStyles.miniLabel);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
@ -301,7 +301,7 @@ namespace AlicizaX.Editor
|
||||
}
|
||||
|
||||
EventDebugRegistry.TryGetDetails(_selectedEventType, out _, out EventDebugSubscriberInfo[] subscribers);
|
||||
DrawSummary(summary);
|
||||
DrawSummary(summary, GetInitialCapacity(_selectedEventType));
|
||||
DrawAlerts(summary, subscribers);
|
||||
DrawSnapshotDiff(_selectedEventType, summary, subscribers);
|
||||
DrawSubscribers(subscribers);
|
||||
@ -310,18 +310,22 @@ namespace AlicizaX.Editor
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private static void DrawSummary(EventDebugSummary summary)
|
||||
private static void DrawSummary(EventDebugSummary summary, int initialCapacity)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
GUILayout.Label("摘要", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("初始容量", initialCapacity.ToString());
|
||||
EditorGUILayout.LabelField("当前订阅数", summary.SubscriberCount.ToString());
|
||||
EditorGUILayout.LabelField("当前派发模式", $"in {summary.InSubscriberCount} | 值 {summary.ValueSubscriberCount}");
|
||||
EditorGUILayout.LabelField("峰值订阅数", summary.PeakSubscriberCount.ToString());
|
||||
EditorGUILayout.LabelField("当前容量", summary.Capacity.ToString());
|
||||
EditorGUILayout.LabelField("容量利用率", FormatRatio(summary.SubscriberCount, summary.Capacity));
|
||||
EditorGUILayout.LabelField("发布次数", summary.PublishCount.ToString());
|
||||
EditorGUILayout.LabelField("订阅次数", summary.SubscribeCount.ToString());
|
||||
EditorGUILayout.LabelField("取消订阅次数", summary.UnsubscribeCount.ToString());
|
||||
EditorGUILayout.LabelField("扩容次数", summary.ResizeCount.ToString());
|
||||
EditorGUILayout.LabelField("清空次数", summary.ClearCount.ToString());
|
||||
EditorGUILayout.LabelField("发布期非法变更", summary.MutationRejectedCount.ToString());
|
||||
EditorGUILayout.LabelField("最后操作帧", summary.LastOperationFrame.ToString());
|
||||
EditorGUILayout.LabelField("最后操作时间", FormatTicks(summary.LastOperationTicksUtc));
|
||||
EditorGUILayout.EndVertical();
|
||||
@ -365,14 +369,20 @@ namespace AlicizaX.Editor
|
||||
long unsubscribeDelta = currentSummary.UnsubscribeCount - snapshot.Summary.UnsubscribeCount;
|
||||
int resizeDelta = currentSummary.ResizeCount - snapshot.Summary.ResizeCount;
|
||||
int capacityDelta = currentSummary.Capacity - snapshot.Summary.Capacity;
|
||||
int valueSubscriberDelta = currentSummary.ValueSubscriberCount - snapshot.Summary.ValueSubscriberCount;
|
||||
int inSubscriberDelta = currentSummary.InSubscriberCount - snapshot.Summary.InSubscriberCount;
|
||||
long mutationRejectedDelta = currentSummary.MutationRejectedCount - snapshot.Summary.MutationRejectedCount;
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.LabelField("订阅数变化", FormatSigned(subscriberDelta));
|
||||
EditorGUILayout.LabelField("in 订阅变化", FormatSigned(inSubscriberDelta));
|
||||
EditorGUILayout.LabelField("值传参订阅变化", FormatSigned(valueSubscriberDelta));
|
||||
EditorGUILayout.LabelField("发布次数变化", FormatSigned(publishDelta));
|
||||
EditorGUILayout.LabelField("订阅次数变化", FormatSigned(subscribeDelta));
|
||||
EditorGUILayout.LabelField("取消订阅次数变化", FormatSigned(unsubscribeDelta));
|
||||
EditorGUILayout.LabelField("扩容次数变化", FormatSigned(resizeDelta));
|
||||
EditorGUILayout.LabelField("容量变化", FormatSigned(capacityDelta));
|
||||
EditorGUILayout.LabelField("非法变更变化", FormatSigned(mutationRejectedDelta));
|
||||
|
||||
List<string> addedSubscribers = GetSubscriberDiff(currentSubscribers, snapshot.Subscribers);
|
||||
List<string> removedSubscribers = GetSubscriberDiff(snapshot.Subscribers, currentSubscribers);
|
||||
@ -444,10 +454,13 @@ namespace AlicizaX.Editor
|
||||
}
|
||||
|
||||
if (summary.SubscriberCount != snapshot.Summary.SubscriberCount ||
|
||||
summary.ValueSubscriberCount != snapshot.Summary.ValueSubscriberCount ||
|
||||
summary.InSubscriberCount != snapshot.Summary.InSubscriberCount ||
|
||||
summary.PublishCount != snapshot.Summary.PublishCount ||
|
||||
summary.SubscribeCount != snapshot.Summary.SubscribeCount ||
|
||||
summary.UnsubscribeCount != snapshot.Summary.UnsubscribeCount ||
|
||||
summary.ResizeCount != snapshot.Summary.ResizeCount ||
|
||||
summary.MutationRejectedCount != snapshot.Summary.MutationRejectedCount ||
|
||||
summary.Capacity != snapshot.Summary.Capacity)
|
||||
{
|
||||
changedCount++;
|
||||
@ -465,12 +478,18 @@ namespace AlicizaX.Editor
|
||||
EditorGUILayout.LabelField("版本", subscriber.Version.ToString());
|
||||
EditorGUILayout.LabelField("目标", subscriber.TargetTypeName);
|
||||
EditorGUILayout.LabelField("类型", subscriber.IsStatic ? "静态方法" : "实例方法");
|
||||
EditorGUILayout.LabelField("派发方式", subscriber.UsesInParameter ? "in" : "按值复制");
|
||||
|
||||
if (subscriber.IsUnityObjectDestroyed)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Unity 目标对象已经被销毁,但委托仍然存在。", MessageType.Warning);
|
||||
}
|
||||
|
||||
if (subscriber.IsCompilerGeneratedTarget || subscriber.IsCompilerGeneratedMethod)
|
||||
{
|
||||
EditorGUILayout.HelpBox("该订阅者看起来来自 lambda 或闭包,可能带来额外分配或生命周期问题。", MessageType.Info);
|
||||
}
|
||||
|
||||
if (subscriber.UnityTarget != null && !subscriber.IsUnityObjectDestroyed)
|
||||
{
|
||||
if (GUILayout.Button("定位目标", GUILayout.Width(90f)))
|
||||
@ -531,7 +550,7 @@ namespace AlicizaX.Editor
|
||||
bool initialized = summaries.TryGetValue(eventType, out EventDebugSummary summary);
|
||||
EventDebugSummary rowSummary = initialized
|
||||
? summary
|
||||
: new EventDebugSummary(eventType, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
: new EventDebugSummary(eventType, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
EventRow row = new EventRow(eventType, initialized, rowSummary, GetInitialCapacity(eventType));
|
||||
if (MatchesFilter(row))
|
||||
@ -658,6 +677,17 @@ namespace AlicizaX.Editor
|
||||
return new DateTime(ticksUtc, DateTimeKind.Utc).ToLocalTime().ToString("HH:mm:ss");
|
||||
}
|
||||
|
||||
private static string FormatRatio(int value, int capacity)
|
||||
{
|
||||
if (capacity <= 0)
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
|
||||
float percent = value / (float)capacity * 100f;
|
||||
return $"{percent:F1}% ({value}/{capacity})";
|
||||
}
|
||||
|
||||
private static string FormatSigned(int value)
|
||||
{
|
||||
if (value > 0)
|
||||
@ -682,12 +712,18 @@ namespace AlicizaX.Editor
|
||||
{
|
||||
List<EventAlert> alerts = new();
|
||||
int destroyedTargetCount = 0;
|
||||
int compilerGeneratedCount = 0;
|
||||
for (int i = 0; i < subscribers.Length; i++)
|
||||
{
|
||||
if (subscribers[i].IsUnityObjectDestroyed)
|
||||
{
|
||||
destroyedTargetCount++;
|
||||
}
|
||||
|
||||
if (subscribers[i].IsCompilerGeneratedMethod || subscribers[i].IsCompilerGeneratedTarget)
|
||||
{
|
||||
compilerGeneratedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (destroyedTargetCount > 0)
|
||||
@ -695,6 +731,11 @@ namespace AlicizaX.Editor
|
||||
alerts.Add(new EventAlert(MessageType.Warning, $"发现 {destroyedTargetCount} 个订阅者的 Unity 目标对象已经被销毁。"));
|
||||
}
|
||||
|
||||
if (summary.MutationRejectedCount > 0)
|
||||
{
|
||||
alerts.Add(new EventAlert(MessageType.Warning, $"派发期间发生了 {summary.MutationRejectedCount} 次非法变更尝试,运行时会直接抛异常。"));
|
||||
}
|
||||
|
||||
if (summary.ResizeCount > 0)
|
||||
{
|
||||
alerts.Add(new EventAlert(MessageType.Warning, $"容器已经扩容 {summary.ResizeCount} 次,当前事件的 Prewarm 可能偏小。"));
|
||||
@ -705,6 +746,16 @@ namespace AlicizaX.Editor
|
||||
alerts.Add(new EventAlert(MessageType.Info, $"当前容量 {summary.Capacity} 远大于峰值订阅数 {summary.PeakSubscriberCount},Prewarm 可能偏大。"));
|
||||
}
|
||||
|
||||
if (summary.ValueSubscriberCount > 0)
|
||||
{
|
||||
alerts.Add(new EventAlert(MessageType.Info, $"当前仍有 {summary.ValueSubscriberCount} 个订阅者使用按值传参;事件体较大时建议改成 in 处理器。"));
|
||||
}
|
||||
|
||||
if (compilerGeneratedCount > 0)
|
||||
{
|
||||
alerts.Add(new EventAlert(MessageType.Info, $"检测到 {compilerGeneratedCount} 个编译器生成的订阅者,通常意味着 lambda 或闭包。"));
|
||||
}
|
||||
|
||||
long churn = summary.SubscribeCount + summary.UnsubscribeCount;
|
||||
if (churn > 0 && summary.PublishCount == 0)
|
||||
{
|
||||
@ -769,6 +820,7 @@ namespace AlicizaX.Editor
|
||||
{
|
||||
builder.Append(" [static]");
|
||||
}
|
||||
builder.Append(subscriber.UsesInParameter ? " [in]" : " [value]");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
@ -782,6 +834,7 @@ namespace AlicizaX.Editor
|
||||
EventDebugOperationKind.Publish => "发布",
|
||||
EventDebugOperationKind.Resize => "扩容",
|
||||
EventDebugOperationKind.Clear => "清空",
|
||||
EventDebugOperationKind.MutationRejected => "非法变更",
|
||||
_ => kind.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
@ -15,6 +15,15 @@ namespace AlicizaX
|
||||
return EventContainer<T>.Subscribe(handler);
|
||||
}
|
||||
|
||||
[Il2CppSetOption(Option.NullChecks, false)]
|
||||
[Il2CppSetOption(Option.DivideByZeroChecks, false)]
|
||||
[Il2CppSetOption(Option.ArrayBoundsChecks, false)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static EventRuntimeHandle Subscribe<T>(InEventHandler<T> handler) where T : struct, IEventArgs
|
||||
{
|
||||
return EventContainer<T>.Subscribe(handler);
|
||||
}
|
||||
|
||||
[Il2CppSetOption(Option.NullChecks, false)]
|
||||
[Il2CppSetOption(Option.DivideByZeroChecks, false)]
|
||||
[Il2CppSetOption(Option.ArrayBoundsChecks, false)]
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Unity.IL2CPP.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
@ -14,26 +13,31 @@ namespace AlicizaX
|
||||
private static readonly int InitialSize = EventInitialSize<TPayload>.Size;
|
||||
private static readonly int TypeId;
|
||||
|
||||
private static Action<TPayload>[] _callbacks;
|
||||
private static Action<TPayload>[] _valueCallbacks;
|
||||
private static InEventHandler<TPayload>[] _inCallbacks;
|
||||
private static int[] _versions;
|
||||
private static int[] _activeSlots;
|
||||
|
||||
private static int[] _freeSlots;
|
||||
private static int _freeCount;
|
||||
private static int[] _activeIndices;
|
||||
|
||||
private static int _freeCount;
|
||||
private static int _activeCount;
|
||||
private static int _valueSubscriberCount;
|
||||
private static int _inSubscriberCount;
|
||||
private static int _version;
|
||||
private static int _publishDepth;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static int _publishDepth;
|
||||
private static System.Collections.Generic.HashSet<Action<TPayload>> _activeHandlers;
|
||||
private static System.Collections.Generic.HashSet<Action<TPayload>> _activeValueHandlers;
|
||||
private static System.Collections.Generic.HashSet<InEventHandler<TPayload>> _activeInHandlers;
|
||||
#endif
|
||||
|
||||
static EventContainer()
|
||||
{
|
||||
TypeId = UnsubscribeRegistry.Register(Unsubscribe);
|
||||
|
||||
_callbacks = new Action<TPayload>[InitialSize];
|
||||
_valueCallbacks = new Action<TPayload>[InitialSize];
|
||||
_inCallbacks = new InEventHandler<TPayload>[InitialSize];
|
||||
_versions = new int[InitialSize];
|
||||
_activeSlots = new int[InitialSize];
|
||||
_freeSlots = new int[InitialSize];
|
||||
@ -46,47 +50,100 @@ namespace AlicizaX
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
_activeHandlers = new System.Collections.Generic.HashSet<Action<TPayload>>();
|
||||
EventDebugRegistry.RegisterContainer<TPayload>(GetDebugSubscriberCount, GetDebugCapacity, GetDebugSubscribers);
|
||||
_activeValueHandlers = new System.Collections.Generic.HashSet<Action<TPayload>>();
|
||||
_activeInHandlers = new System.Collections.Generic.HashSet<InEventHandler<TPayload>>();
|
||||
EventDebugRegistry.RegisterContainer<TPayload>(
|
||||
GetDebugSubscriberCount,
|
||||
GetDebugCapacity,
|
||||
GetDebugValueSubscriberCount,
|
||||
GetDebugInSubscriberCount,
|
||||
GetDebugSubscribers);
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static EventRuntimeHandle Subscribe(Action<TPayload> callback)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (callback == null) throw new ArgumentNullException(nameof(callback));
|
||||
|
||||
ThrowIfMutatingDuringPublish("subscribe");
|
||||
if (_activeHandlers.Contains(callback))
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (_activeValueHandlers.Contains(callback))
|
||||
{
|
||||
Log.Warning($"重复订阅事件处理程序: {callback.Method.Name}");
|
||||
return default;
|
||||
}
|
||||
|
||||
_activeHandlers.Add(callback);
|
||||
_activeValueHandlers.Add(callback);
|
||||
#endif
|
||||
|
||||
int handlerIndex = GetFreeSlot();
|
||||
return SubscribeCore(callback, null);
|
||||
}
|
||||
|
||||
if (_activeCount >= _activeIndices.Length)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static EventRuntimeHandle Subscribe(InEventHandler<TPayload> callback)
|
||||
{
|
||||
if (callback == null) throw new ArgumentNullException(nameof(callback));
|
||||
|
||||
ThrowIfMutatingDuringPublish("subscribe");
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (_activeInHandlers.Contains(callback))
|
||||
{
|
||||
Array.Resize(ref _activeIndices, _activeIndices.Length << 1);
|
||||
Log.Warning($"重复订阅事件处理程序: {callback.Method.Name}");
|
||||
return default;
|
||||
}
|
||||
|
||||
_activeInHandlers.Add(callback);
|
||||
#endif
|
||||
|
||||
return SubscribeCore(null, callback);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static EventRuntimeHandle SubscribeCore(Action<TPayload> valueCallback, InEventHandler<TPayload> inCallback)
|
||||
{
|
||||
int handlerIndex = GetFreeSlot();
|
||||
EnsureActiveIndicesCapacity();
|
||||
|
||||
int activeIndex = _activeCount++;
|
||||
_activeIndices[activeIndex] = handlerIndex;
|
||||
|
||||
int version = ++_version;
|
||||
_callbacks[handlerIndex] = callback;
|
||||
_valueCallbacks[handlerIndex] = valueCallback;
|
||||
_inCallbacks[handlerIndex] = inCallback;
|
||||
_versions[handlerIndex] = version;
|
||||
_activeSlots[handlerIndex] = activeIndex;
|
||||
|
||||
if (inCallback != null)
|
||||
{
|
||||
_inSubscriberCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueSubscriberCount++;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EventDebugRegistry.RecordSubscribe<TPayload>(_activeCount, _callbacks.Length);
|
||||
EventDebugRegistry.RecordSubscribe<TPayload>(_activeCount, _valueCallbacks.Length);
|
||||
#endif
|
||||
|
||||
return new EventRuntimeHandle(TypeId, handlerIndex, version);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void EnsureActiveIndicesCapacity()
|
||||
{
|
||||
if (_activeCount < _activeIndices.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int newSize = _activeIndices.Length == 0 ? 64 : _activeIndices.Length << 1;
|
||||
Array.Resize(ref _activeIndices, newSize);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetFreeSlot()
|
||||
{
|
||||
@ -95,10 +152,11 @@ namespace AlicizaX
|
||||
return _freeSlots[--_freeCount];
|
||||
}
|
||||
|
||||
int oldLen = _callbacks.Length;
|
||||
int oldLen = _valueCallbacks.Length;
|
||||
int newSize = oldLen == 0 ? 64 : oldLen << 1;
|
||||
|
||||
Array.Resize(ref _callbacks, newSize);
|
||||
Array.Resize(ref _valueCallbacks, newSize);
|
||||
Array.Resize(ref _inCallbacks, newSize);
|
||||
Array.Resize(ref _versions, newSize);
|
||||
Array.Resize(ref _activeSlots, newSize);
|
||||
Array.Resize(ref _freeSlots, newSize);
|
||||
@ -109,7 +167,7 @@ namespace AlicizaX
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length);
|
||||
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _valueCallbacks.Length);
|
||||
#endif
|
||||
|
||||
return _freeSlots[--_freeCount];
|
||||
@ -118,95 +176,106 @@ namespace AlicizaX
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Unsubscribe(int handlerIndex, int version)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
ThrowIfMutatingDuringPublish("unsubscribe");
|
||||
#endif
|
||||
|
||||
if ((uint)handlerIndex >= (uint)_versions.Length) return;
|
||||
if (_versions[handlerIndex] != version) return;
|
||||
|
||||
int lastActiveIndex = --_activeCount;
|
||||
int lastHandlerIndex = _activeIndices[lastActiveIndex];
|
||||
int currentActiveIndex = _activeSlots[handlerIndex];
|
||||
int lastActiveIndex = --_activeCount;
|
||||
|
||||
|
||||
_activeIndices[currentActiveIndex] = lastHandlerIndex;
|
||||
_activeSlots[lastHandlerIndex] = currentActiveIndex;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
_activeHandlers.Remove(_callbacks[handlerIndex]);
|
||||
#endif
|
||||
|
||||
_callbacks[handlerIndex] = null;
|
||||
_versions[handlerIndex] = 0;
|
||||
|
||||
if (_freeCount >= _freeSlots.Length)
|
||||
if (currentActiveIndex != lastActiveIndex)
|
||||
{
|
||||
Array.Resize(ref _freeSlots, _freeSlots.Length << 1);
|
||||
int lastHandlerIndex = _activeIndices[lastActiveIndex];
|
||||
_activeIndices[currentActiveIndex] = lastHandlerIndex;
|
||||
_activeSlots[lastHandlerIndex] = currentActiveIndex;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RemoveActiveHandler(handlerIndex);
|
||||
#endif
|
||||
|
||||
if (_inCallbacks[handlerIndex] != null)
|
||||
{
|
||||
_inSubscriberCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
_valueSubscriberCount--;
|
||||
}
|
||||
|
||||
_valueCallbacks[handlerIndex] = null;
|
||||
_inCallbacks[handlerIndex] = null;
|
||||
_versions[handlerIndex] = 0;
|
||||
_activeSlots[handlerIndex] = 0;
|
||||
_activeIndices[lastActiveIndex] = 0;
|
||||
_freeSlots[_freeCount++] = handlerIndex;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EventDebugRegistry.RecordUnsubscribe<TPayload>(_activeCount, _callbacks.Length);
|
||||
EventDebugRegistry.RecordUnsubscribe<TPayload>(_activeCount, _valueCallbacks.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Publish(in TPayload payload)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
_publishDepth++;
|
||||
try
|
||||
{
|
||||
#endif
|
||||
int count = _activeCount;
|
||||
int count = _activeCount;
|
||||
#if UNITY_EDITOR
|
||||
EventDebugRegistry.RecordPublish<TPayload>(count, _callbacks.Length);
|
||||
EventDebugRegistry.RecordPublish<TPayload>(count, _valueCallbacks.Length);
|
||||
#endif
|
||||
if (count == 0) return;
|
||||
if (count == 0) return;
|
||||
|
||||
int[] indices = _activeIndices;
|
||||
Action<TPayload>[] callbacks = _callbacks;
|
||||
int[] indices = _activeIndices;
|
||||
Action<TPayload>[] valueCallbacks = _valueCallbacks;
|
||||
InEventHandler<TPayload>[] inCallbacks = _inCallbacks;
|
||||
|
||||
int i = 0;
|
||||
int unrolled = count & ~3; // count - (count % 4)
|
||||
int i = 0;
|
||||
int unrolled = count & ~3;
|
||||
|
||||
for (; i < unrolled; i += 4)
|
||||
{
|
||||
int idx0 = indices[i];
|
||||
int idx1 = indices[i + 1];
|
||||
int idx2 = indices[i + 2];
|
||||
int idx3 = indices[i + 3];
|
||||
for (; i < unrolled; i += 4)
|
||||
{
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i + 1], in payload);
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i + 2], in payload);
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i + 3], in payload);
|
||||
}
|
||||
|
||||
callbacks[idx0](payload);
|
||||
callbacks[idx1](payload);
|
||||
callbacks[idx2](payload);
|
||||
callbacks[idx3](payload);
|
||||
}
|
||||
|
||||
switch (count - i)
|
||||
{
|
||||
case 3:
|
||||
callbacks[indices[i]](payload);
|
||||
callbacks[indices[i + 1]](payload);
|
||||
callbacks[indices[i + 2]](payload);
|
||||
break;
|
||||
case 2:
|
||||
callbacks[indices[i]](payload);
|
||||
callbacks[indices[i + 1]](payload);
|
||||
break;
|
||||
case 1:
|
||||
callbacks[indices[i]](payload);
|
||||
break;
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
switch (count - i)
|
||||
{
|
||||
case 3:
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i + 1], in payload);
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i + 2], in payload);
|
||||
break;
|
||||
case 2:
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i + 1], in payload);
|
||||
break;
|
||||
case 1:
|
||||
InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_publishDepth--;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InvokeHandler(Action<TPayload>[] valueCallbacks, InEventHandler<TPayload>[] inCallbacks, int handlerIndex, in TPayload payload)
|
||||
{
|
||||
InEventHandler<TPayload> inCallback = inCallbacks[handlerIndex];
|
||||
if (inCallback != null)
|
||||
{
|
||||
inCallback(in payload);
|
||||
return;
|
||||
}
|
||||
|
||||
valueCallbacks[handlerIndex](payload);
|
||||
}
|
||||
|
||||
public static int SubscriberCount
|
||||
@ -217,14 +286,15 @@ namespace AlicizaX
|
||||
|
||||
public static void EnsureCapacity(int capacity)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
|
||||
ThrowIfMutatingDuringPublish("ensure capacity");
|
||||
#endif
|
||||
|
||||
if (_callbacks.Length >= capacity) return;
|
||||
if (_valueCallbacks.Length >= capacity) return;
|
||||
|
||||
int oldLen = _callbacks.Length;
|
||||
Array.Resize(ref _callbacks, capacity);
|
||||
int oldLen = _valueCallbacks.Length;
|
||||
Array.Resize(ref _valueCallbacks, capacity);
|
||||
Array.Resize(ref _inCallbacks, capacity);
|
||||
Array.Resize(ref _versions, capacity);
|
||||
Array.Resize(ref _activeSlots, capacity);
|
||||
Array.Resize(ref _freeSlots, capacity);
|
||||
@ -236,48 +306,83 @@ namespace AlicizaX
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length);
|
||||
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _valueCallbacks.Length);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
ThrowIfMutatingDuringPublish("clear");
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < _activeCount; i++)
|
||||
{
|
||||
int idx = _activeIndices[i];
|
||||
_callbacks[idx] = null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
RemoveActiveHandler(idx);
|
||||
#endif
|
||||
|
||||
_valueCallbacks[idx] = null;
|
||||
_inCallbacks[idx] = null;
|
||||
_versions[idx] = 0;
|
||||
_activeSlots[idx] = 0;
|
||||
_freeSlots[_freeCount++] = idx;
|
||||
_activeIndices[i] = 0;
|
||||
}
|
||||
|
||||
_activeCount = 0;
|
||||
_valueSubscriberCount = 0;
|
||||
_inSubscriberCount = 0;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EventDebugRegistry.RecordClear<TPayload>(_activeCount, _callbacks.Length);
|
||||
_activeHandlers.Clear();
|
||||
EventDebugRegistry.RecordClear<TPayload>(_activeCount, _valueCallbacks.Length);
|
||||
_activeValueHandlers.Clear();
|
||||
_activeInHandlers.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ThrowIfMutatingDuringPublish(string operation)
|
||||
{
|
||||
if (_publishDepth <= 0) return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EventDebugRegistry.RecordMutationRejected<TPayload>(_activeCount, _valueCallbacks.Length);
|
||||
#endif
|
||||
throw new InvalidOperationException(
|
||||
$"EventContainer<{typeof(TPayload).Name}> cannot {operation} while publishing. " +
|
||||
"Supporting dispatch-time mutations would require slower publish semantics.");
|
||||
"This bus guarantees a stable subscriber set during dispatch. Apply the mutation after publish completes.");
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void RemoveActiveHandler(int handlerIndex)
|
||||
{
|
||||
Action<TPayload> valueCallback = _valueCallbacks[handlerIndex];
|
||||
if (valueCallback != null)
|
||||
{
|
||||
_activeValueHandlers.Remove(valueCallback);
|
||||
return;
|
||||
}
|
||||
|
||||
InEventHandler<TPayload> inCallback = _inCallbacks[handlerIndex];
|
||||
if (inCallback != null)
|
||||
{
|
||||
_activeInHandlers.Remove(inCallback);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetDebugSubscriberCount() => _activeCount;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetDebugCapacity() => _callbacks.Length;
|
||||
private static int GetDebugCapacity() => _valueCallbacks.Length;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetDebugValueSubscriberCount() => _valueSubscriberCount;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetDebugInSubscriberCount() => _inSubscriberCount;
|
||||
|
||||
private static EventDebugSubscriberInfo[] GetDebugSubscribers()
|
||||
{
|
||||
@ -291,10 +396,15 @@ namespace AlicizaX
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int handlerIndex = _activeIndices[i];
|
||||
Action<TPayload> callback = _callbacks[handlerIndex];
|
||||
Action<TPayload> valueCallback = _valueCallbacks[handlerIndex];
|
||||
InEventHandler<TPayload> inCallback = _inCallbacks[handlerIndex];
|
||||
Delegate callback = (Delegate)inCallback ?? valueCallback;
|
||||
object target = callback.Target;
|
||||
bool isStatic = target == null;
|
||||
bool isUnityObjectDestroyed = false;
|
||||
bool usesInParameter = inCallback != null;
|
||||
bool isCompilerGeneratedTarget = !isStatic && target.GetType().IsDefined(typeof(CompilerGeneratedAttribute), false);
|
||||
bool isCompilerGeneratedMethod = callback.Method.IsDefined(typeof(CompilerGeneratedAttribute), false);
|
||||
UnityEngine.Object unityTarget = null;
|
||||
|
||||
if (!isStatic && target is UnityEngine.Object engineObject)
|
||||
@ -311,7 +421,10 @@ namespace AlicizaX
|
||||
target?.GetType().FullName ?? "<Static>",
|
||||
unityTarget,
|
||||
isStatic,
|
||||
isUnityObjectDestroyed);
|
||||
isUnityObjectDestroyed,
|
||||
usesInParameter,
|
||||
isCompilerGeneratedTarget,
|
||||
isCompilerGeneratedMethod);
|
||||
}
|
||||
|
||||
return subscribers;
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b7c9a97647245b0a2e0a52e81a466db
|
||||
timeCreated: 1763091204
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@ -11,7 +11,8 @@ namespace AlicizaX
|
||||
Unsubscribe,
|
||||
Publish,
|
||||
Resize,
|
||||
Clear
|
||||
Clear,
|
||||
MutationRejected
|
||||
}
|
||||
|
||||
internal readonly struct EventDebugSummary
|
||||
@ -21,11 +22,14 @@ namespace AlicizaX
|
||||
internal readonly int SubscriberCount;
|
||||
internal readonly int PeakSubscriberCount;
|
||||
internal readonly int Capacity;
|
||||
internal readonly int ValueSubscriberCount;
|
||||
internal readonly int InSubscriberCount;
|
||||
internal readonly long PublishCount;
|
||||
internal readonly long SubscribeCount;
|
||||
internal readonly long UnsubscribeCount;
|
||||
internal readonly int ResizeCount;
|
||||
internal readonly int ClearCount;
|
||||
internal readonly long MutationRejectedCount;
|
||||
internal readonly int LastOperationFrame;
|
||||
internal readonly long LastOperationTicksUtc;
|
||||
|
||||
@ -35,11 +39,14 @@ namespace AlicizaX
|
||||
int subscriberCount,
|
||||
int peakSubscriberCount,
|
||||
int capacity,
|
||||
int valueSubscriberCount,
|
||||
int inSubscriberCount,
|
||||
long publishCount,
|
||||
long subscribeCount,
|
||||
long unsubscribeCount,
|
||||
int resizeCount,
|
||||
int clearCount,
|
||||
long mutationRejectedCount,
|
||||
int lastOperationFrame,
|
||||
long lastOperationTicksUtc)
|
||||
{
|
||||
@ -48,11 +55,14 @@ namespace AlicizaX
|
||||
SubscriberCount = subscriberCount;
|
||||
PeakSubscriberCount = peakSubscriberCount;
|
||||
Capacity = capacity;
|
||||
ValueSubscriberCount = valueSubscriberCount;
|
||||
InSubscriberCount = inSubscriberCount;
|
||||
PublishCount = publishCount;
|
||||
SubscribeCount = subscribeCount;
|
||||
UnsubscribeCount = unsubscribeCount;
|
||||
ResizeCount = resizeCount;
|
||||
ClearCount = clearCount;
|
||||
MutationRejectedCount = mutationRejectedCount;
|
||||
LastOperationFrame = lastOperationFrame;
|
||||
LastOperationTicksUtc = lastOperationTicksUtc;
|
||||
}
|
||||
@ -68,6 +78,9 @@ namespace AlicizaX
|
||||
internal readonly UnityEngine.Object UnityTarget;
|
||||
internal readonly bool IsStatic;
|
||||
internal readonly bool IsUnityObjectDestroyed;
|
||||
internal readonly bool UsesInParameter;
|
||||
internal readonly bool IsCompilerGeneratedTarget;
|
||||
internal readonly bool IsCompilerGeneratedMethod;
|
||||
|
||||
internal EventDebugSubscriberInfo(
|
||||
int handlerIndex,
|
||||
@ -77,7 +90,10 @@ namespace AlicizaX
|
||||
string targetTypeName,
|
||||
UnityEngine.Object unityTarget,
|
||||
bool isStatic,
|
||||
bool isUnityObjectDestroyed)
|
||||
bool isUnityObjectDestroyed,
|
||||
bool usesInParameter,
|
||||
bool isCompilerGeneratedTarget,
|
||||
bool isCompilerGeneratedMethod)
|
||||
{
|
||||
HandlerIndex = handlerIndex;
|
||||
Version = version;
|
||||
@ -87,6 +103,9 @@ namespace AlicizaX
|
||||
UnityTarget = unityTarget;
|
||||
IsStatic = isStatic;
|
||||
IsUnityObjectDestroyed = isUnityObjectDestroyed;
|
||||
UsesInParameter = usesInParameter;
|
||||
IsCompilerGeneratedTarget = isCompilerGeneratedTarget;
|
||||
IsCompilerGeneratedMethod = isCompilerGeneratedMethod;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,6 +144,8 @@ namespace AlicizaX
|
||||
internal readonly Type EventType;
|
||||
internal Func<int> SubscriberCountProvider;
|
||||
internal Func<int> CapacityProvider;
|
||||
internal Func<int> ValueSubscriberCountProvider;
|
||||
internal Func<int> InSubscriberCountProvider;
|
||||
internal Func<EventDebugSubscriberInfo[]> SubscribersProvider;
|
||||
|
||||
internal int PeakSubscriberCount;
|
||||
@ -133,6 +154,7 @@ namespace AlicizaX
|
||||
internal long UnsubscribeCount;
|
||||
internal int ResizeCount;
|
||||
internal int ClearCount;
|
||||
internal long MutationRejectedCount;
|
||||
internal int LastOperationFrame;
|
||||
internal long LastOperationTicksUtc;
|
||||
|
||||
@ -144,13 +166,15 @@ namespace AlicizaX
|
||||
|
||||
private static readonly Dictionary<Type, State> _states = new();
|
||||
private static readonly List<Type> _registrationOrder = new();
|
||||
private static EventDebugOperationRecord[] _history = new EventDebugOperationRecord[HistoryCapacity];
|
||||
private static readonly EventDebugOperationRecord[] _history = new EventDebugOperationRecord[HistoryCapacity];
|
||||
private static int _historyWriteIndex;
|
||||
private static int _historyCount;
|
||||
|
||||
internal static void RegisterContainer<T>(
|
||||
Func<int> subscriberCountProvider,
|
||||
Func<int> capacityProvider,
|
||||
Func<int> valueSubscriberCountProvider,
|
||||
Func<int> inSubscriberCountProvider,
|
||||
Func<EventDebugSubscriberInfo[]> subscribersProvider)
|
||||
where T : struct, IEventArgs
|
||||
{
|
||||
@ -164,6 +188,8 @@ namespace AlicizaX
|
||||
|
||||
state.SubscriberCountProvider = subscriberCountProvider;
|
||||
state.CapacityProvider = capacityProvider;
|
||||
state.ValueSubscriberCountProvider = valueSubscriberCountProvider;
|
||||
state.InSubscriberCountProvider = inSubscriberCountProvider;
|
||||
state.SubscribersProvider = subscribersProvider;
|
||||
state.PeakSubscriberCount = Math.Max(state.PeakSubscriberCount, subscriberCountProvider());
|
||||
}
|
||||
@ -204,6 +230,13 @@ namespace AlicizaX
|
||||
MarkOperation(state, EventDebugOperationKind.Clear, subscriberCount, capacity);
|
||||
}
|
||||
|
||||
internal static void RecordMutationRejected<T>(int subscriberCount, int capacity) where T : struct, IEventArgs
|
||||
{
|
||||
State state = GetState<T>();
|
||||
state.MutationRejectedCount++;
|
||||
MarkOperation(state, EventDebugOperationKind.MutationRejected, subscriberCount, capacity);
|
||||
}
|
||||
|
||||
internal static EventDebugSummary[] GetSummaries()
|
||||
{
|
||||
int count = _registrationOrder.Count;
|
||||
@ -253,6 +286,7 @@ namespace AlicizaX
|
||||
state.UnsubscribeCount = 0;
|
||||
state.ResizeCount = 0;
|
||||
state.ClearCount = 0;
|
||||
state.MutationRejectedCount = 0;
|
||||
state.LastOperationFrame = 0;
|
||||
state.LastOperationTicksUtc = 0;
|
||||
}
|
||||
@ -276,6 +310,8 @@ namespace AlicizaX
|
||||
{
|
||||
int subscriberCount = state.SubscriberCountProvider?.Invoke() ?? 0;
|
||||
int capacity = state.CapacityProvider?.Invoke() ?? 0;
|
||||
int valueSubscriberCount = state.ValueSubscriberCountProvider?.Invoke() ?? 0;
|
||||
int inSubscriberCount = state.InSubscriberCountProvider?.Invoke() ?? 0;
|
||||
|
||||
return new EventDebugSummary(
|
||||
state.EventType,
|
||||
@ -283,11 +319,14 @@ namespace AlicizaX
|
||||
subscriberCount,
|
||||
state.PeakSubscriberCount,
|
||||
capacity,
|
||||
valueSubscriberCount,
|
||||
inSubscriberCount,
|
||||
state.PublishCount,
|
||||
state.SubscribeCount,
|
||||
state.UnsubscribeCount,
|
||||
state.ResizeCount,
|
||||
state.ClearCount,
|
||||
state.MutationRejectedCount,
|
||||
state.LastOperationFrame,
|
||||
state.LastOperationTicksUtc);
|
||||
}
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6f1e983f1df4d6cb307f1c0ab0af6b3
|
||||
timeCreated: 1774080000
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@ -4,6 +4,8 @@ using Unity.IL2CPP.CompilerServices;
|
||||
|
||||
namespace AlicizaX
|
||||
{
|
||||
public delegate void InEventHandler<T>(in T evt) where T : struct, IEventArgs;
|
||||
|
||||
public interface IEventArgs { }
|
||||
|
||||
public static class EventInitialSize<T> where T : struct, IEventArgs
|
||||
@ -12,4 +14,4 @@ namespace AlicizaX
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,16 @@ namespace AlicizaX
|
||||
_eventHandles.Add(handle);
|
||||
}
|
||||
|
||||
[Il2CppSetOption(Option.NullChecks, false)]
|
||||
[Il2CppSetOption(Option.DivideByZeroChecks, false)]
|
||||
[Il2CppSetOption(Option.ArrayBoundsChecks, false)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddUIEvent<T>(InEventHandler<T> handler) where T : struct, IEventArgs
|
||||
{
|
||||
EventRuntimeHandle handle = EventContainer<T>.Subscribe(handler);
|
||||
_eventHandles.Add(handle);
|
||||
}
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user