diff --git a/Editor/Event/EventMonitorWindow.cs b/Editor/Event/EventMonitorWindow.cs index 697eb88..8f0b630 100644 --- a/Editor/Event/EventMonitorWindow.cs +++ b/Editor/Event/EventMonitorWindow.cs @@ -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 addedSubscribers = GetSubscriberDiff(currentSubscribers, snapshot.Subscribers); List 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 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() }; } diff --git a/Runtime/ABase/Event/EventBus.cs b/Runtime/ABase/Event/EventBus.cs index 89de236..596bdfc 100644 --- a/Runtime/ABase/Event/EventBus.cs +++ b/Runtime/ABase/Event/EventBus.cs @@ -15,6 +15,15 @@ namespace AlicizaX return EventContainer.Subscribe(handler); } + [Il2CppSetOption(Option.NullChecks, false)] + [Il2CppSetOption(Option.DivideByZeroChecks, false)] + [Il2CppSetOption(Option.ArrayBoundsChecks, false)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EventRuntimeHandle Subscribe(InEventHandler handler) where T : struct, IEventArgs + { + return EventContainer.Subscribe(handler); + } + [Il2CppSetOption(Option.NullChecks, false)] [Il2CppSetOption(Option.DivideByZeroChecks, false)] [Il2CppSetOption(Option.ArrayBoundsChecks, false)] diff --git a/Runtime/ABase/Event/EventContainer.cs b/Runtime/ABase/Event/EventContainer.cs index b5deba7..59f415c 100644 --- a/Runtime/ABase/Event/EventContainer.cs +++ b/Runtime/ABase/Event/EventContainer.cs @@ -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.Size; private static readonly int TypeId; - private static Action[] _callbacks; + private static Action[] _valueCallbacks; + private static InEventHandler[] _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> _activeHandlers; + private static System.Collections.Generic.HashSet> _activeValueHandlers; + private static System.Collections.Generic.HashSet> _activeInHandlers; #endif static EventContainer() { TypeId = UnsubscribeRegistry.Register(Unsubscribe); - _callbacks = new Action[InitialSize]; + _valueCallbacks = new Action[InitialSize]; + _inCallbacks = new InEventHandler[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>(); - EventDebugRegistry.RegisterContainer(GetDebugSubscriberCount, GetDebugCapacity, GetDebugSubscribers); + _activeValueHandlers = new System.Collections.Generic.HashSet>(); + _activeInHandlers = new System.Collections.Generic.HashSet>(); + EventDebugRegistry.RegisterContainer( + GetDebugSubscriberCount, + GetDebugCapacity, + GetDebugValueSubscriberCount, + GetDebugInSubscriberCount, + GetDebugSubscribers); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EventRuntimeHandle Subscribe(Action 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 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 valueCallback, InEventHandler 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(_activeCount, _callbacks.Length); + EventDebugRegistry.RecordSubscribe(_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(_activeCount, _callbacks.Length); + EventDebugRegistry.RecordResize(_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(_activeCount, _callbacks.Length); + EventDebugRegistry.RecordUnsubscribe(_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(count, _callbacks.Length); + EventDebugRegistry.RecordPublish(count, _valueCallbacks.Length); #endif - if (count == 0) return; + if (count == 0) return; - int[] indices = _activeIndices; - Action[] callbacks = _callbacks; + int[] indices = _activeIndices; + Action[] valueCallbacks = _valueCallbacks; + InEventHandler[] 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[] valueCallbacks, InEventHandler[] inCallbacks, int handlerIndex, in TPayload payload) + { + InEventHandler 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(_activeCount, _callbacks.Length); + EventDebugRegistry.RecordResize(_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(_activeCount, _callbacks.Length); - _activeHandlers.Clear(); + EventDebugRegistry.RecordClear(_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(_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 valueCallback = _valueCallbacks[handlerIndex]; + if (valueCallback != null) + { + _activeValueHandlers.Remove(valueCallback); + return; + } + + InEventHandler 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 callback = _callbacks[handlerIndex]; + Action valueCallback = _valueCallbacks[handlerIndex]; + InEventHandler 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 ?? "", unityTarget, isStatic, - isUnityObjectDestroyed); + isUnityObjectDestroyed, + usesInParameter, + isCompilerGeneratedTarget, + isCompilerGeneratedMethod); } return subscribers; diff --git a/Runtime/ABase/Event/EventContainer.cs.meta b/Runtime/ABase/Event/EventContainer.cs.meta index 3639743..1e0a7d3 100644 --- a/Runtime/ABase/Event/EventContainer.cs.meta +++ b/Runtime/ABase/Event/EventContainer.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 0b7c9a97647245b0a2e0a52e81a466db -timeCreated: 1763091204 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Event/EventDebugRegistry.cs b/Runtime/ABase/Event/EventDebugRegistry.cs index a32ba82..7121d80 100644 --- a/Runtime/ABase/Event/EventDebugRegistry.cs +++ b/Runtime/ABase/Event/EventDebugRegistry.cs @@ -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 SubscriberCountProvider; internal Func CapacityProvider; + internal Func ValueSubscriberCountProvider; + internal Func InSubscriberCountProvider; internal Func 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 _states = new(); private static readonly List _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( Func subscriberCountProvider, Func capacityProvider, + Func valueSubscriberCountProvider, + Func inSubscriberCountProvider, Func 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(int subscriberCount, int capacity) where T : struct, IEventArgs + { + State state = GetState(); + 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); } diff --git a/Runtime/ABase/Event/EventDebugRegistry.cs.meta b/Runtime/ABase/Event/EventDebugRegistry.cs.meta index 2568b45..97bde4e 100644 --- a/Runtime/ABase/Event/EventDebugRegistry.cs.meta +++ b/Runtime/ABase/Event/EventDebugRegistry.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: e6f1e983f1df4d6cb307f1c0ab0af6b3 -timeCreated: 1774080000 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ABase/Event/IEventArgs.cs b/Runtime/ABase/Event/IEventArgs.cs index 6a4d8b1..4171efe 100644 --- a/Runtime/ABase/Event/IEventArgs.cs +++ b/Runtime/ABase/Event/IEventArgs.cs @@ -4,6 +4,8 @@ using Unity.IL2CPP.CompilerServices; namespace AlicizaX { + public delegate void InEventHandler(in T evt) where T : struct, IEventArgs; + public interface IEventArgs { } public static class EventInitialSize where T : struct, IEventArgs @@ -12,4 +14,4 @@ namespace AlicizaX } -} \ No newline at end of file +} diff --git a/Runtime/UI/EventListenerProxy.cs b/Runtime/UI/EventListenerProxy.cs index 7131d58..487a748 100644 --- a/Runtime/UI/EventListenerProxy.cs +++ b/Runtime/UI/EventListenerProxy.cs @@ -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(InEventHandler handler) where T : struct, IEventArgs + { + EventRuntimeHandle handle = EventContainer.Subscribe(handler); + _eventHandles.Add(handle); + } + public void Clear() {