优化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.HelpBox(
"这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会在编辑器中立即抛出异常。",
"这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会抛出 InvalidOperationExceptionPlayer 中也同样生效。",
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()
};
}

View File

@ -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)]

View File

@ -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;

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2
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,
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);
}

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2
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
{
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
}
}
}

View File

@ -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()
{