优化EventBus

This commit is contained in:
陈思海 2026-04-20 14:08:04 +08:00
parent e7a4150495
commit 9cb3b1e511
8 changed files with 348 additions and 106 deletions

View File

@ -170,7 +170,7 @@ namespace AlicizaX.Editor
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
EditorGUILayout.HelpBox( EditorGUILayout.HelpBox(
"这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会在编辑器中立即抛出异常。", "这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会抛出 InvalidOperationExceptionPlayer 中也同样生效。",
MessageType.Info); MessageType.Info);
if (_snapshotEntries.Count > 0) if (_snapshotEntries.Count > 0)
@ -269,9 +269,9 @@ namespace AlicizaX.Editor
string capacityText = row.Initialized ? row.Summary.Capacity.ToString() : row.InitialCapacity.ToString(); string capacityText = row.Initialized ? row.Summary.Capacity.ToString() : row.InitialCapacity.ToString();
string status = row.Initialized ? "已初始化" : "未初始化"; string status = row.Initialized ? "已初始化" : "未初始化";
EditorGUILayout.LabelField( 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); EditorStyles.miniLabel);
EditorGUILayout.LabelField(status, EditorStyles.miniLabel); EditorGUILayout.LabelField($"{status} | 初始容量 {row.InitialCapacity}", EditorStyles.miniLabel);
EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical();
} }
@ -301,7 +301,7 @@ namespace AlicizaX.Editor
} }
EventDebugRegistry.TryGetDetails(_selectedEventType, out _, out EventDebugSubscriberInfo[] subscribers); EventDebugRegistry.TryGetDetails(_selectedEventType, out _, out EventDebugSubscriberInfo[] subscribers);
DrawSummary(summary); DrawSummary(summary, GetInitialCapacity(_selectedEventType));
DrawAlerts(summary, subscribers); DrawAlerts(summary, subscribers);
DrawSnapshotDiff(_selectedEventType, summary, subscribers); DrawSnapshotDiff(_selectedEventType, summary, subscribers);
DrawSubscribers(subscribers); DrawSubscribers(subscribers);
@ -310,18 +310,22 @@ namespace AlicizaX.Editor
EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical();
} }
private static void DrawSummary(EventDebugSummary summary) private static void DrawSummary(EventDebugSummary summary, int initialCapacity)
{ {
EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label("摘要", EditorStyles.boldLabel); GUILayout.Label("摘要", EditorStyles.boldLabel);
EditorGUILayout.LabelField("初始容量", initialCapacity.ToString());
EditorGUILayout.LabelField("当前订阅数", summary.SubscriberCount.ToString()); EditorGUILayout.LabelField("当前订阅数", summary.SubscriberCount.ToString());
EditorGUILayout.LabelField("当前派发模式", $"in {summary.InSubscriberCount} | 值 {summary.ValueSubscriberCount}");
EditorGUILayout.LabelField("峰值订阅数", summary.PeakSubscriberCount.ToString()); EditorGUILayout.LabelField("峰值订阅数", summary.PeakSubscriberCount.ToString());
EditorGUILayout.LabelField("当前容量", summary.Capacity.ToString()); EditorGUILayout.LabelField("当前容量", summary.Capacity.ToString());
EditorGUILayout.LabelField("容量利用率", FormatRatio(summary.SubscriberCount, summary.Capacity));
EditorGUILayout.LabelField("发布次数", summary.PublishCount.ToString()); EditorGUILayout.LabelField("发布次数", summary.PublishCount.ToString());
EditorGUILayout.LabelField("订阅次数", summary.SubscribeCount.ToString()); EditorGUILayout.LabelField("订阅次数", summary.SubscribeCount.ToString());
EditorGUILayout.LabelField("取消订阅次数", summary.UnsubscribeCount.ToString()); EditorGUILayout.LabelField("取消订阅次数", summary.UnsubscribeCount.ToString());
EditorGUILayout.LabelField("扩容次数", summary.ResizeCount.ToString()); EditorGUILayout.LabelField("扩容次数", summary.ResizeCount.ToString());
EditorGUILayout.LabelField("清空次数", summary.ClearCount.ToString()); EditorGUILayout.LabelField("清空次数", summary.ClearCount.ToString());
EditorGUILayout.LabelField("发布期非法变更", summary.MutationRejectedCount.ToString());
EditorGUILayout.LabelField("最后操作帧", summary.LastOperationFrame.ToString()); EditorGUILayout.LabelField("最后操作帧", summary.LastOperationFrame.ToString());
EditorGUILayout.LabelField("最后操作时间", FormatTicks(summary.LastOperationTicksUtc)); EditorGUILayout.LabelField("最后操作时间", FormatTicks(summary.LastOperationTicksUtc));
EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical();
@ -365,14 +369,20 @@ namespace AlicizaX.Editor
long unsubscribeDelta = currentSummary.UnsubscribeCount - snapshot.Summary.UnsubscribeCount; long unsubscribeDelta = currentSummary.UnsubscribeCount - snapshot.Summary.UnsubscribeCount;
int resizeDelta = currentSummary.ResizeCount - snapshot.Summary.ResizeCount; int resizeDelta = currentSummary.ResizeCount - snapshot.Summary.ResizeCount;
int capacityDelta = currentSummary.Capacity - snapshot.Summary.Capacity; 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.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("订阅数变化", FormatSigned(subscriberDelta)); EditorGUILayout.LabelField("订阅数变化", FormatSigned(subscriberDelta));
EditorGUILayout.LabelField("in 订阅变化", FormatSigned(inSubscriberDelta));
EditorGUILayout.LabelField("值传参订阅变化", FormatSigned(valueSubscriberDelta));
EditorGUILayout.LabelField("发布次数变化", FormatSigned(publishDelta)); EditorGUILayout.LabelField("发布次数变化", FormatSigned(publishDelta));
EditorGUILayout.LabelField("订阅次数变化", FormatSigned(subscribeDelta)); EditorGUILayout.LabelField("订阅次数变化", FormatSigned(subscribeDelta));
EditorGUILayout.LabelField("取消订阅次数变化", FormatSigned(unsubscribeDelta)); EditorGUILayout.LabelField("取消订阅次数变化", FormatSigned(unsubscribeDelta));
EditorGUILayout.LabelField("扩容次数变化", FormatSigned(resizeDelta)); EditorGUILayout.LabelField("扩容次数变化", FormatSigned(resizeDelta));
EditorGUILayout.LabelField("容量变化", FormatSigned(capacityDelta)); EditorGUILayout.LabelField("容量变化", FormatSigned(capacityDelta));
EditorGUILayout.LabelField("非法变更变化", FormatSigned(mutationRejectedDelta));
List<string> addedSubscribers = GetSubscriberDiff(currentSubscribers, snapshot.Subscribers); List<string> addedSubscribers = GetSubscriberDiff(currentSubscribers, snapshot.Subscribers);
List<string> removedSubscribers = GetSubscriberDiff(snapshot.Subscribers, currentSubscribers); List<string> removedSubscribers = GetSubscriberDiff(snapshot.Subscribers, currentSubscribers);
@ -444,10 +454,13 @@ namespace AlicizaX.Editor
} }
if (summary.SubscriberCount != snapshot.Summary.SubscriberCount || if (summary.SubscriberCount != snapshot.Summary.SubscriberCount ||
summary.ValueSubscriberCount != snapshot.Summary.ValueSubscriberCount ||
summary.InSubscriberCount != snapshot.Summary.InSubscriberCount ||
summary.PublishCount != snapshot.Summary.PublishCount || summary.PublishCount != snapshot.Summary.PublishCount ||
summary.SubscribeCount != snapshot.Summary.SubscribeCount || summary.SubscribeCount != snapshot.Summary.SubscribeCount ||
summary.UnsubscribeCount != snapshot.Summary.UnsubscribeCount || summary.UnsubscribeCount != snapshot.Summary.UnsubscribeCount ||
summary.ResizeCount != snapshot.Summary.ResizeCount || summary.ResizeCount != snapshot.Summary.ResizeCount ||
summary.MutationRejectedCount != snapshot.Summary.MutationRejectedCount ||
summary.Capacity != snapshot.Summary.Capacity) summary.Capacity != snapshot.Summary.Capacity)
{ {
changedCount++; changedCount++;
@ -465,12 +478,18 @@ namespace AlicizaX.Editor
EditorGUILayout.LabelField("版本", subscriber.Version.ToString()); EditorGUILayout.LabelField("版本", subscriber.Version.ToString());
EditorGUILayout.LabelField("目标", subscriber.TargetTypeName); EditorGUILayout.LabelField("目标", subscriber.TargetTypeName);
EditorGUILayout.LabelField("类型", subscriber.IsStatic ? "静态方法" : "实例方法"); EditorGUILayout.LabelField("类型", subscriber.IsStatic ? "静态方法" : "实例方法");
EditorGUILayout.LabelField("派发方式", subscriber.UsesInParameter ? "in" : "按值复制");
if (subscriber.IsUnityObjectDestroyed) if (subscriber.IsUnityObjectDestroyed)
{ {
EditorGUILayout.HelpBox("Unity 目标对象已经被销毁,但委托仍然存在。", MessageType.Warning); EditorGUILayout.HelpBox("Unity 目标对象已经被销毁,但委托仍然存在。", MessageType.Warning);
} }
if (subscriber.IsCompilerGeneratedTarget || subscriber.IsCompilerGeneratedMethod)
{
EditorGUILayout.HelpBox("该订阅者看起来来自 lambda 或闭包,可能带来额外分配或生命周期问题。", MessageType.Info);
}
if (subscriber.UnityTarget != null && !subscriber.IsUnityObjectDestroyed) if (subscriber.UnityTarget != null && !subscriber.IsUnityObjectDestroyed)
{ {
if (GUILayout.Button("定位目标", GUILayout.Width(90f))) if (GUILayout.Button("定位目标", GUILayout.Width(90f)))
@ -531,7 +550,7 @@ namespace AlicizaX.Editor
bool initialized = summaries.TryGetValue(eventType, out EventDebugSummary summary); bool initialized = summaries.TryGetValue(eventType, out EventDebugSummary summary);
EventDebugSummary rowSummary = initialized EventDebugSummary rowSummary = initialized
? summary ? 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)); EventRow row = new EventRow(eventType, initialized, rowSummary, GetInitialCapacity(eventType));
if (MatchesFilter(row)) if (MatchesFilter(row))
@ -658,6 +677,17 @@ namespace AlicizaX.Editor
return new DateTime(ticksUtc, DateTimeKind.Utc).ToLocalTime().ToString("HH:mm:ss"); 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) private static string FormatSigned(int value)
{ {
if (value > 0) if (value > 0)
@ -682,12 +712,18 @@ namespace AlicizaX.Editor
{ {
List<EventAlert> alerts = new(); List<EventAlert> alerts = new();
int destroyedTargetCount = 0; int destroyedTargetCount = 0;
int compilerGeneratedCount = 0;
for (int i = 0; i < subscribers.Length; i++) for (int i = 0; i < subscribers.Length; i++)
{ {
if (subscribers[i].IsUnityObjectDestroyed) if (subscribers[i].IsUnityObjectDestroyed)
{ {
destroyedTargetCount++; destroyedTargetCount++;
} }
if (subscribers[i].IsCompilerGeneratedMethod || subscribers[i].IsCompilerGeneratedTarget)
{
compilerGeneratedCount++;
}
} }
if (destroyedTargetCount > 0) if (destroyedTargetCount > 0)
@ -695,6 +731,11 @@ namespace AlicizaX.Editor
alerts.Add(new EventAlert(MessageType.Warning, $"发现 {destroyedTargetCount} 个订阅者的 Unity 目标对象已经被销毁。")); alerts.Add(new EventAlert(MessageType.Warning, $"发现 {destroyedTargetCount} 个订阅者的 Unity 目标对象已经被销毁。"));
} }
if (summary.MutationRejectedCount > 0)
{
alerts.Add(new EventAlert(MessageType.Warning, $"派发期间发生了 {summary.MutationRejectedCount} 次非法变更尝试,运行时会直接抛异常。"));
}
if (summary.ResizeCount > 0) if (summary.ResizeCount > 0)
{ {
alerts.Add(new EventAlert(MessageType.Warning, $"容器已经扩容 {summary.ResizeCount} 次,当前事件的 Prewarm 可能偏小。")); 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 可能偏大。")); 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; long churn = summary.SubscribeCount + summary.UnsubscribeCount;
if (churn > 0 && summary.PublishCount == 0) if (churn > 0 && summary.PublishCount == 0)
{ {
@ -769,6 +820,7 @@ namespace AlicizaX.Editor
{ {
builder.Append(" [static]"); builder.Append(" [static]");
} }
builder.Append(subscriber.UsesInParameter ? " [in]" : " [value]");
return builder.ToString(); return builder.ToString();
} }
@ -782,6 +834,7 @@ namespace AlicizaX.Editor
EventDebugOperationKind.Publish => "发布", EventDebugOperationKind.Publish => "发布",
EventDebugOperationKind.Resize => "扩容", EventDebugOperationKind.Resize => "扩容",
EventDebugOperationKind.Clear => "清空", EventDebugOperationKind.Clear => "清空",
EventDebugOperationKind.MutationRejected => "非法变更",
_ => kind.ToString() _ => kind.ToString()
}; };
} }

View File

@ -15,6 +15,15 @@ namespace AlicizaX
return EventContainer<T>.Subscribe(handler); 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.NullChecks, false)]
[Il2CppSetOption(Option.DivideByZeroChecks, false)] [Il2CppSetOption(Option.DivideByZeroChecks, false)]
[Il2CppSetOption(Option.ArrayBoundsChecks, false)] [Il2CppSetOption(Option.ArrayBoundsChecks, false)]

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Unity.IL2CPP.CompilerServices; using Unity.IL2CPP.CompilerServices;
using UnityEngine; using UnityEngine;
@ -14,26 +13,31 @@ namespace AlicizaX
private static readonly int InitialSize = EventInitialSize<TPayload>.Size; private static readonly int InitialSize = EventInitialSize<TPayload>.Size;
private static readonly int TypeId; 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[] _versions;
private static int[] _activeSlots; private static int[] _activeSlots;
private static int[] _freeSlots; private static int[] _freeSlots;
private static int _freeCount;
private static int[] _activeIndices; private static int[] _activeIndices;
private static int _freeCount;
private static int _activeCount; private static int _activeCount;
private static int _valueSubscriberCount;
private static int _inSubscriberCount;
private static int _version; private static int _version;
private static int _publishDepth;
#if UNITY_EDITOR #if UNITY_EDITOR
private static int _publishDepth; private static System.Collections.Generic.HashSet<Action<TPayload>> _activeValueHandlers;
private static System.Collections.Generic.HashSet<Action<TPayload>> _activeHandlers; private static System.Collections.Generic.HashSet<InEventHandler<TPayload>> _activeInHandlers;
#endif #endif
static EventContainer() static EventContainer()
{ {
TypeId = UnsubscribeRegistry.Register(Unsubscribe); TypeId = UnsubscribeRegistry.Register(Unsubscribe);
_callbacks = new Action<TPayload>[InitialSize]; _valueCallbacks = new Action<TPayload>[InitialSize];
_inCallbacks = new InEventHandler<TPayload>[InitialSize];
_versions = new int[InitialSize]; _versions = new int[InitialSize];
_activeSlots = new int[InitialSize]; _activeSlots = new int[InitialSize];
_freeSlots = new int[InitialSize]; _freeSlots = new int[InitialSize];
@ -46,47 +50,100 @@ namespace AlicizaX
} }
#if UNITY_EDITOR #if UNITY_EDITOR
_activeHandlers = new System.Collections.Generic.HashSet<Action<TPayload>>(); _activeValueHandlers = new System.Collections.Generic.HashSet<Action<TPayload>>();
EventDebugRegistry.RegisterContainer<TPayload>(GetDebugSubscriberCount, GetDebugCapacity, GetDebugSubscribers); _activeInHandlers = new System.Collections.Generic.HashSet<InEventHandler<TPayload>>();
EventDebugRegistry.RegisterContainer<TPayload>(
GetDebugSubscriberCount,
GetDebugCapacity,
GetDebugValueSubscriberCount,
GetDebugInSubscriberCount,
GetDebugSubscribers);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EventRuntimeHandle Subscribe(Action<TPayload> callback) public static EventRuntimeHandle Subscribe(Action<TPayload> callback)
{ {
#if UNITY_EDITOR if (callback == null) throw new ArgumentNullException(nameof(callback));
ThrowIfMutatingDuringPublish("subscribe"); ThrowIfMutatingDuringPublish("subscribe");
if (_activeHandlers.Contains(callback))
#if UNITY_EDITOR
if (_activeValueHandlers.Contains(callback))
{ {
Log.Warning($"重复订阅事件处理程序: {callback.Method.Name}"); Log.Warning($"重复订阅事件处理程序: {callback.Method.Name}");
return default; return default;
} }
_activeHandlers.Add(callback); _activeValueHandlers.Add(callback);
#endif #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++; int activeIndex = _activeCount++;
_activeIndices[activeIndex] = handlerIndex; _activeIndices[activeIndex] = handlerIndex;
int version = ++_version; int version = ++_version;
_callbacks[handlerIndex] = callback; _valueCallbacks[handlerIndex] = valueCallback;
_inCallbacks[handlerIndex] = inCallback;
_versions[handlerIndex] = version; _versions[handlerIndex] = version;
_activeSlots[handlerIndex] = activeIndex; _activeSlots[handlerIndex] = activeIndex;
if (inCallback != null)
{
_inSubscriberCount++;
}
else
{
_valueSubscriberCount++;
}
#if UNITY_EDITOR #if UNITY_EDITOR
EventDebugRegistry.RecordSubscribe<TPayload>(_activeCount, _callbacks.Length); EventDebugRegistry.RecordSubscribe<TPayload>(_activeCount, _valueCallbacks.Length);
#endif #endif
return new EventRuntimeHandle(TypeId, handlerIndex, version); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetFreeSlot() private static int GetFreeSlot()
{ {
@ -95,10 +152,11 @@ namespace AlicizaX
return _freeSlots[--_freeCount]; return _freeSlots[--_freeCount];
} }
int oldLen = _callbacks.Length; int oldLen = _valueCallbacks.Length;
int newSize = oldLen == 0 ? 64 : oldLen << 1; 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 _versions, newSize);
Array.Resize(ref _activeSlots, newSize); Array.Resize(ref _activeSlots, newSize);
Array.Resize(ref _freeSlots, newSize); Array.Resize(ref _freeSlots, newSize);
@ -109,7 +167,7 @@ namespace AlicizaX
} }
#if UNITY_EDITOR #if UNITY_EDITOR
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length); EventDebugRegistry.RecordResize<TPayload>(_activeCount, _valueCallbacks.Length);
#endif #endif
return _freeSlots[--_freeCount]; return _freeSlots[--_freeCount];
@ -118,95 +176,106 @@ namespace AlicizaX
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Unsubscribe(int handlerIndex, int version) private static void Unsubscribe(int handlerIndex, int version)
{ {
#if UNITY_EDITOR
ThrowIfMutatingDuringPublish("unsubscribe"); ThrowIfMutatingDuringPublish("unsubscribe");
#endif
if ((uint)handlerIndex >= (uint)_versions.Length) return;
if (_versions[handlerIndex] != version) return; if (_versions[handlerIndex] != version) return;
int lastActiveIndex = --_activeCount;
int lastHandlerIndex = _activeIndices[lastActiveIndex];
int currentActiveIndex = _activeSlots[handlerIndex]; int currentActiveIndex = _activeSlots[handlerIndex];
int lastActiveIndex = --_activeCount;
if (currentActiveIndex != lastActiveIndex)
_activeIndices[currentActiveIndex] = lastHandlerIndex;
_activeSlots[lastHandlerIndex] = currentActiveIndex;
#if UNITY_EDITOR
_activeHandlers.Remove(_callbacks[handlerIndex]);
#endif
_callbacks[handlerIndex] = null;
_versions[handlerIndex] = 0;
if (_freeCount >= _freeSlots.Length)
{ {
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; _freeSlots[_freeCount++] = handlerIndex;
#if UNITY_EDITOR #if UNITY_EDITOR
EventDebugRegistry.RecordUnsubscribe<TPayload>(_activeCount, _callbacks.Length); EventDebugRegistry.RecordUnsubscribe<TPayload>(_activeCount, _valueCallbacks.Length);
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Publish(in TPayload payload) public static void Publish(in TPayload payload)
{ {
#if UNITY_EDITOR
_publishDepth++; _publishDepth++;
try try
{ {
#endif int count = _activeCount;
int count = _activeCount;
#if UNITY_EDITOR #if UNITY_EDITOR
EventDebugRegistry.RecordPublish<TPayload>(count, _callbacks.Length); EventDebugRegistry.RecordPublish<TPayload>(count, _valueCallbacks.Length);
#endif #endif
if (count == 0) return; if (count == 0) return;
int[] indices = _activeIndices; int[] indices = _activeIndices;
Action<TPayload>[] callbacks = _callbacks; Action<TPayload>[] valueCallbacks = _valueCallbacks;
InEventHandler<TPayload>[] inCallbacks = _inCallbacks;
int i = 0; int i = 0;
int unrolled = count & ~3; // count - (count % 4) int unrolled = count & ~3;
for (; i < unrolled; i += 4) for (; i < unrolled; i += 4)
{ {
int idx0 = indices[i]; InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
int idx1 = indices[i + 1]; InvokeHandler(valueCallbacks, inCallbacks, indices[i + 1], in payload);
int idx2 = indices[i + 2]; InvokeHandler(valueCallbacks, inCallbacks, indices[i + 2], in payload);
int idx3 = indices[i + 3]; InvokeHandler(valueCallbacks, inCallbacks, indices[i + 3], in payload);
}
callbacks[idx0](payload); switch (count - i)
callbacks[idx1](payload); {
callbacks[idx2](payload); case 3:
callbacks[idx3](payload); InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
} InvokeHandler(valueCallbacks, inCallbacks, indices[i + 1], in payload);
InvokeHandler(valueCallbacks, inCallbacks, indices[i + 2], in payload);
switch (count - i) break;
{ case 2:
case 3: InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
callbacks[indices[i]](payload); InvokeHandler(valueCallbacks, inCallbacks, indices[i + 1], in payload);
callbacks[indices[i + 1]](payload); break;
callbacks[indices[i + 2]](payload); case 1:
break; InvokeHandler(valueCallbacks, inCallbacks, indices[i], in payload);
case 2: break;
callbacks[indices[i]](payload); }
callbacks[indices[i + 1]](payload);
break;
case 1:
callbacks[indices[i]](payload);
break;
}
#if UNITY_EDITOR
} }
finally finally
{ {
_publishDepth--; _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 public static int SubscriberCount
@ -217,14 +286,15 @@ namespace AlicizaX
public static void EnsureCapacity(int capacity) public static void EnsureCapacity(int capacity)
{ {
#if UNITY_EDITOR if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity));
ThrowIfMutatingDuringPublish("ensure capacity"); ThrowIfMutatingDuringPublish("ensure capacity");
#endif
if (_callbacks.Length >= capacity) return; if (_valueCallbacks.Length >= capacity) return;
int oldLen = _callbacks.Length; int oldLen = _valueCallbacks.Length;
Array.Resize(ref _callbacks, capacity); Array.Resize(ref _valueCallbacks, capacity);
Array.Resize(ref _inCallbacks, capacity);
Array.Resize(ref _versions, capacity); Array.Resize(ref _versions, capacity);
Array.Resize(ref _activeSlots, capacity); Array.Resize(ref _activeSlots, capacity);
Array.Resize(ref _freeSlots, capacity); Array.Resize(ref _freeSlots, capacity);
@ -236,48 +306,83 @@ namespace AlicizaX
} }
#if UNITY_EDITOR #if UNITY_EDITOR
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length); EventDebugRegistry.RecordResize<TPayload>(_activeCount, _valueCallbacks.Length);
#endif #endif
} }
public static void Clear() public static void Clear()
{ {
#if UNITY_EDITOR
ThrowIfMutatingDuringPublish("clear"); ThrowIfMutatingDuringPublish("clear");
#endif
for (int i = 0; i < _activeCount; i++) for (int i = 0; i < _activeCount; i++)
{ {
int idx = _activeIndices[i]; int idx = _activeIndices[i];
_callbacks[idx] = null;
#if UNITY_EDITOR
RemoveActiveHandler(idx);
#endif
_valueCallbacks[idx] = null;
_inCallbacks[idx] = null;
_versions[idx] = 0; _versions[idx] = 0;
_activeSlots[idx] = 0;
_freeSlots[_freeCount++] = idx; _freeSlots[_freeCount++] = idx;
_activeIndices[i] = 0;
} }
_activeCount = 0; _activeCount = 0;
_valueSubscriberCount = 0;
_inSubscriberCount = 0;
#if UNITY_EDITOR #if UNITY_EDITOR
EventDebugRegistry.RecordClear<TPayload>(_activeCount, _callbacks.Length); EventDebugRegistry.RecordClear<TPayload>(_activeCount, _valueCallbacks.Length);
_activeHandlers.Clear(); _activeValueHandlers.Clear();
_activeInHandlers.Clear();
#endif #endif
} }
#if UNITY_EDITOR
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ThrowIfMutatingDuringPublish(string operation) private static void ThrowIfMutatingDuringPublish(string operation)
{ {
if (_publishDepth <= 0) return; if (_publishDepth <= 0) return;
#if UNITY_EDITOR
EventDebugRegistry.RecordMutationRejected<TPayload>(_activeCount, _valueCallbacks.Length);
#endif
throw new InvalidOperationException( throw new InvalidOperationException(
$"EventContainer<{typeof(TPayload).Name}> cannot {operation} while publishing. " + $"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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetDebugSubscriberCount() => _activeCount; private static int GetDebugSubscriberCount() => _activeCount;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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() private static EventDebugSubscriberInfo[] GetDebugSubscribers()
{ {
@ -291,10 +396,15 @@ namespace AlicizaX
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
int handlerIndex = _activeIndices[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; object target = callback.Target;
bool isStatic = target == null; bool isStatic = target == null;
bool isUnityObjectDestroyed = false; 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; UnityEngine.Object unityTarget = null;
if (!isStatic && target is UnityEngine.Object engineObject) if (!isStatic && target is UnityEngine.Object engineObject)
@ -311,7 +421,10 @@ namespace AlicizaX
target?.GetType().FullName ?? "<Static>", target?.GetType().FullName ?? "<Static>",
unityTarget, unityTarget,
isStatic, isStatic,
isUnityObjectDestroyed); isUnityObjectDestroyed,
usesInParameter,
isCompilerGeneratedTarget,
isCompilerGeneratedMethod);
} }
return subscribers; return subscribers;

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 0b7c9a97647245b0a2e0a52e81a466db guid: 0b7c9a97647245b0a2e0a52e81a466db
timeCreated: 1763091204 MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -11,7 +11,8 @@ namespace AlicizaX
Unsubscribe, Unsubscribe,
Publish, Publish,
Resize, Resize,
Clear Clear,
MutationRejected
} }
internal readonly struct EventDebugSummary internal readonly struct EventDebugSummary
@ -21,11 +22,14 @@ namespace AlicizaX
internal readonly int SubscriberCount; internal readonly int SubscriberCount;
internal readonly int PeakSubscriberCount; internal readonly int PeakSubscriberCount;
internal readonly int Capacity; internal readonly int Capacity;
internal readonly int ValueSubscriberCount;
internal readonly int InSubscriberCount;
internal readonly long PublishCount; internal readonly long PublishCount;
internal readonly long SubscribeCount; internal readonly long SubscribeCount;
internal readonly long UnsubscribeCount; internal readonly long UnsubscribeCount;
internal readonly int ResizeCount; internal readonly int ResizeCount;
internal readonly int ClearCount; internal readonly int ClearCount;
internal readonly long MutationRejectedCount;
internal readonly int LastOperationFrame; internal readonly int LastOperationFrame;
internal readonly long LastOperationTicksUtc; internal readonly long LastOperationTicksUtc;
@ -35,11 +39,14 @@ namespace AlicizaX
int subscriberCount, int subscriberCount,
int peakSubscriberCount, int peakSubscriberCount,
int capacity, int capacity,
int valueSubscriberCount,
int inSubscriberCount,
long publishCount, long publishCount,
long subscribeCount, long subscribeCount,
long unsubscribeCount, long unsubscribeCount,
int resizeCount, int resizeCount,
int clearCount, int clearCount,
long mutationRejectedCount,
int lastOperationFrame, int lastOperationFrame,
long lastOperationTicksUtc) long lastOperationTicksUtc)
{ {
@ -48,11 +55,14 @@ namespace AlicizaX
SubscriberCount = subscriberCount; SubscriberCount = subscriberCount;
PeakSubscriberCount = peakSubscriberCount; PeakSubscriberCount = peakSubscriberCount;
Capacity = capacity; Capacity = capacity;
ValueSubscriberCount = valueSubscriberCount;
InSubscriberCount = inSubscriberCount;
PublishCount = publishCount; PublishCount = publishCount;
SubscribeCount = subscribeCount; SubscribeCount = subscribeCount;
UnsubscribeCount = unsubscribeCount; UnsubscribeCount = unsubscribeCount;
ResizeCount = resizeCount; ResizeCount = resizeCount;
ClearCount = clearCount; ClearCount = clearCount;
MutationRejectedCount = mutationRejectedCount;
LastOperationFrame = lastOperationFrame; LastOperationFrame = lastOperationFrame;
LastOperationTicksUtc = lastOperationTicksUtc; LastOperationTicksUtc = lastOperationTicksUtc;
} }
@ -68,6 +78,9 @@ namespace AlicizaX
internal readonly UnityEngine.Object UnityTarget; internal readonly UnityEngine.Object UnityTarget;
internal readonly bool IsStatic; internal readonly bool IsStatic;
internal readonly bool IsUnityObjectDestroyed; internal readonly bool IsUnityObjectDestroyed;
internal readonly bool UsesInParameter;
internal readonly bool IsCompilerGeneratedTarget;
internal readonly bool IsCompilerGeneratedMethod;
internal EventDebugSubscriberInfo( internal EventDebugSubscriberInfo(
int handlerIndex, int handlerIndex,
@ -77,7 +90,10 @@ namespace AlicizaX
string targetTypeName, string targetTypeName,
UnityEngine.Object unityTarget, UnityEngine.Object unityTarget,
bool isStatic, bool isStatic,
bool isUnityObjectDestroyed) bool isUnityObjectDestroyed,
bool usesInParameter,
bool isCompilerGeneratedTarget,
bool isCompilerGeneratedMethod)
{ {
HandlerIndex = handlerIndex; HandlerIndex = handlerIndex;
Version = version; Version = version;
@ -87,6 +103,9 @@ namespace AlicizaX
UnityTarget = unityTarget; UnityTarget = unityTarget;
IsStatic = isStatic; IsStatic = isStatic;
IsUnityObjectDestroyed = isUnityObjectDestroyed; IsUnityObjectDestroyed = isUnityObjectDestroyed;
UsesInParameter = usesInParameter;
IsCompilerGeneratedTarget = isCompilerGeneratedTarget;
IsCompilerGeneratedMethod = isCompilerGeneratedMethod;
} }
} }
@ -125,6 +144,8 @@ namespace AlicizaX
internal readonly Type EventType; internal readonly Type EventType;
internal Func<int> SubscriberCountProvider; internal Func<int> SubscriberCountProvider;
internal Func<int> CapacityProvider; internal Func<int> CapacityProvider;
internal Func<int> ValueSubscriberCountProvider;
internal Func<int> InSubscriberCountProvider;
internal Func<EventDebugSubscriberInfo[]> SubscribersProvider; internal Func<EventDebugSubscriberInfo[]> SubscribersProvider;
internal int PeakSubscriberCount; internal int PeakSubscriberCount;
@ -133,6 +154,7 @@ namespace AlicizaX
internal long UnsubscribeCount; internal long UnsubscribeCount;
internal int ResizeCount; internal int ResizeCount;
internal int ClearCount; internal int ClearCount;
internal long MutationRejectedCount;
internal int LastOperationFrame; internal int LastOperationFrame;
internal long LastOperationTicksUtc; internal long LastOperationTicksUtc;
@ -144,13 +166,15 @@ namespace AlicizaX
private static readonly Dictionary<Type, State> _states = new(); private static readonly Dictionary<Type, State> _states = new();
private static readonly List<Type> _registrationOrder = 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 _historyWriteIndex;
private static int _historyCount; private static int _historyCount;
internal static void RegisterContainer<T>( internal static void RegisterContainer<T>(
Func<int> subscriberCountProvider, Func<int> subscriberCountProvider,
Func<int> capacityProvider, Func<int> capacityProvider,
Func<int> valueSubscriberCountProvider,
Func<int> inSubscriberCountProvider,
Func<EventDebugSubscriberInfo[]> subscribersProvider) Func<EventDebugSubscriberInfo[]> subscribersProvider)
where T : struct, IEventArgs where T : struct, IEventArgs
{ {
@ -164,6 +188,8 @@ namespace AlicizaX
state.SubscriberCountProvider = subscriberCountProvider; state.SubscriberCountProvider = subscriberCountProvider;
state.CapacityProvider = capacityProvider; state.CapacityProvider = capacityProvider;
state.ValueSubscriberCountProvider = valueSubscriberCountProvider;
state.InSubscriberCountProvider = inSubscriberCountProvider;
state.SubscribersProvider = subscribersProvider; state.SubscribersProvider = subscribersProvider;
state.PeakSubscriberCount = Math.Max(state.PeakSubscriberCount, subscriberCountProvider()); state.PeakSubscriberCount = Math.Max(state.PeakSubscriberCount, subscriberCountProvider());
} }
@ -204,6 +230,13 @@ namespace AlicizaX
MarkOperation(state, EventDebugOperationKind.Clear, subscriberCount, capacity); 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() internal static EventDebugSummary[] GetSummaries()
{ {
int count = _registrationOrder.Count; int count = _registrationOrder.Count;
@ -253,6 +286,7 @@ namespace AlicizaX
state.UnsubscribeCount = 0; state.UnsubscribeCount = 0;
state.ResizeCount = 0; state.ResizeCount = 0;
state.ClearCount = 0; state.ClearCount = 0;
state.MutationRejectedCount = 0;
state.LastOperationFrame = 0; state.LastOperationFrame = 0;
state.LastOperationTicksUtc = 0; state.LastOperationTicksUtc = 0;
} }
@ -276,6 +310,8 @@ namespace AlicizaX
{ {
int subscriberCount = state.SubscriberCountProvider?.Invoke() ?? 0; int subscriberCount = state.SubscriberCountProvider?.Invoke() ?? 0;
int capacity = state.CapacityProvider?.Invoke() ?? 0; int capacity = state.CapacityProvider?.Invoke() ?? 0;
int valueSubscriberCount = state.ValueSubscriberCountProvider?.Invoke() ?? 0;
int inSubscriberCount = state.InSubscriberCountProvider?.Invoke() ?? 0;
return new EventDebugSummary( return new EventDebugSummary(
state.EventType, state.EventType,
@ -283,11 +319,14 @@ namespace AlicizaX
subscriberCount, subscriberCount,
state.PeakSubscriberCount, state.PeakSubscriberCount,
capacity, capacity,
valueSubscriberCount,
inSubscriberCount,
state.PublishCount, state.PublishCount,
state.SubscribeCount, state.SubscribeCount,
state.UnsubscribeCount, state.UnsubscribeCount,
state.ResizeCount, state.ResizeCount,
state.ClearCount, state.ClearCount,
state.MutationRejectedCount,
state.LastOperationFrame, state.LastOperationFrame,
state.LastOperationTicksUtc); state.LastOperationTicksUtc);
} }

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: e6f1e983f1df4d6cb307f1c0ab0af6b3 guid: e6f1e983f1df4d6cb307f1c0ab0af6b3
timeCreated: 1774080000 MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -4,6 +4,8 @@ using Unity.IL2CPP.CompilerServices;
namespace AlicizaX namespace AlicizaX
{ {
public delegate void InEventHandler<T>(in T evt) where T : struct, IEventArgs;
public interface IEventArgs { } public interface IEventArgs { }
public static class EventInitialSize<T> where T : struct, IEventArgs public static class EventInitialSize<T> where T : struct, IEventArgs

View File

@ -23,6 +23,16 @@ namespace AlicizaX
_eventHandles.Add(handle); _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() public void Clear()
{ {