com.wallstop-studios.dxmessaging 2.2.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CHANGELOG.md +106 -67
  2. package/CHANGELOG.md.meta +7 -7
  3. package/Editor/Analyzers/BaseCallIlInspector.cs +277 -0
  4. package/Editor/Analyzers/BaseCallIlInspector.cs.meta +11 -0
  5. package/Editor/Analyzers/BaseCallLogMessageParser.cs +295 -0
  6. package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +11 -0
  7. package/Editor/Analyzers/BaseCallReportAggregator.cs +308 -0
  8. package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +11 -0
  9. package/Editor/Analyzers/BaseCallTypeScanner.cs +110 -0
  10. package/Editor/Analyzers/BaseCallTypeScanner.cs.meta +11 -0
  11. package/Editor/Analyzers/BaseCallTypeScannerCore.cs +562 -0
  12. package/Editor/Analyzers/BaseCallTypeScannerCore.cs.meta +11 -0
  13. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +1122 -0
  14. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +11 -0
  15. package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +44 -44
  16. package/Editor/Analyzers/Microsoft.CodeAnalysis.dll.meta +44 -44
  17. package/Editor/Analyzers/System.Collections.Immutable.dll.meta +44 -44
  18. package/Editor/Analyzers/System.Reflection.Metadata.dll.meta +44 -44
  19. package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +44 -44
  20. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  21. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +33 -0
  22. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  23. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +72 -72
  24. package/Editor/Analyzers.meta +8 -8
  25. package/Editor/AssemblyInfo.cs.meta +3 -3
  26. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs +81 -0
  27. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +11 -0
  28. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +420 -0
  29. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +11 -0
  30. package/Editor/CustomEditors/MessagingComponentEditor.cs +1 -1
  31. package/Editor/CustomEditors/MessagingComponentEditor.cs.meta +2 -2
  32. package/Editor/CustomEditors.meta +2 -2
  33. package/Editor/DxMessagingEditorInitializer.cs.meta +2 -2
  34. package/Editor/DxMessagingMenu.cs.meta +11 -11
  35. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +11 -11
  36. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +190 -0
  37. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +11 -0
  38. package/Editor/Settings/DxMessagingSettings.cs +189 -0
  39. package/Editor/Settings/DxMessagingSettings.cs.meta +2 -2
  40. package/Editor/Settings/DxMessagingSettingsProvider.cs +50 -33
  41. package/Editor/Settings/DxMessagingSettingsProvider.cs.meta +2 -2
  42. package/Editor/Settings.meta +2 -2
  43. package/Editor/SetupCscRsp.cs +209 -8
  44. package/Editor/SetupCscRsp.cs.meta +2 -2
  45. package/Editor/Testing/MessagingComponentEditorHarness.cs +1 -1
  46. package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +3 -3
  47. package/Editor/Testing.meta +3 -3
  48. package/Editor/WallstopStudios.DxMessaging.Editor.asmdef +14 -14
  49. package/Editor/WallstopStudios.DxMessaging.Editor.asmdef.meta +7 -7
  50. package/Editor.meta +8 -8
  51. package/LICENSE.md +9 -9
  52. package/LICENSE.md.meta +7 -7
  53. package/README.md +941 -900
  54. package/README.md.meta +7 -7
  55. package/Runtime/AssemblyInfo.cs +4 -0
  56. package/Runtime/AssemblyInfo.cs.meta +2 -2
  57. package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +2 -2
  58. package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs.meta +2 -2
  59. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs +26 -0
  60. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +11 -0
  61. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs.meta +2 -2
  62. package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs.meta +2 -2
  63. package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs.meta +2 -2
  64. package/Runtime/Core/Attributes.meta +2 -2
  65. package/Runtime/Core/Configuration/DxMessagingRuntimeSettings.cs +195 -0
  66. package/Runtime/Core/Configuration/DxMessagingRuntimeSettings.cs.meta +11 -0
  67. package/Runtime/Core/Configuration/DxMessagingRuntimeSettingsProvider.cs +179 -0
  68. package/Runtime/Core/Configuration/DxMessagingRuntimeSettingsProvider.cs.meta +11 -0
  69. package/Runtime/Core/Configuration.meta +9 -0
  70. package/Runtime/Core/DataStructure/CyclicBuffer.cs +2 -2
  71. package/Runtime/Core/DataStructure/CyclicBuffer.cs.meta +2 -2
  72. package/Runtime/Core/DataStructure.meta +2 -2
  73. package/Runtime/Core/Diagnostics/MessageEmissionData.cs.meta +2 -2
  74. package/Runtime/Core/Diagnostics/MessageRegistrationData.cs.meta +2 -2
  75. package/Runtime/Core/Diagnostics/MessageRegistrationType.cs.meta +2 -2
  76. package/Runtime/Core/Diagnostics.meta +2 -2
  77. package/Runtime/Core/DxMessagingStaticState.cs +19 -0
  78. package/Runtime/Core/DxMessagingStaticState.cs.meta +11 -11
  79. package/Runtime/Core/Extensions/EnumExtensions.cs.meta +2 -2
  80. package/Runtime/Core/Extensions/IListExtensions.cs.meta +2 -2
  81. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +12 -12
  82. package/Runtime/Core/Extensions/MessageExtensions.cs.meta +11 -11
  83. package/Runtime/Core/Extensions.meta +8 -8
  84. package/Runtime/Core/Helper/MessageCache.cs +32 -0
  85. package/Runtime/Core/Helper/MessageCache.cs.meta +2 -2
  86. package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +2 -2
  87. package/Runtime/Core/Helper.meta +2 -2
  88. package/Runtime/Core/IMessage.cs +3 -3
  89. package/Runtime/Core/IMessage.cs.meta +11 -11
  90. package/Runtime/Core/InstanceId.cs.meta +11 -11
  91. package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs +51 -0
  92. package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs.meta +11 -0
  93. package/Runtime/Core/Internal/TypedGlobalSlotIndex.cs +38 -0
  94. package/Runtime/Core/Internal/TypedGlobalSlotIndex.cs.meta +11 -0
  95. package/Runtime/Core/Internal/TypedSlotIndex.cs +81 -0
  96. package/Runtime/Core/Internal/TypedSlotIndex.cs.meta +11 -0
  97. package/Runtime/Core/Internal/TypedSlots.cs +613 -0
  98. package/Runtime/Core/Internal/TypedSlots.cs.meta +11 -0
  99. package/Runtime/Core/Internal.meta +9 -0
  100. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +11 -11
  101. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +11 -11
  102. package/Runtime/Core/MessageBus/IMessageBus.cs +177 -3
  103. package/Runtime/Core/MessageBus/IMessageBus.cs.meta +11 -11
  104. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +11 -11
  105. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +11 -11
  106. package/Runtime/Core/MessageBus/Internal/BusContextIndex.cs +16 -0
  107. package/Runtime/Core/MessageBus/Internal/BusContextIndex.cs.meta +11 -0
  108. package/Runtime/Core/MessageBus/Internal/BusSinkIndex.cs +40 -0
  109. package/Runtime/Core/MessageBus/Internal/BusSinkIndex.cs.meta +11 -0
  110. package/Runtime/Core/MessageBus/Internal/BusSlots.cs +718 -0
  111. package/Runtime/Core/MessageBus/Internal/BusSlots.cs.meta +11 -0
  112. package/Runtime/Core/MessageBus/Internal/DispatchKind.cs +38 -0
  113. package/Runtime/Core/MessageBus/Internal/DispatchKind.cs.meta +11 -0
  114. package/Runtime/Core/MessageBus/Internal/DispatchPhase.cs +20 -0
  115. package/Runtime/Core/MessageBus/Internal/DispatchPhase.cs.meta +11 -0
  116. package/Runtime/Core/MessageBus/Internal/DispatchVariant.cs +28 -0
  117. package/Runtime/Core/MessageBus/Internal/DispatchVariant.cs.meta +11 -0
  118. package/Runtime/Core/MessageBus/Internal/IEvictableSlot.cs +48 -0
  119. package/Runtime/Core/MessageBus/Internal/IEvictableSlot.cs.meta +11 -0
  120. package/Runtime/Core/MessageBus/Internal/ISweepable.cs +15 -0
  121. package/Runtime/Core/MessageBus/Internal/ISweepable.cs.meta +11 -0
  122. package/Runtime/Core/MessageBus/Internal/RegistrationMethodAxes.cs +222 -0
  123. package/Runtime/Core/MessageBus/Internal/RegistrationMethodAxes.cs.meta +11 -0
  124. package/Runtime/Core/MessageBus/Internal/SlotKey.cs +192 -0
  125. package/Runtime/Core/MessageBus/Internal/SlotKey.cs.meta +11 -0
  126. package/Runtime/Core/MessageBus/Internal.meta +9 -0
  127. package/Runtime/Core/MessageBus/MessageBus.cs +2651 -500
  128. package/Runtime/Core/MessageBus/MessageBus.cs.meta +11 -11
  129. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +11 -11
  130. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +11 -11
  131. package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +11 -11
  132. package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +11 -11
  133. package/Runtime/Core/MessageBus.meta +8 -8
  134. package/Runtime/Core/MessageHandler.cs +2019 -542
  135. package/Runtime/Core/MessageHandler.cs.meta +11 -11
  136. package/Runtime/Core/MessageRegistrationHandle.cs.meta +11 -11
  137. package/Runtime/Core/MessageRegistrationToken.cs +7 -0
  138. package/Runtime/Core/MessageRegistrationToken.cs.meta +11 -11
  139. package/Runtime/Core/Messages/GlobalStringMessage.cs.meta +2 -2
  140. package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +11 -11
  141. package/Runtime/Core/Messages/ITargetedMessage.cs.meta +11 -11
  142. package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +11 -11
  143. package/Runtime/Core/Messages/ReflexiveMessage.cs.meta +2 -2
  144. package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +11 -11
  145. package/Runtime/Core/Messages/StringMessage.cs.meta +2 -2
  146. package/Runtime/Core/Messages.meta +8 -8
  147. package/Runtime/Core/MessagingDebug.cs.meta +11 -11
  148. package/Runtime/Core/Pooling/CollectionPool.cs +266 -0
  149. package/Runtime/Core/Pooling/CollectionPool.cs.meta +11 -0
  150. package/Runtime/Core/Pooling/CollectionPoolDiagnostics.cs +30 -0
  151. package/Runtime/Core/Pooling/CollectionPoolDiagnostics.cs.meta +11 -0
  152. package/Runtime/Core/Pooling/DxPools.cs +157 -0
  153. package/Runtime/Core/Pooling/DxPools.cs.meta +11 -0
  154. package/Runtime/Core/Pooling/EvictionPlayerLoopHook.cs +106 -0
  155. package/Runtime/Core/Pooling/EvictionPlayerLoopHook.cs.meta +11 -0
  156. package/Runtime/Core/Pooling/IDxMessagingClock.cs +18 -0
  157. package/Runtime/Core/Pooling/IDxMessagingClock.cs.meta +11 -0
  158. package/Runtime/Core/Pooling/PoolDiagnosticsSnapshot.cs +55 -0
  159. package/Runtime/Core/Pooling/PoolDiagnosticsSnapshot.cs.meta +11 -0
  160. package/Runtime/Core/Pooling/StopwatchClock.cs +27 -0
  161. package/Runtime/Core/Pooling/StopwatchClock.cs.meta +11 -0
  162. package/Runtime/Core/Pooling/UnityRealtimeClock.cs +31 -0
  163. package/Runtime/Core/Pooling/UnityRealtimeClock.cs.meta +11 -0
  164. package/Runtime/Core/Pooling.meta +9 -0
  165. package/Runtime/Core.meta +8 -8
  166. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +12 -12
  167. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +11 -11
  168. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +12 -12
  169. package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +2 -2
  170. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +73 -0
  171. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +11 -11
  172. package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef +20 -20
  173. package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef.meta +7 -7
  174. package/Runtime/Unity/Integrations/Reflex.meta +8 -8
  175. package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +2 -2
  176. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +109 -1
  177. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +11 -11
  178. package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef +30 -30
  179. package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef.meta +7 -7
  180. package/Runtime/Unity/Integrations/VContainer.meta +8 -8
  181. package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +2 -2
  182. package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef +30 -30
  183. package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef.meta +7 -7
  184. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +79 -1
  185. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +11 -11
  186. package/Runtime/Unity/Integrations/Zenject.meta +8 -8
  187. package/Runtime/Unity/Integrations.meta +8 -8
  188. package/Runtime/Unity/MessageAwareComponent.cs +29 -0
  189. package/Runtime/Unity/MessageAwareComponent.cs.meta +11 -11
  190. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +12 -12
  191. package/Runtime/Unity/MessagingComponent.cs.meta +11 -11
  192. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +12 -12
  193. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +12 -12
  194. package/Runtime/Unity.meta +8 -8
  195. package/Runtime/WallstopStudios.DxMessaging.asmdef +14 -14
  196. package/Runtime/WallstopStudios.DxMessaging.asmdef.meta +7 -7
  197. package/Runtime.meta +8 -8
  198. package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab +98 -98
  199. package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab.meta +7 -7
  200. package/Samples~/DI/Prefabs.meta +8 -8
  201. package/Samples~/DI/Providers/GlobalMessageBusProvider.asset +14 -14
  202. package/Samples~/DI/Providers/GlobalMessageBusProvider.asset.meta +8 -8
  203. package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset +14 -14
  204. package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset.meta +8 -8
  205. package/Samples~/DI/Providers.meta +8 -8
  206. package/Samples~/DI/README.md +51 -51
  207. package/Samples~/DI/README.md.meta +7 -7
  208. package/Samples~/DI/Reflex/SampleInstaller.cs +7 -0
  209. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +11 -11
  210. package/Samples~/DI/Reflex.meta +8 -8
  211. package/Samples~/DI/VContainer/SampleLifetimeScope.cs +6 -1
  212. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +11 -11
  213. package/Samples~/DI/VContainer.meta +8 -8
  214. package/Samples~/DI/Zenject/SampleInstaller.cs +8 -0
  215. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +11 -11
  216. package/Samples~/DI/Zenject.meta +8 -8
  217. package/Samples~/DI.meta +8 -8
  218. package/Samples~/Mini Combat/Boot.cs.meta +11 -11
  219. package/Samples~/Mini Combat/Enemy.cs.meta +11 -11
  220. package/Samples~/Mini Combat/Messages.cs.meta +11 -11
  221. package/Samples~/Mini Combat/Player.cs.meta +11 -11
  222. package/Samples~/Mini Combat/README.md +324 -323
  223. package/Samples~/Mini Combat/README.md.meta +7 -7
  224. package/Samples~/Mini Combat/UIOverlay.cs.meta +11 -11
  225. package/Samples~/Mini Combat/Walkthrough.md +430 -430
  226. package/Samples~/Mini Combat/Walkthrough.md.meta +7 -7
  227. package/Samples~/Mini Combat/WallstopStudios.DxMessaging.MiniCombat.Sample.asmdef +13 -13
  228. package/Samples~/Mini Combat/WallstopStudios.DxMessaging.MiniCombat.Sample.asmdef.meta +7 -7
  229. package/Samples~/Mini Combat.meta +8 -8
  230. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +11 -11
  231. package/Samples~/UI Buttons + Inspector/Messages.cs.meta +11 -11
  232. package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +11 -11
  233. package/Samples~/UI Buttons + Inspector/README.md +210 -209
  234. package/Samples~/UI Buttons + Inspector/README.md.meta +7 -7
  235. package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +11 -11
  236. package/Samples~/UI Buttons + Inspector/WallstopStudios.DxMessaging.UIButtons.Sample.asmdef +13 -13
  237. package/Samples~/UI Buttons + Inspector/WallstopStudios.DxMessaging.UIButtons.Sample.asmdef.meta +7 -7
  238. package/Samples~/UI Buttons + Inspector.meta +8 -8
  239. package/SourceGenerators/Directory.Build.props.meta +7 -7
  240. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +11 -11
  241. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +2 -2
  242. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj.meta +7 -7
  243. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.meta +8 -8
  244. package/SourceGenerators.meta +8 -8
  245. package/Third Party Notices.md +3 -3
  246. package/Third Party Notices.md.meta +7 -7
  247. package/package.json +115 -92
  248. package/package.json.meta +7 -7
@@ -0,0 +1,718 @@
1
+ namespace DxMessaging.Core.MessageBus.Internal
2
+ {
3
+ using System.Collections.Generic;
4
+ using System.Diagnostics;
5
+ using System.Runtime.CompilerServices;
6
+ using DxMessaging.Core;
7
+ using DxMessaging.Core.Pooling;
8
+
9
+ /// <summary>
10
+ /// Per-priority leaf storage for a single dispatch slot. Mirrors the
11
+ /// per-priority bucket previously held inside the legacy nested
12
+ /// <c>HandlerCache</c> type previously declared in <see cref="MessageBus"/>:
13
+ /// a handler set keyed by <see cref="MessageHandler"/> with insertion-order
14
+ /// tracking via the integer payload (priority slot index), plus a flat
15
+ /// cache list for snapshot-friendly iteration.
16
+ /// </summary>
17
+ /// <remarks>
18
+ /// <para>
19
+ /// <see cref="BusPriorityBucket"/> is intentionally NOT an
20
+ /// <see cref="IEvictableSlot"/>: it is only ever owned by a
21
+ /// <see cref="BusSinkSlot"/> and is reclaimed transitively when its parent
22
+ /// is reset. Eviction never targets a bucket directly.
23
+ /// </para>
24
+ /// <para>
25
+ /// The <see cref="version"/> / <see cref="lastSeenVersion"/> /
26
+ /// <see cref="lastSeenEmissionId"/> triple is the same staged-dispatch
27
+ /// snapshot mechanism used by the legacy <c>HandlerCache</c> -- structural
28
+ /// mutations bump <see cref="version"/>, dispatchers compare against
29
+ /// <see cref="lastSeenVersion"/> to decide whether to re-snapshot.
30
+ /// </para>
31
+ /// </remarks>
32
+ internal sealed class BusPriorityBucket
33
+ {
34
+ /// <summary>
35
+ /// Live handlers in this priority bucket. Value is the priority slot
36
+ /// index used by the staged dispatch snapshot pattern (matches the
37
+ /// legacy <c>HandlerCache.handlers</c> layout).
38
+ /// </summary>
39
+ public readonly Dictionary<MessageHandler, int> handlers = new();
40
+
41
+ /// <summary>
42
+ /// Flat snapshot-friendly cache of <see cref="handlers"/> keys; rebuilt
43
+ /// lazily by the dispatcher when <see cref="version"/> changes.
44
+ /// </summary>
45
+ public readonly List<MessageHandler> cache = new();
46
+
47
+ /// <summary>Monotonic version counter for the bucket contents.</summary>
48
+ public long version;
49
+
50
+ /// <summary>
51
+ /// The <see cref="version"/> value observed by the most recent
52
+ /// dispatcher snapshot. Used to decide whether <see cref="cache"/> needs
53
+ /// to be re-materialized before the next dispatch.
54
+ /// </summary>
55
+ public long lastSeenVersion = -1;
56
+
57
+ /// <summary>
58
+ /// The bus emission id of the most recent dispatch that consumed this
59
+ /// bucket. Used by the staged dispatch staleness check.
60
+ /// </summary>
61
+ public long lastSeenEmissionId = -1;
62
+
63
+ /// <summary>
64
+ /// Clear all bucket state. Mirrors the legacy
65
+ /// <c>HandlerCache.Clear()</c> body -- empties handlers and cache and
66
+ /// resets the dispatch-snapshot counters. Resets <see cref="version"/>
67
+ /// to <c>0</c>; this is the legacy "full reset" semantic and is NOT
68
+ /// monotonic. Eviction-driven reset semantics live on the parent
69
+ /// <see cref="BusSinkSlot.Reset"/>, which preserves monotonicity by
70
+ /// bumping the parent slot's version after clearing buckets.
71
+ /// </summary>
72
+ public void Clear()
73
+ {
74
+ handlers.Clear();
75
+ cache.Clear();
76
+ version = 0;
77
+ lastSeenVersion = -1;
78
+ lastSeenEmissionId = -1;
79
+ }
80
+ }
81
+
82
+ /// <summary>
83
+ /// Per-message-type, per-context dispatch slot. Replaces the inner
84
+ /// <c>HandlerCache&lt;int, HandlerCache&gt;</c> type previously declared in
85
+ /// <see cref="MessageBus"/>. Holds the priority-keyed map of
86
+ /// <see cref="BusPriorityBucket"/>s and the flat ordered-priority list used
87
+ /// by the staged dispatch snapshot pattern.
88
+ /// </summary>
89
+ /// <remarks>
90
+ /// <para>
91
+ /// In the new layout each <see cref="BusSinkSlot"/> belongs to either the
92
+ /// scalar slot grid (<c>WithoutContext</c> variants -- no
93
+ /// <see cref="InstanceId"/> hash on the hot path) or the inner map of a
94
+ /// <see cref="BusContextSlot"/> (variants that carry an
95
+ /// <see cref="InstanceId"/> recipient or source).
96
+ /// </para>
97
+ /// <para>
98
+ /// The <see cref="dispatchState"/> field carries the staged Stage/Acquire
99
+ /// snapshot for this slot.
100
+ /// </para>
101
+ /// </remarks>
102
+ internal sealed class BusSinkSlot : IEvictableSlot
103
+ {
104
+ /// <summary>
105
+ /// Per-priority handler buckets, keyed by priority value.
106
+ /// </summary>
107
+ public readonly Dictionary<int, BusPriorityBucket> handlersByPriority = new();
108
+
109
+ /// <summary>
110
+ /// Insertion-ordered list of priority keys present in
111
+ /// <see cref="handlersByPriority"/>. Mirrors the legacy
112
+ /// <c>HandlerCache.order</c> field.
113
+ /// </summary>
114
+ public readonly List<int> orderedPriorities = new();
115
+
116
+ /// <summary>
117
+ /// Flat snapshot-friendly cache of <see cref="handlersByPriority"/>
118
+ /// entries; rebuilt lazily by the dispatcher when <see cref="version"/>
119
+ /// changes. Mirrors the legacy <c>HandlerCache.cache</c> field.
120
+ /// </summary>
121
+ public readonly List<KeyValuePair<int, BusPriorityBucket>> cache = new();
122
+
123
+ /// <summary>Monotonic version counter for the slot's structural state.</summary>
124
+ public long version;
125
+
126
+ /// <summary>
127
+ /// The <see cref="version"/> value observed by the most recent
128
+ /// dispatcher snapshot. Used to decide whether <see cref="cache"/>
129
+ /// needs to be re-materialized before the next dispatch.
130
+ /// </summary>
131
+ public long lastSeenVersion = -1;
132
+
133
+ /// <summary>
134
+ /// The bus emission id of the most recent dispatch that consumed this
135
+ /// slot. Used by the staged dispatch staleness check.
136
+ /// </summary>
137
+ public long lastSeenEmissionId = -1;
138
+
139
+ /// <summary>
140
+ /// Bus tick counter value at the most recent register / deregister /
141
+ /// emit that touched this slot. Maintained by the sweep touch hook;
142
+ /// preserved across <see cref="Clear"/> and <see cref="Reset"/> so the
143
+ /// sweep can distinguish never-touched slots from freshly-reset slots.
144
+ /// </summary>
145
+ public long lastTouchTicks;
146
+
147
+ /// <summary>
148
+ /// <para>
149
+ /// Reserved live-handler counter intended to mirror the unique
150
+ /// (<see cref="MessageHandler"/>, priority) pair count across every
151
+ /// entry in <see cref="handlersByPriority"/>, so <see cref="IsEmpty"/>
152
+ /// becomes a single integer compare rather than a walk over priority
153
+ /// buckets. This counter is reserved until <see cref="BusSinkSlot"/> is
154
+ /// used as the typed-sink storage type. <see cref="IsEmpty"/> currently
155
+ /// returns <c>true</c> at all times because no writer increments
156
+ /// <see cref="liveCount"/>.
157
+ /// </para>
158
+ /// <para>
159
+ /// Intended transitions once wired: re-registration of an existing
160
+ /// pair will be a no-op on this counter; only newly-inserted pairs
161
+ /// will increment it, and only the final removal of a pair will
162
+ /// decrement it.
163
+ /// </para>
164
+ /// </summary>
165
+ public int liveCount;
166
+
167
+ /// <summary>
168
+ /// Per-slot dispatch state for the staged Stage/Acquire snapshot
169
+ /// pattern. Single field per slot -- the previous per-slot-key
170
+ /// dictionary keyed by dispatch slot is unnecessary once
171
+ /// each slot maps 1:1 to a <see cref="SlotKey"/>. Lazy alloc on
172
+ /// first Stage/Acquire; null after Reset(). This field is
173
+ /// forward-compatible plumbing. Wiring lands when
174
+ /// <see cref="BusSinkSlot"/> becomes the storage type backing the
175
+ /// typed-sink hot dispatch path (replacing the current legacy
176
+ /// <c>HandlerCache&lt;int, HandlerCache&gt;</c> generic outer +
177
+ /// non-generic inner pair). The intermediate phases that retire
178
+ /// the legacy category enum and split the Handle-phase variants
179
+ /// do NOT touch this field; they continue working through the
180
+ /// legacy storage type's <c>dispatchState</c>.
181
+ /// </summary>
182
+ public MessageBus.DispatchState dispatchState;
183
+
184
+ /// <inheritdoc />
185
+ public long LastTouchTicks
186
+ {
187
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
188
+ get => lastTouchTicks;
189
+ }
190
+
191
+ /// <inheritdoc />
192
+ public bool IsEmpty
193
+ {
194
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
195
+ get => liveCount == 0;
196
+ }
197
+
198
+ /// <inheritdoc />
199
+ public long Version
200
+ {
201
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
202
+ get => version;
203
+ }
204
+
205
+ /// <summary>
206
+ /// Full-reset semantic that mirrors the legacy
207
+ /// <c>HandlerCache&lt;TKey, TValue&gt;.Clear()</c> body. Clears all
208
+ /// priority buckets and the outer maps, and resets the
209
+ /// staged-dispatch snapshot counters.
210
+ /// Resets <see cref="version"/> to <c>0</c>; this is NOT monotonic and
211
+ /// is intended only for the bus-wide
212
+ /// <c>MessageBus.ResetState()</c> code path. Use <see cref="Reset"/>
213
+ /// for sweep-driven slot reclamation.
214
+ /// </summary>
215
+ public void Clear()
216
+ {
217
+ foreach (BusPriorityBucket bucket in handlersByPriority.Values)
218
+ {
219
+ bucket.Clear();
220
+ }
221
+ handlersByPriority.Clear();
222
+ orderedPriorities.Clear();
223
+ cache.Clear();
224
+ dispatchState?.Reset();
225
+ dispatchState = null;
226
+ version = 0;
227
+ lastSeenVersion = -1;
228
+ lastSeenEmissionId = -1;
229
+ liveCount = 0;
230
+ }
231
+
232
+ /// <summary>
233
+ /// Eviction-driven reset. Clears all structural state without touching
234
+ /// <see cref="version"/>, then bumps <see cref="version"/> as the LAST
235
+ /// step so any captured dispatch closure that observed the prior
236
+ /// version detects invalidation. <see cref="lastTouchTicks"/> is
237
+ /// intentionally preserved so the sweep can distinguish freshly-reset
238
+ /// slots from never-touched ones.
239
+ /// </summary>
240
+ public void Reset()
241
+ {
242
+ // Inline the structural-clear body of Clear(); do NOT call Clear()
243
+ // because that resets version=0 and would break the monotonic
244
+ // invariant the eviction layer depends on: stale deregister
245
+ // closures captured before reset must observe a strictly larger
246
+ // version after reset and skip their work.
247
+ foreach (BusPriorityBucket bucket in handlersByPriority.Values)
248
+ {
249
+ bucket.Clear();
250
+ }
251
+ handlersByPriority.Clear();
252
+ orderedPriorities.Clear();
253
+ cache.Clear();
254
+ dispatchState?.Reset();
255
+ dispatchState = null;
256
+ lastSeenVersion = -1;
257
+ lastSeenEmissionId = -1;
258
+ liveCount = 0;
259
+ unchecked
260
+ {
261
+ ++version;
262
+ }
263
+ }
264
+ }
265
+
266
+ /// <summary>
267
+ /// Per-message-type context-bound dispatch slot. Owns the
268
+ /// <see cref="InstanceId"/>-keyed map of inner <see cref="BusSinkSlot"/>s
269
+ /// for one message type's targeted or broadcast variants. Replaces the
270
+ /// outer per-message-type dictionaries previously held in the
271
+ /// targeted/broadcast sink fields on <see cref="MessageBus"/>.
272
+ /// </summary>
273
+ /// <remarks>
274
+ /// <para>
275
+ /// The inner map is rented from
276
+ /// <see cref="DxPools.InstanceIdDicts"/>. The pool stores
277
+ /// <c>Dictionary&lt;InstanceId, object&gt;</c> -- generic-erased to share a
278
+ /// single pool across every message-type instantiation. Each value is a
279
+ /// <see cref="BusSinkSlot"/>, accessed via
280
+ /// <see cref="Unsafe.As{T}(object)"/>; the class is sealed and only inserted
281
+ /// from this type's own methods, so the cast cannot encounter a foreign
282
+ /// runtime type. <c>DEBUG</c> builds verify the invariant at every
283
+ /// cast site.
284
+ /// </para>
285
+ /// <para>
286
+ /// The map is left null until first registration so empty slots cost only
287
+ /// the field set itself. <see cref="Clear"/> empties the map in place but
288
+ /// does NOT return it to the pool; <see cref="Reset"/> returns the map to
289
+ /// the pool and nulls the field.
290
+ /// </para>
291
+ /// </remarks>
292
+ internal sealed class BusContextSlot : IEvictableSlot
293
+ {
294
+ /// <summary>
295
+ /// Inner per-context map. Null until first registration. Values are
296
+ /// <see cref="BusSinkSlot"/> instances stored as <see cref="object"/>
297
+ /// so the underlying dictionary can be pooled in the shared
298
+ /// <see cref="DxPools.InstanceIdDicts"/> pool.
299
+ /// </summary>
300
+ public Dictionary<InstanceId, object> byContext;
301
+
302
+ /// <summary>Monotonic version counter for the slot's structural state.</summary>
303
+ public long version;
304
+
305
+ /// <summary>
306
+ /// Bus tick counter value at the most recent register / deregister /
307
+ /// emit that touched this slot. Maintained by the sweep touch hook;
308
+ /// preserved across <see cref="Clear"/> and <see cref="Reset"/>.
309
+ /// </summary>
310
+ public long lastTouchTicks;
311
+
312
+ /// <summary>
313
+ /// <para>
314
+ /// Reserved live-context counter intended to mirror the count of
315
+ /// <see cref="InstanceId"/> keys in <see cref="byContext"/> that
316
+ /// currently retain at least one live handler, so <see cref="IsEmpty"/>
317
+ /// becomes a single integer compare rather than a recursive walk over
318
+ /// the inner per-context slots. This counter is reserved until
319
+ /// <see cref="BusContextSlot"/> is used as the typed-sink storage type.
320
+ /// <see cref="IsEmpty"/> currently returns <c>true</c> at all times
321
+ /// because no writer increments <see cref="liveCount"/>.
322
+ /// </para>
323
+ /// <para>
324
+ /// Intended transitions once wired: the bus will increment by 1 when
325
+ /// a context goes from zero handlers to one, and decrement by 1 when
326
+ /// a context drops back to zero handlers (and is removed via
327
+ /// <see cref="RemoveContext"/>); registering or deregistering inside
328
+ /// an already-live context will not adjust this counter.
329
+ /// (<see cref="BusSinkSlot.liveCount"/> is the per-context handler
330
+ /// count; this is the per-slot context count.)
331
+ /// </para>
332
+ /// </summary>
333
+ public int liveCount;
334
+
335
+ /// <inheritdoc />
336
+ public long LastTouchTicks
337
+ {
338
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
339
+ get => lastTouchTicks;
340
+ }
341
+
342
+ /// <inheritdoc />
343
+ public bool IsEmpty
344
+ {
345
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
346
+ get => liveCount == 0;
347
+ }
348
+
349
+ /// <inheritdoc />
350
+ public long Version
351
+ {
352
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
353
+ get => version;
354
+ }
355
+
356
+ /// <summary>
357
+ /// Look up the inner <see cref="BusSinkSlot"/> for the supplied
358
+ /// context. Returns <c>false</c> when <see cref="byContext"/> is null
359
+ /// or the context is not present.
360
+ /// </summary>
361
+ /// <param name="context">The <see cref="InstanceId"/> context key.</param>
362
+ /// <param name="slot">
363
+ /// The inner slot when present; <c>null</c> otherwise.
364
+ /// </param>
365
+ /// <returns><c>true</c> when a slot was found.</returns>
366
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
367
+ public bool TryGetSlot(InstanceId context, out BusSinkSlot slot)
368
+ {
369
+ Dictionary<InstanceId, object> map = byContext;
370
+ if (map == null)
371
+ {
372
+ slot = null;
373
+ return false;
374
+ }
375
+ if (!map.TryGetValue(context, out object boxed))
376
+ {
377
+ slot = null;
378
+ return false;
379
+ }
380
+ DebugAssertSlot(boxed);
381
+ slot = Unsafe.As<BusSinkSlot>(boxed);
382
+ return true;
383
+ }
384
+
385
+ /// <summary>
386
+ /// Look up or create the inner <see cref="BusSinkSlot"/> for the
387
+ /// supplied context. Lazily rents the inner map from
388
+ /// <see cref="DxPools.InstanceIdDicts"/> on first use.
389
+ /// </summary>
390
+ /// <param name="context">The <see cref="InstanceId"/> context key.</param>
391
+ /// <returns>
392
+ /// The existing or freshly-allocated <see cref="BusSinkSlot"/> for
393
+ /// <paramref name="context"/>.
394
+ /// </returns>
395
+ public BusSinkSlot GetOrAddSlot(InstanceId context)
396
+ {
397
+ Dictionary<InstanceId, object> map = byContext;
398
+ if (map == null)
399
+ {
400
+ map = DxPools.InstanceIdDicts.Rent();
401
+ byContext = map;
402
+ }
403
+ if (map.TryGetValue(context, out object boxed))
404
+ {
405
+ DebugAssertSlot(boxed);
406
+ return Unsafe.As<BusSinkSlot>(boxed);
407
+ }
408
+ BusSinkSlot slot = new BusSinkSlot();
409
+ map[context] = slot;
410
+ return slot;
411
+ }
412
+
413
+ /// <summary>
414
+ /// Remove the inner slot for the supplied context, if present. Returns
415
+ /// <c>true</c> when an entry was removed. The removed inner slot is NOT
416
+ /// reset by this method -- callers wanting to reclaim it should call
417
+ /// <see cref="BusSinkSlot.Reset"/> on the returned reference before
418
+ /// dropping it.
419
+ /// </summary>
420
+ /// <param name="context">The <see cref="InstanceId"/> context key.</param>
421
+ /// <returns>
422
+ /// <c>true</c> when the context was present in <see cref="byContext"/>.
423
+ /// </returns>
424
+ /// <remarks>
425
+ /// The caller (the bus) is responsible for adjusting
426
+ /// <see cref="liveCount"/> after a successful removal. This method
427
+ /// intentionally does not touch <see cref="liveCount"/> so the bus can
428
+ /// decide the right semantic at the call site (<see cref="InstanceId"/>
429
+ /// keys vs handler sum -- see the <see cref="liveCount"/> field
430
+ /// docstring).
431
+ /// </remarks>
432
+ public bool RemoveContext(InstanceId context)
433
+ {
434
+ Dictionary<InstanceId, object> map = byContext;
435
+ if (map == null)
436
+ {
437
+ return false;
438
+ }
439
+ return map.Remove(context);
440
+ }
441
+
442
+ /// <summary>
443
+ /// Full-reset semantic. Recursively clears every inner
444
+ /// <see cref="BusSinkSlot"/> in place via
445
+ /// <see cref="BusSinkSlot.Clear"/> (deeper than the legacy
446
+ /// <c>HandlerCache&lt;TKey, TValue&gt;.Clear()</c> body, which relied
447
+ /// on GC of dropped entries) and empties the outer map without
448
+ /// returning it to the pool. Resets <see cref="version"/> to <c>0</c>;
449
+ /// this is NOT
450
+ /// monotonic and is intended only for the bus-wide
451
+ /// <c>MessageBus.ResetState()</c> code path. Use <see cref="Reset"/>
452
+ /// for sweep-driven slot reclamation.
453
+ /// </summary>
454
+ public void Clear()
455
+ {
456
+ Dictionary<InstanceId, object> map = byContext;
457
+ if (map != null)
458
+ {
459
+ foreach (object boxed in map.Values)
460
+ {
461
+ if (boxed == null)
462
+ {
463
+ continue;
464
+ }
465
+ DebugAssertSlot(boxed);
466
+ Unsafe.As<BusSinkSlot>(boxed).Clear();
467
+ }
468
+ map.Clear();
469
+ }
470
+ version = 0;
471
+ liveCount = 0;
472
+ }
473
+
474
+ /// <summary>
475
+ /// Eviction-driven reset. Walks every inner <see cref="BusSinkSlot"/>
476
+ /// and calls <see cref="BusSinkSlot.Reset"/> on each. Inner pooled
477
+ /// state must be drained BEFORE the outer map is recycled. Then returns
478
+ /// <see cref="byContext"/> to the
479
+ /// shared <see cref="DxPools.InstanceIdDicts"/> pool and nulls the
480
+ /// field. Bumps <see cref="version"/> as the LAST step so any captured
481
+ /// dispatch closure that observed the prior version detects
482
+ /// invalidation. <see cref="lastTouchTicks"/> is intentionally
483
+ /// preserved.
484
+ /// </summary>
485
+ public void Reset()
486
+ {
487
+ Dictionary<InstanceId, object> map = byContext;
488
+ if (map != null)
489
+ {
490
+ foreach (object boxed in map.Values)
491
+ {
492
+ if (boxed == null)
493
+ {
494
+ continue;
495
+ }
496
+ DebugAssertSlot(boxed);
497
+ Unsafe.As<BusSinkSlot>(boxed).Reset();
498
+ }
499
+ // Pool's onRecycled callback clears the dictionary before re-use.
500
+ DxPools.InstanceIdDicts.Return(map);
501
+ byContext = null;
502
+ }
503
+ liveCount = 0;
504
+ unchecked
505
+ {
506
+ ++version;
507
+ }
508
+ }
509
+
510
+ [Conditional("DEBUG")]
511
+ private static void DebugAssertSlot(object boxed)
512
+ {
513
+ Debug.Assert(
514
+ boxed is BusSinkSlot,
515
+ "BusContextSlot.byContext must only contain BusSinkSlot values; "
516
+ + "Unsafe.As<BusSinkSlot> would otherwise produce undefined behavior."
517
+ );
518
+ }
519
+ }
520
+
521
+ /// <summary>
522
+ /// Per-bus global accept-all slot. Replaces the legacy non-generic
523
+ /// <c>HandlerCache</c> previously declared in <see cref="MessageBus"/> --
524
+ /// the slot that holds the "subscribe to every emit" handlers.
525
+ /// </summary>
526
+ /// <remarks>
527
+ /// <para>
528
+ /// This slot models global accept-all handlers as one shared handler set
529
+ /// (<see cref="sharedHandlers"/> / <see cref="sharedCache"/>) and three
530
+ /// separate per-kind dispatch state fields
531
+ /// (<see cref="untargetedDispatchState"/>,
532
+ /// <see cref="targetedDispatchState"/>,
533
+ /// <see cref="broadcastDispatchState"/>). The discrete fields keep the
534
+ /// per-emission slot select branch-free under JIT monomorphization,
535
+ /// avoiding the dictionary lookup the legacy non-generic
536
+ /// <c>HandlerCache</c> imposed.
537
+ /// </para>
538
+ /// </remarks>
539
+ internal sealed class BusGlobalSlot : IEvictableSlot
540
+ {
541
+ /// <summary>
542
+ /// Live global handlers, keyed by handler with insertion order tracked
543
+ /// via the integer payload. Mirrors the legacy non-generic
544
+ /// <c>HandlerCache.handlers</c> field.
545
+ /// </summary>
546
+ public readonly Dictionary<MessageHandler, int> sharedHandlers = new();
547
+
548
+ /// <summary>
549
+ /// Reserved for global-slot snapshot iteration. Mirrors the legacy
550
+ /// non-generic <c>HandlerCache.cache</c> field, which was likewise
551
+ /// allocated for parity but never populated or read by any dispatch path.
552
+ /// Cleared by <see cref="Clear"/> and <see cref="Reset"/> as part of the
553
+ /// slot lifecycle.
554
+ /// </summary>
555
+ public readonly List<MessageHandler> sharedCache = new();
556
+
557
+ /// <summary>Monotonic version counter for the slot's structural state.</summary>
558
+ public long version;
559
+
560
+ /// <summary>
561
+ /// Reserved counter intended to record the <see cref="version"/> value
562
+ /// observed by the most recent dispatcher snapshot. Allocated for parity
563
+ /// with the per-cache <see cref="BusSinkSlot.lastSeenVersion"/> contract.
564
+ /// </summary>
565
+ public long lastSeenVersion = -1;
566
+
567
+ /// <summary>
568
+ /// Reserved counter intended to record the bus emission id of the
569
+ /// most recent dispatch that consumed this slot. Allocated for parity
570
+ /// with the per-cache <see cref="BusSinkSlot.lastSeenEmissionId"/>
571
+ /// contract.
572
+ /// </summary>
573
+ public long lastSeenEmissionId = -1;
574
+
575
+ /// <summary>
576
+ /// Bus tick counter value at the most recent register / deregister /
577
+ /// emit that touched this slot. Maintained by the sweep touch hook;
578
+ /// preserved across <see cref="Clear"/> and <see cref="Reset"/>.
579
+ /// </summary>
580
+ public long lastTouchTicks;
581
+
582
+ /// <summary>
583
+ /// <para>
584
+ /// Live-handler counter that mirrors <c>sharedHandlers.Count</c> at
585
+ /// every stable observation point. Maintained by the bus at the
586
+ /// register / deregister sites for <c>RegisterGlobalAcceptAll</c> so
587
+ /// <see cref="IsEmpty"/> is a single integer compare rather than a
588
+ /// dictionary-count read.
589
+ /// </para>
590
+ /// <para>
591
+ /// The invariant is <c>liveCount == sharedHandlers.Count</c>: only the
592
+ /// per-handler refcount's <c>0 -&gt; 1</c> transition (newly-inserted
593
+ /// handler) increments <see cref="liveCount"/>, and only the
594
+ /// <c>1 -&gt; 0</c> transition (final removal of a handler) decrements
595
+ /// it. Re-registering an already-present handler (refcount
596
+ /// <c>n -&gt; n+1</c> for <c>n &gt;= 1</c>) leaves the counter alone,
597
+ /// matching the dictionary's behaviour. Over-deregistration is a
598
+ /// no-op for both fields. <c>DEBUG</c> builds verify the invariant
599
+ /// after every register / deregister via
600
+ /// <c>MessageBus.DebugAssertGlobalLiveCount</c> and
601
+ /// <see cref="DebugAssertLiveCountInvariant"/>.
602
+ /// </para>
603
+ /// </summary>
604
+ public int liveCount;
605
+
606
+ /// <summary>
607
+ /// Dispatch state for the Untargeted-global emission path. One of the
608
+ /// three discrete per-kind fields. Separate slots over a per-kind
609
+ /// dictionary keep the per-emission
610
+ /// select branch-free under JIT monomorphization. Lazy alloc on first
611
+ /// Stage/Acquire; null after Reset().
612
+ /// </summary>
613
+ public MessageBus.DispatchState untargetedDispatchState;
614
+
615
+ /// <summary>
616
+ /// Dispatch state for the Targeted-global emission path. Sibling of
617
+ /// <see cref="untargetedDispatchState"/>; same lifetime semantics.
618
+ /// </summary>
619
+ public MessageBus.DispatchState targetedDispatchState;
620
+
621
+ /// <summary>
622
+ /// Dispatch state for the Broadcast-global emission path. Sibling of
623
+ /// <see cref="untargetedDispatchState"/>; same lifetime semantics.
624
+ /// </summary>
625
+ public MessageBus.DispatchState broadcastDispatchState;
626
+
627
+ /// <inheritdoc />
628
+ public long LastTouchTicks
629
+ {
630
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
631
+ get => lastTouchTicks;
632
+ }
633
+
634
+ /// <inheritdoc />
635
+ public bool IsEmpty
636
+ {
637
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
638
+ get => liveCount == 0;
639
+ }
640
+
641
+ /// <inheritdoc />
642
+ public long Version
643
+ {
644
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
645
+ get => version;
646
+ }
647
+
648
+ /// <summary>
649
+ /// Full-reset semantic that mirrors the legacy non-generic
650
+ /// <c>HandlerCache.Clear()</c> body. Clears
651
+ /// <see cref="sharedHandlers"/> and <see cref="sharedCache"/>
652
+ /// and resets the dispatch-snapshot counters. Resets
653
+ /// <see cref="version"/> to <c>0</c>; this is NOT monotonic and is
654
+ /// intended only for the bus-wide <c>MessageBus.ResetState()</c> code
655
+ /// path. Use <see cref="Reset"/> for sweep-driven slot reclamation.
656
+ /// </summary>
657
+ public void Clear()
658
+ {
659
+ sharedHandlers.Clear();
660
+ sharedCache.Clear();
661
+ untargetedDispatchState?.Reset();
662
+ untargetedDispatchState = null;
663
+ targetedDispatchState?.Reset();
664
+ targetedDispatchState = null;
665
+ broadcastDispatchState?.Reset();
666
+ broadcastDispatchState = null;
667
+ version = 0;
668
+ lastSeenVersion = -1;
669
+ lastSeenEmissionId = -1;
670
+ liveCount = 0;
671
+ }
672
+
673
+ /// <summary>
674
+ /// Eviction-driven reset. Clears all structural state without touching
675
+ /// <see cref="version"/>, then bumps <see cref="version"/> as the LAST
676
+ /// step so any captured dispatch closure that observed the prior
677
+ /// version detects invalidation. <see cref="lastTouchTicks"/> is
678
+ /// intentionally preserved.
679
+ /// </summary>
680
+ public void Reset()
681
+ {
682
+ // Inline the structural-clear body of Clear(); do NOT call Clear()
683
+ // because that resets version=0 and would break the monotonic
684
+ // invariant the eviction layer depends on.
685
+ sharedHandlers.Clear();
686
+ sharedCache.Clear();
687
+ untargetedDispatchState?.Reset();
688
+ untargetedDispatchState = null;
689
+ targetedDispatchState?.Reset();
690
+ targetedDispatchState = null;
691
+ broadcastDispatchState?.Reset();
692
+ broadcastDispatchState = null;
693
+ lastSeenVersion = -1;
694
+ lastSeenEmissionId = -1;
695
+ liveCount = 0;
696
+ unchecked
697
+ {
698
+ ++version;
699
+ }
700
+ }
701
+
702
+ /// <summary>
703
+ /// Defensive <c>DEBUG</c>-only assertion that <see cref="liveCount"/>
704
+ /// equals <c>sharedHandlers.Count</c>. Provided so contract tests can
705
+ /// pin the invariant without exposing private bus state. Stripped in
706
+ /// Release builds via <see cref="ConditionalAttribute"/>.
707
+ /// </summary>
708
+ [Conditional("DEBUG")]
709
+ internal void DebugAssertLiveCountInvariant()
710
+ {
711
+ Debug.Assert(
712
+ liveCount == sharedHandlers.Count,
713
+ "BusGlobalSlot.liveCount must mirror sharedHandlers.Count at every "
714
+ + "stable observation point."
715
+ );
716
+ }
717
+ }
718
+ }