com.wallstop-studios.dxmessaging 3.0.1 → 3.2.0

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 (148) hide show
  1. package/CHANGELOG.md +497 -2
  2. package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +3 -3
  3. package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +3 -3
  4. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +69 -62
  5. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +3 -3
  6. package/Editor/AssemblyInfo.cs.meta +9 -1
  7. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
  8. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +24 -15
  9. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
  10. package/Editor/CustomEditors/MessagingComponentEditor.cs.meta +9 -1
  11. package/Editor/DxMessagingEditorIdle.cs +62 -0
  12. package/{Runtime/Core/Internal/TypedDispatchLinkIndex.cs.meta → Editor/DxMessagingEditorIdle.cs.meta} +1 -1
  13. package/Editor/DxMessagingEditorInitializer.cs +112 -15
  14. package/Editor/DxMessagingEditorInitializer.cs.meta +9 -1
  15. package/Editor/DxMessagingEditorLog.cs +32 -0
  16. package/Editor/DxMessagingEditorLog.cs.meta +11 -0
  17. package/Editor/DxMessagingMenu.cs.meta +3 -3
  18. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
  19. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +137 -15
  20. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
  21. package/Editor/Settings/DxMessagingSettings.cs +92 -31
  22. package/Editor/Settings/DxMessagingSettings.cs.meta +9 -1
  23. package/Editor/Settings/DxMessagingSettingsProvider.cs.meta +9 -1
  24. package/Editor/SetupCscRsp.cs +539 -304
  25. package/Editor/SetupCscRsp.cs.meta +9 -1
  26. package/Editor/Testing/MessagingComponentEditorHarness.cs +1 -1
  27. package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +9 -1
  28. package/README.md +17 -18
  29. package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +3 -3
  30. package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +3 -3
  31. package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +3 -3
  32. package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +3 -3
  33. package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +3 -3
  34. package/Runtime/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  35. package/Runtime/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +46 -0
  36. package/Runtime/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  37. package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +2 -2
  38. package/Runtime/Analyzers.meta +8 -0
  39. package/Runtime/AssemblyInfo.cs.meta +9 -1
  40. package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +9 -1
  41. package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs.meta +9 -1
  42. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
  43. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +2 -4
  44. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs.meta +9 -1
  45. package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs.meta +9 -1
  46. package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs.meta +9 -1
  47. package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs +56 -0
  48. package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs.meta +11 -0
  49. package/Runtime/Core/DataStructure/CyclicBuffer.cs +44 -26
  50. package/Runtime/Core/DataStructure/CyclicBuffer.cs.meta +9 -1
  51. package/Runtime/Core/Diagnostics/MessageEmissionData.cs.meta +9 -1
  52. package/Runtime/Core/Diagnostics/MessageRegistrationData.cs.meta +9 -1
  53. package/Runtime/Core/Diagnostics/MessageRegistrationType.cs.meta +9 -1
  54. package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
  55. package/Runtime/Core/Extensions/EnumExtensions.cs +6 -5
  56. package/Runtime/Core/Extensions/EnumExtensions.cs.meta +9 -1
  57. package/Runtime/Core/Extensions/IListExtensions.cs.meta +9 -1
  58. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
  59. package/Runtime/Core/Extensions/MessageExtensions.cs +0 -60
  60. package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
  61. package/Runtime/Core/Helper/MessageCache.cs.meta +9 -1
  62. package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +9 -1
  63. package/Runtime/Core/IMessage.cs.meta +3 -3
  64. package/Runtime/Core/InstanceId.cs +25 -1
  65. package/Runtime/Core/InstanceId.cs.meta +3 -3
  66. package/Runtime/Core/Internal/DxUnsafe.cs +60 -0
  67. package/Runtime/Core/Internal/DxUnsafe.cs.meta +11 -0
  68. package/Runtime/Core/Internal/FlatDispatch.cs +198 -0
  69. package/Runtime/Core/Internal/FlatDispatch.cs.meta +11 -0
  70. package/Runtime/Core/Internal/TypedSlots.cs +47 -21
  71. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
  72. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
  73. package/Runtime/Core/MessageBus/IMessageBus.cs +75 -40
  74. package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
  75. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
  76. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +1 -0
  77. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
  78. package/Runtime/Core/MessageBus/Internal/BusSlots.cs +7 -6
  79. package/Runtime/Core/MessageBus/MessageBus.cs +2864 -3413
  80. package/Runtime/Core/MessageBus/MessageBus.cs.meta +3 -3
  81. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +3 -3
  82. package/Runtime/Core/MessageBus/MessageBusRegistration.cs +207 -0
  83. package/Runtime/Core/MessageBus/MessageBusRegistration.cs.meta +11 -0
  84. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +187 -14
  85. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +3 -3
  86. package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +3 -3
  87. package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +3 -3
  88. package/Runtime/Core/MessageHandler.cs +2206 -1510
  89. package/Runtime/Core/MessageHandler.cs.meta +3 -3
  90. package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
  91. package/Runtime/Core/MessageRegistrationToken.cs +1667 -812
  92. package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
  93. package/Runtime/Core/Messages/GlobalStringMessage.cs.meta +9 -1
  94. package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +3 -3
  95. package/Runtime/Core/Messages/ITargetedMessage.cs.meta +3 -3
  96. package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +3 -3
  97. package/Runtime/Core/Messages/ReflexiveMessage.cs.meta +9 -1
  98. package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +3 -3
  99. package/Runtime/Core/Messages/StringMessage.cs.meta +9 -1
  100. package/Runtime/Core/MessagingDebug.cs.meta +3 -3
  101. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +3 -3
  102. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +3 -3
  103. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +3 -3
  104. package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +9 -1
  105. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +3 -3
  106. package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +9 -1
  107. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
  108. package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +9 -1
  109. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
  110. package/Runtime/Unity/MessageAwareComponent.cs +46 -1
  111. package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
  112. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
  113. package/Runtime/Unity/MessagingComponent.cs +43 -10
  114. package/Runtime/Unity/MessagingComponent.cs.meta +3 -3
  115. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +3 -3
  116. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +3 -3
  117. package/Runtime/WallstopStudios.DxMessaging.asmdef +1 -1
  118. package/Samples~/DI/README.md +7 -7
  119. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +3 -3
  120. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +3 -3
  121. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +3 -3
  122. package/Samples~/Mini Combat/Boot.cs.meta +3 -3
  123. package/Samples~/Mini Combat/Enemy.cs.meta +3 -3
  124. package/Samples~/Mini Combat/Messages.cs.meta +3 -3
  125. package/Samples~/Mini Combat/Player.cs.meta +3 -3
  126. package/Samples~/Mini Combat/UIOverlay.cs.meta +3 -3
  127. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +3 -3
  128. package/Samples~/UI Buttons + Inspector/Messages.cs.meta +3 -3
  129. package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +3 -3
  130. package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +3 -3
  131. package/SourceGenerators/Directory.Build.props +50 -3
  132. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +96 -63
  133. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
  134. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +745 -87
  135. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +9 -1
  136. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +40 -46
  137. package/SourceGenerators/global.json +7 -0
  138. package/SourceGenerators/global.json.meta +7 -0
  139. package/package.json +30 -40
  140. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  141. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -33
  142. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  143. package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs +0 -51
  144. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  145. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
  146. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
  147. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
  148. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
@@ -2,6 +2,7 @@ namespace DxMessaging.Core
2
2
  {
3
3
  using System;
4
4
  using System.Collections.Generic;
5
+ using System.Runtime.ExceptionServices;
5
6
  using DataStructure;
6
7
  using Diagnostics;
7
8
  using MessageBus;
@@ -57,17 +58,81 @@ namespace DxMessaging.Core
57
58
 
58
59
  private readonly MessageHandler _messageHandler;
59
60
 
60
- private readonly Dictionary<MessageRegistrationHandle, Action> _registrations = new();
61
- private readonly Dictionary<MessageRegistrationHandle, Action> _deregistrations = new();
62
- private readonly List<Action> _actionQueue = new();
61
+ // Maps each staged registration handle to the unified per-handle Registration
62
+ // object the public Register* methods built. Calling Registration.Register()
63
+ // (re)registers the handler on the bus and returns the matching
64
+ // HandlerDeregistration. The Registration object IS the collapsed staging
65
+ // state: it replaces (a) the per-registration staging Func display class, (b)
66
+ // the staging Func delegate itself, and (c) the nested AugmentedHandler
67
+ // local-function delegate -- the captured target/source, user handler,
68
+ // priority, and kind are now plain fields, and the diagnostics-augmented
69
+ // invoker is an instance method bound to the object (the FastHandler<T> handed
70
+ // to MessageHandler is (FastHandler<T>)registration.AugmentedHandlerScalar).
71
+ // Net ~ -2 managed allocations per registration. The central replay loop pairs
72
+ // the Registration with its handle and performs the AddDeregistration. The
73
+ // re-entrancy snapshot semantics are unchanged: the replay queue captures the
74
+ // Registration reference (exactly as it previously captured the staging Func),
75
+ // so a registration removed mid-replay is still replayed if it was already
76
+ // snapshotted.
77
+ private readonly Dictionary<MessageRegistrationHandle, Registration> _registrations = new();
78
+
79
+ // Staged-registration handles in registration order. Dictionary
80
+ // enumeration order is NOT stable across Remove/Add churn, so Enable()
81
+ // (and RetargetMessageBus) replay staged registrations by walking this
82
+ // list instead of _registrations.Values; otherwise a Disable()/Enable()
83
+ // cycle after churn would silently permute the documented
84
+ // "same priority uses registration order" dispatch contract.
85
+ // Invariant: contains exactly the keys of _registrations, in the order
86
+ // InternalRegister staged them (handle ids are monotonically
87
+ // increasing, so this list is also sorted by handle id).
88
+ private readonly List<MessageRegistrationHandle> _registrationOrder = new();
89
+
90
+ // Maps a handle to its live de-registration(s). The common case is EXACTLY ONE
91
+ // de-registration per handle, so the single Action is stored INLINE as the dictionary
92
+ // value (presence == pending, removal == done, keep-on-throw == retryable) -- no
93
+ // per-registration PendingDeregistration object. A PendingDeregistration is allocated and
94
+ // substituted ONLY when a second de-registration accumulates on the same handle (the rare
95
+ // retarget-recovery replay; see AddDeregistration). The value is therefore either an
96
+ // Action (1 de-registration) or a PendingDeregistration (2+).
97
+ private readonly Dictionary<MessageRegistrationHandle, object> _deregistrations = new();
98
+
99
+ // Snapshot of (handle, Registration object) pairs to replay on Enable() /
100
+ // RetargetMessageBus. Snapshotting before invoking lets replay tolerate
101
+ // re-entrant registration mutation (a handler that registers or removes
102
+ // handlers while it runs). The pair carries the handle so the central replay
103
+ // loop can call Registration.Register() and AddDeregistration without a
104
+ // per-registration wrapper closure.
105
+ private readonly List<StagedRegistration> _registrationReplayQueue = new();
106
+ private readonly List<MessageRegistrationHandle> _handleQueue = new();
63
107
  internal readonly Dictionary<
64
108
  MessageRegistrationHandle,
65
109
  MessageRegistrationMetadata
66
110
  > _metadata = new();
67
- internal readonly Dictionary<MessageRegistrationHandle, int> _callCounts = new();
68
- internal readonly CyclicBuffer<MessageEmissionData> _emissionBuffer = new(
69
- IMessageBus.GlobalMessageBufferSize
70
- );
111
+
112
+ // Diagnostics-only collections, allocated lazily on first use. A token whose
113
+ // owner never enables diagnostics (the default -- GlobalDiagnosticsTargets is
114
+ // Off -- and the common player case) never materializes them, saving the
115
+ // dictionary, the cyclic buffer, and the buffer's two backing lists per token.
116
+ // These are exposed as properties under their original field names so the
117
+ // inspector overlay and the diagnostics tests (which read token._callCounts /
118
+ // token._emissionBuffer) compile and behave unchanged; the getter caches into
119
+ // the backing field, so repeated reads return the same instance and an editor
120
+ // read simply materializes an empty collection. The only production writers are
121
+ // the dispatch-time AugmentedHandler bodies, all guarded by _diagnosticMode, so
122
+ // production-with-diagnostics-off never triggers the allocation. Teardown
123
+ // (ClearDiagnosticState / RemoveRegistrationState /
124
+ // PruneRegistrationStateToFailedDeregistrations) clears through the backing
125
+ // fields to avoid materializing a collection just to empty it.
126
+ private Dictionary<MessageRegistrationHandle, int> _callCountsBacking;
127
+ private CyclicBuffer<MessageEmissionData> _emissionBufferBacking;
128
+
129
+ internal Dictionary<MessageRegistrationHandle, int> _callCounts =>
130
+ _callCountsBacking ??= new Dictionary<MessageRegistrationHandle, int>();
131
+
132
+ internal CyclicBuffer<MessageEmissionData> _emissionBuffer =>
133
+ _emissionBufferBacking ??= new CyclicBuffer<MessageEmissionData>(
134
+ IMessageBus.GlobalMessageBufferSize
135
+ );
71
136
 
72
137
  private IMessageBus _messageBus;
73
138
  private bool _enabled;
@@ -93,33 +158,19 @@ namespace DxMessaging.Core
93
158
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
94
159
  }
95
160
  return InternalRegister(
96
- handle =>
97
- {
98
- return _messageHandler.RegisterTargetedMessageHandler(
99
- target,
100
- targetedHandler,
101
- AugmentedHandler,
102
- priority: priority,
103
- messageBus: _messageBus
104
- );
105
-
106
- void AugmentedHandler(T message)
107
- {
108
- targetedHandler(message);
109
- if (_diagnosticMode)
110
- {
111
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
112
- _emissionBuffer.Add(new MessageEmissionData(message, target));
113
- }
114
- }
115
- },
116
- () =>
117
- new MessageRegistrationMetadata(
118
- target,
119
- typeof(T),
120
- MessageRegistrationType.Targeted,
121
- priority
122
- )
161
+ new TargetedRegistration<T>(
162
+ this,
163
+ RegistrationKind.TargetedHandlerAction,
164
+ target,
165
+ targetedHandler,
166
+ priority
167
+ ),
168
+ new MessageRegistrationMetadata(
169
+ target,
170
+ typeof(T),
171
+ MessageRegistrationType.Targeted,
172
+ priority
173
+ )
123
174
  );
124
175
  }
125
176
 
@@ -136,33 +187,19 @@ namespace DxMessaging.Core
136
187
  }
137
188
 
138
189
  return InternalRegister(
139
- handle =>
140
- {
141
- return _messageHandler.RegisterTargetedMessageHandler(
142
- target,
143
- targetedHandler,
144
- AugmentedHandler,
145
- priority: priority,
146
- messageBus: _messageBus
147
- );
148
-
149
- void AugmentedHandler(ref T message)
150
- {
151
- targetedHandler(ref message);
152
- if (_diagnosticMode)
153
- {
154
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
155
- _emissionBuffer.Add(new MessageEmissionData(message, target));
156
- }
157
- }
158
- },
159
- () =>
160
- new MessageRegistrationMetadata(
161
- target,
162
- typeof(T),
163
- MessageRegistrationType.Targeted,
164
- priority
165
- )
190
+ new TargetedRegistration<T>(
191
+ this,
192
+ RegistrationKind.TargetedHandlerFast,
193
+ target,
194
+ targetedHandler,
195
+ priority
196
+ ),
197
+ new MessageRegistrationMetadata(
198
+ target,
199
+ typeof(T),
200
+ MessageRegistrationType.Targeted,
201
+ priority
202
+ )
166
203
  );
167
204
  }
168
205
 
@@ -284,33 +321,19 @@ namespace DxMessaging.Core
284
321
  where T : ITargetedMessage
285
322
  {
286
323
  return InternalRegister(
287
- handle =>
288
- {
289
- return _messageHandler.RegisterTargetedPostProcessor(
290
- target,
291
- targetedPostProcessor,
292
- AugmentedHandler,
293
- priority,
294
- _messageBus
295
- );
296
-
297
- void AugmentedHandler(ref T message)
298
- {
299
- targetedPostProcessor(ref message);
300
- if (_diagnosticMode)
301
- {
302
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
303
- _emissionBuffer.Add(new MessageEmissionData(message, target));
304
- }
305
- }
306
- },
307
- () =>
308
- new MessageRegistrationMetadata(
309
- target,
310
- typeof(T),
311
- MessageRegistrationType.TargetedPostProcessor,
312
- priority
313
- )
324
+ new TargetedRegistration<T>(
325
+ this,
326
+ RegistrationKind.TargetedPostProcessorFast,
327
+ target,
328
+ targetedPostProcessor,
329
+ priority
330
+ ),
331
+ new MessageRegistrationMetadata(
332
+ target,
333
+ typeof(T),
334
+ MessageRegistrationType.TargetedPostProcessor,
335
+ priority
336
+ )
314
337
  );
315
338
  }
316
339
 
@@ -330,33 +353,19 @@ namespace DxMessaging.Core
330
353
  where T : ITargetedMessage
331
354
  {
332
355
  return InternalRegister(
333
- handle =>
334
- {
335
- return _messageHandler.RegisterTargetedPostProcessor(
336
- target,
337
- targetedPostProcessor,
338
- AugmentedHandler,
339
- priority,
340
- _messageBus
341
- );
342
-
343
- void AugmentedHandler(ref T message)
344
- {
345
- targetedPostProcessor(ref message);
346
- if (_diagnosticMode)
347
- {
348
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
349
- _emissionBuffer.Add(new MessageEmissionData(message, target));
350
- }
351
- }
352
- },
353
- () =>
354
- new MessageRegistrationMetadata(
355
- target,
356
- typeof(T),
357
- MessageRegistrationType.TargetedPostProcessor,
358
- priority
359
- )
356
+ new TargetedRegistration<T>(
357
+ this,
358
+ RegistrationKind.TargetedPostProcessorFast,
359
+ target,
360
+ targetedPostProcessor,
361
+ priority
362
+ ),
363
+ new MessageRegistrationMetadata(
364
+ target,
365
+ typeof(T),
366
+ MessageRegistrationType.TargetedPostProcessor,
367
+ priority
368
+ )
360
369
  );
361
370
  }
362
371
  #endif
@@ -419,33 +428,19 @@ namespace DxMessaging.Core
419
428
  where T : ITargetedMessage
420
429
  {
421
430
  return InternalRegister(
422
- handle =>
423
- {
424
- return _messageHandler.RegisterTargetedPostProcessor(
425
- target,
426
- targetedPostProcessor,
427
- AugmentedHandler,
428
- priority,
429
- _messageBus
430
- );
431
-
432
- void AugmentedHandler(ref T message)
433
- {
434
- targetedPostProcessor(ref message);
435
- if (_diagnosticMode)
436
- {
437
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
438
- _emissionBuffer.Add(new MessageEmissionData(message, target));
439
- }
440
- }
441
- },
442
- () =>
443
- new MessageRegistrationMetadata(
444
- target,
445
- typeof(T),
446
- MessageRegistrationType.TargetedPostProcessor,
447
- priority
448
- )
431
+ new TargetedRegistration<T>(
432
+ this,
433
+ RegistrationKind.TargetedPostProcessorFast,
434
+ target,
435
+ targetedPostProcessor,
436
+ priority
437
+ ),
438
+ new MessageRegistrationMetadata(
439
+ target,
440
+ typeof(T),
441
+ MessageRegistrationType.TargetedPostProcessor,
442
+ priority
443
+ )
449
444
  );
450
445
  }
451
446
 
@@ -465,33 +460,19 @@ namespace DxMessaging.Core
465
460
  where T : ITargetedMessage
466
461
  {
467
462
  return InternalRegister(
468
- handle =>
469
- {
470
- return _messageHandler.RegisterTargetedPostProcessor(
471
- target,
472
- targetedPostProcessor,
473
- AugmentedHandler,
474
- priority,
475
- _messageBus
476
- );
477
-
478
- void AugmentedHandler(T message)
479
- {
480
- targetedPostProcessor(message);
481
- if (_diagnosticMode)
482
- {
483
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
484
- _emissionBuffer.Add(new MessageEmissionData(message, target));
485
- }
486
- }
487
- },
488
- () =>
489
- new MessageRegistrationMetadata(
490
- target,
491
- typeof(T),
492
- MessageRegistrationType.TargetedPostProcessor,
493
- priority
494
- )
463
+ new TargetedRegistration<T>(
464
+ this,
465
+ RegistrationKind.TargetedPostProcessorAction,
466
+ target,
467
+ targetedPostProcessor,
468
+ priority
469
+ ),
470
+ new MessageRegistrationMetadata(
471
+ target,
472
+ typeof(T),
473
+ MessageRegistrationType.TargetedPostProcessor,
474
+ priority
475
+ )
495
476
  );
496
477
  }
497
478
 
@@ -516,32 +497,19 @@ namespace DxMessaging.Core
516
497
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
517
498
  }
518
499
  return InternalRegister(
519
- handle =>
520
- {
521
- return _messageHandler.RegisterTargetedWithoutTargeting(
522
- messageHandler,
523
- AugmentedHandler,
524
- priority: priority,
525
- messageBus: _messageBus
526
- );
527
-
528
- void AugmentedHandler(InstanceId target, T message)
529
- {
530
- messageHandler(target, message);
531
- if (_diagnosticMode)
532
- {
533
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
534
- _emissionBuffer.Add(new MessageEmissionData(message, target));
535
- }
536
- }
537
- },
538
- () =>
539
- new MessageRegistrationMetadata(
540
- null,
541
- typeof(T),
542
- MessageRegistrationType.TargetedWithoutTargeting,
543
- priority
544
- )
500
+ new TargetedRegistration<T>(
501
+ this,
502
+ RegistrationKind.TargetedWithoutTargetingAction,
503
+ default,
504
+ messageHandler,
505
+ priority
506
+ ),
507
+ new MessageRegistrationMetadata(
508
+ null,
509
+ typeof(T),
510
+ MessageRegistrationType.TargetedWithoutTargeting,
511
+ priority
512
+ )
545
513
  );
546
514
  }
547
515
 
@@ -566,32 +534,19 @@ namespace DxMessaging.Core
566
534
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
567
535
  }
568
536
  return InternalRegister(
569
- handle =>
570
- {
571
- return _messageHandler.RegisterTargetedWithoutTargeting(
572
- messageHandler,
573
- AugmentedHandler,
574
- priority: priority,
575
- messageBus: _messageBus
576
- );
577
-
578
- void AugmentedHandler(ref InstanceId target, ref T message)
579
- {
580
- messageHandler(ref target, ref message);
581
- if (_diagnosticMode)
582
- {
583
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
584
- _emissionBuffer.Add(new MessageEmissionData(message, target));
585
- }
586
- }
587
- },
588
- () =>
589
- new MessageRegistrationMetadata(
590
- null,
591
- typeof(T),
592
- MessageRegistrationType.TargetedWithoutTargeting,
593
- priority
594
- )
537
+ new TargetedRegistration<T>(
538
+ this,
539
+ RegistrationKind.TargetedWithoutTargetingFast,
540
+ default,
541
+ messageHandler,
542
+ priority
543
+ ),
544
+ new MessageRegistrationMetadata(
545
+ null,
546
+ typeof(T),
547
+ MessageRegistrationType.TargetedWithoutTargeting,
548
+ priority
549
+ )
595
550
  );
596
551
  }
597
552
 
@@ -616,32 +571,19 @@ namespace DxMessaging.Core
616
571
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
617
572
  }
618
573
  return InternalRegister(
619
- handle =>
620
- {
621
- return _messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
622
- postProcessor,
623
- AugmentedHandler,
624
- priority,
625
- _messageBus
626
- );
627
-
628
- void AugmentedHandler(InstanceId target, T message)
629
- {
630
- postProcessor(target, message);
631
- if (_diagnosticMode)
632
- {
633
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
634
- _emissionBuffer.Add(new MessageEmissionData(message, target));
635
- }
636
- }
637
- },
638
- () =>
639
- new MessageRegistrationMetadata(
640
- null,
641
- typeof(T),
642
- MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
643
- priority
644
- )
574
+ new TargetedRegistration<T>(
575
+ this,
576
+ RegistrationKind.TargetedWithoutTargetingPostProcessorAction,
577
+ default,
578
+ postProcessor,
579
+ priority
580
+ ),
581
+ new MessageRegistrationMetadata(
582
+ null,
583
+ typeof(T),
584
+ MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
585
+ priority
586
+ )
645
587
  );
646
588
  }
647
589
 
@@ -666,32 +608,19 @@ namespace DxMessaging.Core
666
608
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
667
609
  }
668
610
  return InternalRegister(
669
- handle =>
670
- {
671
- return _messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
672
- postProcessor,
673
- AugmentedHandler,
674
- priority,
675
- _messageBus
676
- );
677
-
678
- void AugmentedHandler(ref InstanceId target, ref T message)
679
- {
680
- postProcessor(ref target, ref message);
681
- if (_diagnosticMode)
682
- {
683
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
684
- _emissionBuffer.Add(new MessageEmissionData(message, target));
685
- }
686
- }
687
- },
688
- () =>
689
- new MessageRegistrationMetadata(
690
- null,
691
- typeof(T),
692
- MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
693
- priority
694
- )
611
+ new TargetedRegistration<T>(
612
+ this,
613
+ RegistrationKind.TargetedWithoutTargetingPostProcessorFast,
614
+ default,
615
+ postProcessor,
616
+ priority
617
+ ),
618
+ new MessageRegistrationMetadata(
619
+ null,
620
+ typeof(T),
621
+ MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
622
+ priority
623
+ )
695
624
  );
696
625
  }
697
626
 
@@ -723,32 +652,18 @@ namespace DxMessaging.Core
723
652
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
724
653
  }
725
654
  return InternalRegister(
726
- handle =>
727
- {
728
- return _messageHandler.RegisterUntargetedMessageHandler(
729
- untargetedHandler,
730
- AugmentedHandler,
731
- priority: priority,
732
- messageBus: _messageBus
733
- );
734
-
735
- void AugmentedHandler(T message)
736
- {
737
- untargetedHandler(message);
738
- if (_diagnosticMode)
739
- {
740
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
741
- _emissionBuffer.Add(new MessageEmissionData(message));
742
- }
743
- }
744
- },
745
- () =>
746
- new MessageRegistrationMetadata(
747
- null,
748
- typeof(T),
749
- MessageRegistrationType.Untargeted,
750
- priority
751
- )
655
+ new UntargetedRegistration<T>(
656
+ this,
657
+ RegistrationKind.UntargetedHandlerAction,
658
+ untargetedHandler,
659
+ priority
660
+ ),
661
+ new MessageRegistrationMetadata(
662
+ null,
663
+ typeof(T),
664
+ MessageRegistrationType.Untargeted,
665
+ priority
666
+ )
752
667
  );
753
668
  }
754
669
 
@@ -779,32 +694,18 @@ namespace DxMessaging.Core
779
694
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
780
695
  }
781
696
  return InternalRegister(
782
- handle =>
783
- {
784
- return _messageHandler.RegisterUntargetedMessageHandler(
785
- untargetedHandler,
786
- AugmentedHandler,
787
- priority: priority,
788
- messageBus: _messageBus
789
- );
790
-
791
- void AugmentedHandler(ref T message)
792
- {
793
- untargetedHandler(ref message);
794
- if (_diagnosticMode)
795
- {
796
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
797
- _emissionBuffer.Add(new MessageEmissionData(message));
798
- }
799
- }
800
- },
801
- () =>
802
- new MessageRegistrationMetadata(
803
- null,
804
- typeof(T),
805
- MessageRegistrationType.Untargeted,
806
- priority
807
- )
697
+ new UntargetedRegistration<T>(
698
+ this,
699
+ RegistrationKind.UntargetedHandlerFast,
700
+ untargetedHandler,
701
+ priority
702
+ ),
703
+ new MessageRegistrationMetadata(
704
+ null,
705
+ typeof(T),
706
+ MessageRegistrationType.Untargeted,
707
+ priority
708
+ )
808
709
  );
809
710
  }
810
711
 
@@ -826,32 +727,18 @@ namespace DxMessaging.Core
826
727
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
827
728
  }
828
729
  return InternalRegister(
829
- handle =>
830
- {
831
- return _messageHandler.RegisterUntargetedPostProcessor(
832
- untargetedPostProcessor,
833
- AugmentedHandler,
834
- priority,
835
- _messageBus
836
- );
837
-
838
- void AugmentedHandler(ref T message)
839
- {
840
- untargetedPostProcessor(ref message);
841
- if (_diagnosticMode)
842
- {
843
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
844
- _emissionBuffer.Add(new MessageEmissionData(message));
845
- }
846
- }
847
- },
848
- () =>
849
- new MessageRegistrationMetadata(
850
- null,
851
- typeof(T),
852
- MessageRegistrationType.UntargetedPostProcessor,
853
- priority
854
- )
730
+ new UntargetedRegistration<T>(
731
+ this,
732
+ RegistrationKind.UntargetedPostProcessorFast,
733
+ untargetedPostProcessor,
734
+ priority
735
+ ),
736
+ new MessageRegistrationMetadata(
737
+ null,
738
+ typeof(T),
739
+ MessageRegistrationType.UntargetedPostProcessor,
740
+ priority
741
+ )
855
742
  );
856
743
  }
857
744
 
@@ -867,33 +754,19 @@ namespace DxMessaging.Core
867
754
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
868
755
  }
869
756
  return InternalRegister(
870
- handle =>
871
- {
872
- return _messageHandler.RegisterSourcedBroadcastMessageHandler(
873
- source,
874
- broadcastHandler,
875
- AugmentedHandler,
876
- priority: priority,
877
- messageBus: _messageBus
878
- );
879
-
880
- void AugmentedHandler(T message)
881
- {
882
- broadcastHandler(message);
883
- if (_diagnosticMode)
884
- {
885
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
886
- _emissionBuffer.Add(new MessageEmissionData(message, source));
887
- }
888
- }
889
- },
890
- () =>
891
- new MessageRegistrationMetadata(
892
- source,
893
- typeof(T),
894
- MessageRegistrationType.Broadcast,
895
- priority
896
- )
757
+ new BroadcastRegistration<T>(
758
+ this,
759
+ RegistrationKind.BroadcastHandlerAction,
760
+ source,
761
+ broadcastHandler,
762
+ priority
763
+ ),
764
+ new MessageRegistrationMetadata(
765
+ source,
766
+ typeof(T),
767
+ MessageRegistrationType.Broadcast,
768
+ priority
769
+ )
897
770
  );
898
771
  }
899
772
 
@@ -909,33 +782,19 @@ namespace DxMessaging.Core
909
782
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
910
783
  }
911
784
  return InternalRegister(
912
- handle =>
913
- {
914
- return _messageHandler.RegisterSourcedBroadcastMessageHandler(
915
- source,
916
- broadcastHandler,
917
- AugmentedHandler,
918
- priority: priority,
919
- messageBus: _messageBus
920
- );
921
-
922
- void AugmentedHandler(ref T message)
923
- {
924
- broadcastHandler(ref message);
925
- if (_diagnosticMode)
926
- {
927
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
928
- _emissionBuffer.Add(new MessageEmissionData(message, source));
929
- }
930
- }
931
- },
932
- () =>
933
- new MessageRegistrationMetadata(
934
- source,
935
- typeof(T),
936
- MessageRegistrationType.Broadcast,
937
- priority
938
- )
785
+ new BroadcastRegistration<T>(
786
+ this,
787
+ RegistrationKind.BroadcastHandlerFast,
788
+ source,
789
+ broadcastHandler,
790
+ priority
791
+ ),
792
+ new MessageRegistrationMetadata(
793
+ source,
794
+ typeof(T),
795
+ MessageRegistrationType.Broadcast,
796
+ priority
797
+ )
939
798
  );
940
799
  }
941
800
 
@@ -952,33 +811,19 @@ namespace DxMessaging.Core
952
811
  }
953
812
 
954
813
  return InternalRegister(
955
- handle =>
956
- {
957
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
958
- source,
959
- broadcastPostProcessor,
960
- AugmentedHandler,
961
- priority,
962
- _messageBus
963
- );
964
-
965
- void AugmentedHandler(T message)
966
- {
967
- broadcastPostProcessor(message);
968
- if (_diagnosticMode)
969
- {
970
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
971
- _emissionBuffer.Add(new MessageEmissionData(message, source));
972
- }
973
- }
974
- },
975
- () =>
976
- new MessageRegistrationMetadata(
977
- source,
978
- typeof(T),
979
- MessageRegistrationType.BroadcastPostProcessor,
980
- priority
981
- )
814
+ new BroadcastRegistration<T>(
815
+ this,
816
+ RegistrationKind.BroadcastPostProcessorAction,
817
+ source,
818
+ broadcastPostProcessor,
819
+ priority
820
+ ),
821
+ new MessageRegistrationMetadata(
822
+ source,
823
+ typeof(T),
824
+ MessageRegistrationType.BroadcastPostProcessor,
825
+ priority
826
+ )
982
827
  );
983
828
  }
984
829
 
@@ -994,33 +839,19 @@ namespace DxMessaging.Core
994
839
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
995
840
  }
996
841
  return InternalRegister(
997
- handle =>
998
- {
999
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
1000
- source,
1001
- broadcastPostProcessor,
1002
- AugmentedHandler,
1003
- priority,
1004
- _messageBus
1005
- );
1006
-
1007
- void AugmentedHandler(ref T message)
1008
- {
1009
- broadcastPostProcessor(ref message);
1010
- if (_diagnosticMode)
1011
- {
1012
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1013
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1014
- }
1015
- }
1016
- },
1017
- () =>
1018
- new MessageRegistrationMetadata(
1019
- source,
1020
- typeof(T),
1021
- MessageRegistrationType.BroadcastPostProcessor,
1022
- priority
1023
- )
842
+ new BroadcastRegistration<T>(
843
+ this,
844
+ RegistrationKind.BroadcastPostProcessorFast,
845
+ source,
846
+ broadcastPostProcessor,
847
+ priority
848
+ ),
849
+ new MessageRegistrationMetadata(
850
+ source,
851
+ typeof(T),
852
+ MessageRegistrationType.BroadcastPostProcessor,
853
+ priority
854
+ )
1024
855
  );
1025
856
  }
1026
857
 
@@ -1165,33 +996,19 @@ namespace DxMessaging.Core
1165
996
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1166
997
  }
1167
998
  return InternalRegister(
1168
- handle =>
1169
- {
1170
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
1171
- source,
1172
- broadcastPostProcessor,
1173
- AugmentedHandler,
1174
- priority: priority,
1175
- _messageBus
1176
- );
1177
-
1178
- void AugmentedHandler(T message)
1179
- {
1180
- broadcastPostProcessor(message);
1181
- if (_diagnosticMode)
1182
- {
1183
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1184
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1185
- }
1186
- }
1187
- },
1188
- () =>
1189
- new MessageRegistrationMetadata(
1190
- source,
1191
- typeof(T),
1192
- MessageRegistrationType.BroadcastPostProcessor,
1193
- priority
1194
- )
999
+ new BroadcastRegistration<T>(
1000
+ this,
1001
+ RegistrationKind.BroadcastPostProcessorAction,
1002
+ source,
1003
+ broadcastPostProcessor,
1004
+ priority
1005
+ ),
1006
+ new MessageRegistrationMetadata(
1007
+ source,
1008
+ typeof(T),
1009
+ MessageRegistrationType.BroadcastPostProcessor,
1010
+ priority
1011
+ )
1195
1012
  );
1196
1013
  }
1197
1014
 
@@ -1215,33 +1032,19 @@ namespace DxMessaging.Core
1215
1032
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1216
1033
  }
1217
1034
  return InternalRegister(
1218
- handle =>
1219
- {
1220
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
1221
- source,
1222
- broadcastPostProcessor,
1223
- AugmentedHandler,
1224
- priority: priority,
1225
- _messageBus
1226
- );
1227
-
1228
- void AugmentedHandler(ref T message)
1229
- {
1230
- broadcastPostProcessor(ref message);
1231
- if (_diagnosticMode)
1232
- {
1233
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1234
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1235
- }
1236
- }
1237
- },
1238
- () =>
1239
- new MessageRegistrationMetadata(
1240
- source,
1241
- typeof(T),
1242
- MessageRegistrationType.BroadcastPostProcessor,
1243
- priority
1244
- )
1035
+ new BroadcastRegistration<T>(
1036
+ this,
1037
+ RegistrationKind.BroadcastPostProcessorFast,
1038
+ source,
1039
+ broadcastPostProcessor,
1040
+ priority
1041
+ ),
1042
+ new MessageRegistrationMetadata(
1043
+ source,
1044
+ typeof(T),
1045
+ MessageRegistrationType.BroadcastPostProcessor,
1046
+ priority
1047
+ )
1245
1048
  );
1246
1049
  }
1247
1050
  #endif
@@ -1357,32 +1160,19 @@ namespace DxMessaging.Core
1357
1160
  }
1358
1161
 
1359
1162
  return InternalRegister(
1360
- handle =>
1361
- {
1362
- return _messageHandler.RegisterSourcedBroadcastWithoutSource(
1363
- broadcastHandler,
1364
- AugmentedHandler,
1365
- priority: priority,
1366
- messageBus: _messageBus
1367
- );
1368
-
1369
- void AugmentedHandler(InstanceId source, T message)
1370
- {
1371
- broadcastHandler(source, message);
1372
- if (_diagnosticMode)
1373
- {
1374
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1375
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1376
- }
1377
- }
1378
- },
1379
- () =>
1380
- new MessageRegistrationMetadata(
1381
- null,
1382
- typeof(T),
1383
- MessageRegistrationType.BroadcastWithoutSource,
1384
- priority
1385
- )
1163
+ new BroadcastRegistration<T>(
1164
+ this,
1165
+ RegistrationKind.BroadcastWithoutSourceAction,
1166
+ default,
1167
+ broadcastHandler,
1168
+ priority
1169
+ ),
1170
+ new MessageRegistrationMetadata(
1171
+ null,
1172
+ typeof(T),
1173
+ MessageRegistrationType.BroadcastWithoutSource,
1174
+ priority
1175
+ )
1386
1176
  );
1387
1177
  }
1388
1178
 
@@ -1408,32 +1198,19 @@ namespace DxMessaging.Core
1408
1198
  }
1409
1199
 
1410
1200
  return InternalRegister(
1411
- handle =>
1412
- {
1413
- return _messageHandler.RegisterSourcedBroadcastWithoutSource(
1414
- broadcastHandler,
1415
- AugmentedHandler,
1416
- priority: priority,
1417
- messageBus: _messageBus
1418
- );
1419
-
1420
- void AugmentedHandler(ref InstanceId source, ref T message)
1421
- {
1422
- broadcastHandler(ref source, ref message);
1423
- if (_diagnosticMode)
1424
- {
1425
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1426
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1427
- }
1428
- }
1429
- },
1430
- () =>
1431
- new MessageRegistrationMetadata(
1432
- null,
1433
- typeof(T),
1434
- MessageRegistrationType.BroadcastWithoutSource,
1435
- priority
1436
- )
1201
+ new BroadcastRegistration<T>(
1202
+ this,
1203
+ RegistrationKind.BroadcastWithoutSourceFast,
1204
+ default,
1205
+ broadcastHandler,
1206
+ priority
1207
+ ),
1208
+ new MessageRegistrationMetadata(
1209
+ null,
1210
+ typeof(T),
1211
+ MessageRegistrationType.BroadcastWithoutSource,
1212
+ priority
1213
+ )
1437
1214
  );
1438
1215
  }
1439
1216
 
@@ -1464,32 +1241,19 @@ namespace DxMessaging.Core
1464
1241
  }
1465
1242
 
1466
1243
  return InternalRegister(
1467
- handle =>
1468
- {
1469
- return _messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
1470
- broadcastHandler,
1471
- AugmentedHandler,
1472
- priority: priority,
1473
- _messageBus
1474
- );
1475
-
1476
- void AugmentedHandler(InstanceId source, T message)
1477
- {
1478
- broadcastHandler(source, message);
1479
- if (_diagnosticMode)
1480
- {
1481
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1482
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1483
- }
1484
- }
1485
- },
1486
- () =>
1487
- new MessageRegistrationMetadata(
1488
- null,
1489
- typeof(T),
1490
- MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1491
- priority
1492
- )
1244
+ new BroadcastRegistration<T>(
1245
+ this,
1246
+ RegistrationKind.BroadcastWithoutSourcePostProcessorAction,
1247
+ default,
1248
+ broadcastHandler,
1249
+ priority
1250
+ ),
1251
+ new MessageRegistrationMetadata(
1252
+ null,
1253
+ typeof(T),
1254
+ MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1255
+ priority
1256
+ )
1493
1257
  );
1494
1258
  }
1495
1259
 
@@ -1515,32 +1279,19 @@ namespace DxMessaging.Core
1515
1279
  }
1516
1280
 
1517
1281
  return InternalRegister(
1518
- handle =>
1519
- {
1520
- return _messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
1521
- broadcastHandler,
1522
- AugmentedHandler,
1523
- priority: priority,
1524
- _messageBus
1525
- );
1526
-
1527
- void AugmentedHandler(ref InstanceId source, ref T message)
1528
- {
1529
- broadcastHandler(ref source, ref message);
1530
- if (_diagnosticMode)
1531
- {
1532
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1533
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1534
- }
1535
- }
1536
- },
1537
- () =>
1538
- new MessageRegistrationMetadata(
1539
- null,
1540
- typeof(T),
1541
- MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1542
- priority
1543
- )
1282
+ new BroadcastRegistration<T>(
1283
+ this,
1284
+ RegistrationKind.BroadcastWithoutSourcePostProcessorFast,
1285
+ default,
1286
+ broadcastHandler,
1287
+ priority
1288
+ ),
1289
+ new MessageRegistrationMetadata(
1290
+ null,
1291
+ typeof(T),
1292
+ MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1293
+ priority
1294
+ )
1544
1295
  );
1545
1296
  }
1546
1297
 
@@ -1565,55 +1316,18 @@ namespace DxMessaging.Core
1565
1316
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1566
1317
  }
1567
1318
  return InternalRegister(
1568
- handle =>
1569
- {
1570
- return _messageHandler.RegisterGlobalAcceptAll(
1571
- acceptAllUntargeted,
1572
- AugmentedUntargeted,
1573
- acceptAllTargeted,
1574
- AugmentedTargeted,
1575
- acceptAllBroadcast,
1576
- AugmentedBroadcast,
1577
- _messageBus
1578
- );
1579
-
1580
- void AugmentedUntargeted(IUntargetedMessage message)
1581
- {
1582
- acceptAllUntargeted(message);
1583
- if (_diagnosticMode)
1584
- {
1585
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1586
- _emissionBuffer.Add(new MessageEmissionData(message));
1587
- }
1588
- }
1589
-
1590
- void AugmentedTargeted(InstanceId target, ITargetedMessage message)
1591
- {
1592
- acceptAllTargeted(target, message);
1593
- if (_diagnosticMode)
1594
- {
1595
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1596
- _emissionBuffer.Add(new MessageEmissionData(message, target));
1597
- }
1598
- }
1599
-
1600
- void AugmentedBroadcast(InstanceId source, IBroadcastMessage message)
1601
- {
1602
- acceptAllBroadcast(source, message);
1603
- if (_diagnosticMode)
1604
- {
1605
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1606
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1607
- }
1608
- }
1609
- },
1610
- () =>
1611
- new MessageRegistrationMetadata(
1612
- null,
1613
- typeof(IMessage),
1614
- MessageRegistrationType.GlobalAcceptAll,
1615
- 0
1616
- )
1319
+ new GlobalAcceptAllRegistration(
1320
+ this,
1321
+ acceptAllUntargeted,
1322
+ acceptAllTargeted,
1323
+ acceptAllBroadcast
1324
+ ),
1325
+ new MessageRegistrationMetadata(
1326
+ null,
1327
+ typeof(IMessage),
1328
+ MessageRegistrationType.GlobalAcceptAll,
1329
+ 0
1330
+ )
1617
1331
  );
1618
1332
  }
1619
1333
 
@@ -1647,55 +1361,18 @@ namespace DxMessaging.Core
1647
1361
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1648
1362
  }
1649
1363
  return InternalRegister(
1650
- handle =>
1651
- {
1652
- return _messageHandler.RegisterGlobalAcceptAll(
1653
- acceptAllUntargeted,
1654
- AugmentedUntargeted,
1655
- acceptAllTargeted,
1656
- AugmentedTargeted,
1657
- acceptAllBroadcast,
1658
- AugmentedBroadcast,
1659
- _messageBus
1660
- );
1661
-
1662
- void AugmentedUntargeted(ref IUntargetedMessage message)
1663
- {
1664
- acceptAllUntargeted(ref message);
1665
- if (_diagnosticMode)
1666
- {
1667
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1668
- _emissionBuffer.Add(new MessageEmissionData(message));
1669
- }
1670
- }
1671
-
1672
- void AugmentedTargeted(ref InstanceId target, ref ITargetedMessage message)
1673
- {
1674
- acceptAllTargeted(ref target, ref message);
1675
- if (_diagnosticMode)
1676
- {
1677
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1678
- _emissionBuffer.Add(new MessageEmissionData(message, target));
1679
- }
1680
- }
1681
-
1682
- void AugmentedBroadcast(ref InstanceId source, ref IBroadcastMessage message)
1683
- {
1684
- acceptAllBroadcast(ref source, ref message);
1685
- if (_diagnosticMode)
1686
- {
1687
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1688
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1689
- }
1690
- }
1691
- },
1692
- () =>
1693
- new MessageRegistrationMetadata(
1694
- null,
1695
- typeof(IMessage),
1696
- MessageRegistrationType.GlobalAcceptAll,
1697
- 0
1698
- )
1364
+ new GlobalAcceptAllRegistration(
1365
+ this,
1366
+ acceptAllUntargeted,
1367
+ acceptAllTargeted,
1368
+ acceptAllBroadcast
1369
+ ),
1370
+ new MessageRegistrationMetadata(
1371
+ null,
1372
+ typeof(IMessage),
1373
+ MessageRegistrationType.GlobalAcceptAll,
1374
+ 0
1375
+ )
1699
1376
  );
1700
1377
  }
1701
1378
 
@@ -1718,14 +1395,18 @@ namespace DxMessaging.Core
1718
1395
  }
1719
1396
 
1720
1397
  return InternalRegister(
1721
- _ => _messageHandler.RegisterUntargetedInterceptor(interceptor, priority),
1722
- () =>
1723
- new MessageRegistrationMetadata(
1724
- null,
1725
- typeof(T),
1726
- MessageRegistrationType.UntargetedInterceptor,
1727
- priority
1728
- )
1398
+ new UntargetedRegistration<T>(
1399
+ this,
1400
+ RegistrationKind.UntargetedInterceptor,
1401
+ interceptor,
1402
+ priority
1403
+ ),
1404
+ new MessageRegistrationMetadata(
1405
+ null,
1406
+ typeof(T),
1407
+ MessageRegistrationType.UntargetedInterceptor,
1408
+ priority
1409
+ )
1729
1410
  );
1730
1411
  }
1731
1412
 
@@ -1748,14 +1429,19 @@ namespace DxMessaging.Core
1748
1429
  }
1749
1430
 
1750
1431
  return InternalRegister(
1751
- _ => _messageHandler.RegisterBroadcastInterceptor(interceptor, priority),
1752
- () =>
1753
- new MessageRegistrationMetadata(
1754
- null,
1755
- typeof(T),
1756
- MessageRegistrationType.BroadcastInterceptor,
1757
- priority
1758
- )
1432
+ new BroadcastRegistration<T>(
1433
+ this,
1434
+ RegistrationKind.BroadcastInterceptor,
1435
+ default,
1436
+ interceptor,
1437
+ priority
1438
+ ),
1439
+ new MessageRegistrationMetadata(
1440
+ null,
1441
+ typeof(T),
1442
+ MessageRegistrationType.BroadcastInterceptor,
1443
+ priority
1444
+ )
1759
1445
  );
1760
1446
  }
1761
1447
 
@@ -1778,48 +1464,61 @@ namespace DxMessaging.Core
1778
1464
  }
1779
1465
 
1780
1466
  return InternalRegister(
1781
- _ => _messageHandler.RegisterTargetedInterceptor(interceptor, priority),
1782
- () =>
1783
- new MessageRegistrationMetadata(
1784
- null,
1785
- typeof(T),
1786
- MessageRegistrationType.TargetedInterceptor,
1787
- priority
1788
- )
1467
+ new TargetedRegistration<T>(
1468
+ this,
1469
+ RegistrationKind.TargetedInterceptor,
1470
+ default,
1471
+ interceptor,
1472
+ priority
1473
+ ),
1474
+ new MessageRegistrationMetadata(
1475
+ null,
1476
+ typeof(T),
1477
+ MessageRegistrationType.TargetedInterceptor,
1478
+ priority
1479
+ )
1789
1480
  );
1790
1481
  }
1791
1482
 
1792
1483
  /// <summary>
1793
1484
  /// Handles the actual [de]registration wrapping and (potential) lazy execution.
1794
1485
  /// </summary>
1795
- /// <param name="registerAndGetDeregistration">Proxied registration function that returns a de-registration function.</param>
1796
- /// <param name="metadataProducer">Opaque metadata producer function.</param>
1486
+ /// <param name="registration">The unified per-handle registration object the public Register* method built. Calling <see cref="Registration.Register"/> (re)registers the handler on the bus and returns the matching de-registration.</param>
1487
+ /// <param name="metadata">Registration metadata recorded for the diagnostics inspector overlay and the registration-count warning.</param>
1797
1488
  /// <returns>A handle that allows for registration and de-registration.</returns>
1798
1489
  private MessageRegistrationHandle InternalRegister(
1799
- Func<MessageRegistrationHandle, Action> registerAndGetDeregistration,
1800
- Func<MessageRegistrationMetadata> metadataProducer
1490
+ Registration registration,
1491
+ MessageRegistrationMetadata metadata
1801
1492
  )
1802
1493
  {
1803
1494
  MessageRegistrationHandle handle =
1804
1495
  MessageRegistrationHandle.CreateMessageRegistrationHandle();
1805
1496
 
1806
- _registrations[handle] = Registration;
1807
- _metadata[handle] = metadataProducer();
1808
-
1809
- // Generally, registrations should take place before all calls to enable. Just in case, though...
1497
+ registration.Handle = handle;
1498
+ _registrations[handle] = registration;
1499
+ _registrationOrder.Add(handle);
1500
+ // Metadata is passed by value (a readonly struct) instead of through a
1501
+ // Func<MessageRegistrationMetadata> factory: the factory was invoked
1502
+ // immediately and unconditionally here, so it never deferred any work --
1503
+ // it only cost one delegate (plus its display class) allocation per
1504
+ // registration. Constructing the struct at the call site is identical
1505
+ // work without that closure. _metadata stays populated for every
1506
+ // registration because the inspector overlay and the registration-count
1507
+ // warning read it regardless of diagnostics mode.
1508
+ _metadata[handle] = metadata;
1509
+
1510
+ // Generally, registrations should take place before all calls to enable.
1511
+ // Just in case, though, register immediately if already enabled. We do not
1512
+ // register at staging time when disabled (the owner might not be awake), so
1513
+ // the Registration object is retained in _registrations to lazily
1514
+ // (re)register on Enable().
1810
1515
  if (_enabled)
1811
1516
  {
1812
- Registration();
1517
+ MessageHandler.HandlerDeregistration actualDeregistration = registration.Register();
1518
+ AddDeregistration(handle, actualDeregistration);
1813
1519
  }
1814
1520
 
1815
1521
  return handle;
1816
-
1817
- // We don't want to actually register at this time (might not be awake/enabled) - so we wrap that shit up, to lazy register when we're enabled.
1818
- void Registration()
1819
- {
1820
- Action actualDeregistration = registerAndGetDeregistration(handle);
1821
- _deregistrations[handle] = actualDeregistration;
1822
- }
1823
1522
  }
1824
1523
 
1825
1524
  /// <summary>
@@ -1843,12 +1542,15 @@ namespace DxMessaging.Core
1843
1542
 
1844
1543
  if (_registrations is { Count: > 0 })
1845
1544
  {
1846
- _actionQueue.Clear();
1847
- _actionQueue.AddRange(_registrations.Values);
1848
- foreach (Action action in _actionQueue)
1849
- {
1850
- action();
1851
- }
1545
+ // Replay staged registrations in original registration order
1546
+ // (via _registrationOrder) rather than in
1547
+ // _registrations.Values enumeration order, which permutes
1548
+ // after Remove/Add churn. This preserves the documented
1549
+ // equal-priority "registration order" dispatch contract across
1550
+ // Disable()/Enable() cycles. Snapshot into _registrationReplayQueue first
1551
+ // so replay tolerates re-entrant registration mutation.
1552
+ QueueRegistrationsInOrder();
1553
+ InvokeRegistrationQueueWithRollback();
1852
1554
  }
1853
1555
 
1854
1556
  _enabled = true;
@@ -1867,28 +1569,21 @@ namespace DxMessaging.Core
1867
1569
  /// </example>
1868
1570
  public void Disable()
1869
1571
  {
1870
- if (!_enabled)
1572
+ if (!_enabled && _deregistrations.Count == 0)
1871
1573
  {
1872
1574
  return;
1873
1575
  }
1874
1576
 
1875
- if (_deregistrations is { Count: > 0 })
1577
+ Exception deregistrationException = InvokeDeregistrationQueue();
1578
+ _enabled = _deregistrations.Count > 0;
1579
+ if (deregistrationException != null)
1876
1580
  {
1877
- _actionQueue.Clear();
1878
- _actionQueue.AddRange(_deregistrations.Values);
1879
- foreach (Action deregistration in _actionQueue)
1880
- {
1881
- deregistration?.Invoke();
1882
- }
1581
+ ExceptionDispatchInfo.Capture(deregistrationException).Throw();
1883
1582
  }
1884
-
1885
- // ReSharper disable once ForCanBeConvertedToForeach
1886
-
1887
- _enabled = false;
1888
1583
  }
1889
1584
 
1890
1585
  /// <summary>
1891
- /// Disables the token and clears all registrations and de-registrations.
1586
+ /// Disables the token and clears all registrations, de-registrations, and token-local diagnostic state.
1892
1587
  /// </summary>
1893
1588
  /// <example>
1894
1589
  /// <code>
@@ -1899,19 +1594,19 @@ namespace DxMessaging.Core
1899
1594
  /// </example>
1900
1595
  public void UnregisterAll()
1901
1596
  {
1902
- if (_deregistrations is { Count: > 0 })
1597
+ Exception deregistrationException = InvokeDeregistrationQueue();
1598
+ if (deregistrationException == null)
1903
1599
  {
1904
- _actionQueue.Clear();
1905
- _actionQueue.AddRange(_deregistrations.Values);
1906
- foreach (Action deregistration in _actionQueue)
1907
- {
1908
- deregistration?.Invoke();
1909
- }
1600
+ _enabled = false;
1601
+ _registrations?.Clear();
1602
+ _registrationOrder?.Clear();
1603
+ ClearDiagnosticState();
1604
+ return;
1910
1605
  }
1911
1606
 
1912
- _enabled = false;
1913
- _registrations?.Clear();
1914
- _deregistrations?.Clear();
1607
+ _enabled = _deregistrations.Count > 0;
1608
+ PruneRegistrationStateToFailedDeregistrations();
1609
+ ExceptionDispatchInfo.Capture(deregistrationException).Throw();
1915
1610
  }
1916
1611
 
1917
1612
  /// <summary>
@@ -1938,52 +1633,1212 @@ namespace DxMessaging.Core
1938
1633
  return;
1939
1634
  }
1940
1635
 
1636
+ IMessageBus previousMessageBus = _messageBus;
1637
+ List<MessageRegistrationHandle> activeRetargetHandles = rebindActiveRegistrations
1638
+ ? new List<MessageRegistrationHandle>(_deregistrations.Keys)
1639
+ : null;
1941
1640
  if (rebindActiveRegistrations)
1942
1641
  {
1943
- _actionQueue.Clear();
1944
- _actionQueue.AddRange(_deregistrations.Values);
1945
- foreach (Action deregistration in _actionQueue)
1642
+ Exception deregistrationException = InvokeDeregistrationQueue();
1643
+ if (deregistrationException != null)
1946
1644
  {
1947
- deregistration?.Invoke();
1645
+ RestoreMissingRegistrationsAfterRetargetDeregistrationFailure(
1646
+ previousMessageBus,
1647
+ activeRetargetHandles
1648
+ );
1649
+ _enabled = _deregistrations.Count > 0;
1650
+ ExceptionDispatchInfo.Capture(deregistrationException).Throw();
1948
1651
  }
1652
+
1653
+ _enabled = false;
1949
1654
  }
1950
1655
 
1951
1656
  _messageBus = messageBus;
1952
1657
 
1953
1658
  if (rebindActiveRegistrations && _registrations is { Count: > 0 })
1954
1659
  {
1955
- _actionQueue.Clear();
1956
- _actionQueue.AddRange(_registrations.Values);
1957
- foreach (Action registration in _actionQueue)
1660
+ // Mirror Enable(): rebind in original registration order so the
1661
+ // equal-priority dispatch order survives a bus retarget.
1662
+ QueueRegistrationsInOrder();
1663
+ try
1664
+ {
1665
+ InvokeRegistrationQueueWithRollback();
1666
+ _enabled = true;
1667
+ }
1668
+ catch (Exception exception)
1958
1669
  {
1959
- registration?.Invoke();
1670
+ RestoreRegistrationsAfterRetargetFailure(previousMessageBus);
1671
+ ExceptionDispatchInfo.Capture(exception).Throw();
1672
+ throw;
1960
1673
  }
1961
1674
  }
1962
1675
  }
1963
1676
 
1964
- /// <summary>
1965
- /// Removes a single staged registration by handle.
1966
- /// </summary>
1967
- /// <param name="handle">Handle returned from a Register* method.</param>
1968
- /// <example>
1969
- /// <code>
1970
- /// var h = token.RegisterUntargeted&lt;SceneLoaded&gt;(OnScene);
1971
- /// token.RemoveRegistration(h); // de-register just this one
1972
- /// </code>
1973
- /// </example>
1974
- public void RemoveRegistration(MessageRegistrationHandle handle)
1677
+ private void AddDeregistration(
1678
+ MessageRegistrationHandle handle,
1679
+ MessageHandler.HandlerDeregistration deregistration
1680
+ )
1975
1681
  {
1976
- if (_deregistrations?.Remove(handle, out Action deregistrationAction) == true)
1682
+ // First (and usually only) de-registration: store the object inline in ONE dictionary
1683
+ // operation. TryAdd succeeds on the common first-registration path without the prior
1684
+ // separate TryGetValue-miss + indexer-insert (two probes of the same key); the rare
1685
+ // second-de-registration promotion path below re-reads the existing value.
1686
+ if (_deregistrations.TryAdd(handle, deregistration))
1977
1687
  {
1978
- deregistrationAction?.Invoke();
1688
+ return;
1979
1689
  }
1980
1690
 
1981
- // Drop the matching staged registration and metadata so a later
1982
- // Disable()/Enable() cycle does not silently re-register the
1983
- // handler we were just asked to remove.
1984
- _ = _registrations?.Remove(handle);
1985
- _ = _metadata?.Remove(handle);
1986
- _ = _callCounts?.Remove(handle);
1691
+ object existing = _deregistrations[handle];
1692
+ if (existing is PendingDeregistration pending)
1693
+ {
1694
+ pending.Add(deregistration);
1695
+ return;
1696
+ }
1697
+
1698
+ // A second de-registration accumulated on this handle: promote the inline object to a
1699
+ // PendingDeregistration holder that preserves the ordering / partial-failure / rollback
1700
+ // semantics for the multi-de-registration (retarget-recovery replay) case.
1701
+ PendingDeregistration promoted = new();
1702
+ promoted.Add((MessageHandler.HandlerDeregistration)existing);
1703
+ promoted.Add(deregistration);
1704
+ _deregistrations[handle] = promoted;
1705
+ }
1706
+
1707
+ // Logical de-registration count for a _deregistrations value (inline object == 1).
1708
+ private static int DeregistrationCount(object value) =>
1709
+ value is PendingDeregistration pending ? pending.Count : 1;
1710
+
1711
+ // Invokes the de-registration tail [startIndex..) for a _deregistrations value. For the
1712
+ // inline object (logical Count 1, index 0): on success it is consumed (shouldRemove = true);
1713
+ // on throw it is KEPT (retryable, shouldRemove = false); a rollback pass (startIndex &gt; 0)
1714
+ // leaves the baseline entry untouched. For a holder it delegates to InvokeFrom, mutating the
1715
+ // holder in place. Mirrors the prior PendingDeregistration-only semantics exactly.
1716
+ private static Exception InvokeDeregistration(
1717
+ object value,
1718
+ int startIndex,
1719
+ out bool shouldRemove
1720
+ )
1721
+ {
1722
+ if (value is PendingDeregistration pending)
1723
+ {
1724
+ Exception holderException = pending.InvokeFrom(startIndex);
1725
+ shouldRemove = pending.Count == 0;
1726
+ return holderException;
1727
+ }
1728
+
1729
+ if (startIndex > 0)
1730
+ {
1731
+ // Rollback baseline pass: the inline head (logical index 0) is below the requested
1732
+ // tail, so leave it untouched.
1733
+ shouldRemove = false;
1734
+ return null;
1735
+ }
1736
+
1737
+ try
1738
+ {
1739
+ ((MessageHandler.HandlerDeregistration)value)?.Deregister();
1740
+ shouldRemove = true;
1741
+ return null;
1742
+ }
1743
+ catch (Exception exception)
1744
+ {
1745
+ // Keep the failed de-registration (retryable), exactly as the holder form does.
1746
+ shouldRemove = false;
1747
+ return exception;
1748
+ }
1749
+ }
1750
+
1751
+ private Dictionary<MessageRegistrationHandle, int> SnapshotDeregistrationCounts()
1752
+ {
1753
+ Dictionary<MessageRegistrationHandle, int> snapshot = new(_deregistrations.Count);
1754
+ foreach (KeyValuePair<MessageRegistrationHandle, object> entry in _deregistrations)
1755
+ {
1756
+ snapshot[entry.Key] = DeregistrationCount(entry.Value);
1757
+ }
1758
+
1759
+ return snapshot;
1760
+ }
1761
+
1762
+ private void RestoreMissingRegistrationsAfterRetargetDeregistrationFailure(
1763
+ IMessageBus previousMessageBus,
1764
+ List<MessageRegistrationHandle> activeRetargetHandles
1765
+ )
1766
+ {
1767
+ _messageBus = previousMessageBus;
1768
+ if (activeRetargetHandles == null || activeRetargetHandles.Count == 0)
1769
+ {
1770
+ return;
1771
+ }
1772
+
1773
+ QueueMissingRegistrationsInOrder(activeRetargetHandles);
1774
+ try
1775
+ {
1776
+ InvokeRegistrationQueueWithRollback();
1777
+ }
1778
+ catch (Exception exception)
1779
+ {
1780
+ if (MessagingDebug.enabled)
1781
+ {
1782
+ MessagingDebug.Log(
1783
+ LogLevel.Error,
1784
+ "Failed to restore registrations after retarget deregistration failure: {0}",
1785
+ exception
1786
+ );
1787
+ }
1788
+ }
1789
+ }
1790
+
1791
+ private void RestoreRegistrationsAfterRetargetFailure(IMessageBus previousMessageBus)
1792
+ {
1793
+ _messageBus = previousMessageBus;
1794
+ if (_registrations.Count == 0)
1795
+ {
1796
+ _enabled = false;
1797
+ return;
1798
+ }
1799
+
1800
+ QueueRegistrationsWithoutRetryableDeregistrationsInOrder();
1801
+ try
1802
+ {
1803
+ InvokeRegistrationQueueWithRollback();
1804
+ _enabled = true;
1805
+ }
1806
+ catch (Exception restoreException)
1807
+ {
1808
+ _enabled = _deregistrations.Count > 0;
1809
+ if (MessagingDebug.enabled)
1810
+ {
1811
+ MessagingDebug.Log(
1812
+ LogLevel.Error,
1813
+ "Failed to restore registrations after retarget replay failure: {0}",
1814
+ restoreException
1815
+ );
1816
+ }
1817
+ }
1818
+ }
1819
+
1820
+ private void QueueRegistrationsInOrder()
1821
+ {
1822
+ _registrationReplayQueue.Clear();
1823
+ int registrationCount = _registrationOrder.Count;
1824
+ for (int i = 0; i < registrationCount; ++i)
1825
+ {
1826
+ MessageRegistrationHandle handle = _registrationOrder[i];
1827
+ if (_registrations.TryGetValue(handle, out Registration registration))
1828
+ {
1829
+ _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
1830
+ }
1831
+ }
1832
+ }
1833
+
1834
+ private void QueueMissingRegistrationsInOrder(
1835
+ List<MessageRegistrationHandle> activeRetargetHandles
1836
+ )
1837
+ {
1838
+ _registrationReplayQueue.Clear();
1839
+ int registrationCount = _registrationOrder.Count;
1840
+ for (int i = 0; i < registrationCount; ++i)
1841
+ {
1842
+ MessageRegistrationHandle handle = _registrationOrder[i];
1843
+ if (
1844
+ !activeRetargetHandles.Contains(handle)
1845
+ || _deregistrations.ContainsKey(handle)
1846
+ || !_registrations.TryGetValue(handle, out Registration registration)
1847
+ )
1848
+ {
1849
+ continue;
1850
+ }
1851
+
1852
+ _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
1853
+ }
1854
+ }
1855
+
1856
+ private void QueueRegistrationsWithoutRetryableDeregistrationsInOrder()
1857
+ {
1858
+ _registrationReplayQueue.Clear();
1859
+ int registrationCount = _registrationOrder.Count;
1860
+ for (int i = 0; i < registrationCount; ++i)
1861
+ {
1862
+ MessageRegistrationHandle handle = _registrationOrder[i];
1863
+ if (
1864
+ _deregistrations.ContainsKey(handle)
1865
+ || !_registrations.TryGetValue(handle, out Registration registration)
1866
+ )
1867
+ {
1868
+ continue;
1869
+ }
1870
+
1871
+ _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
1872
+ }
1873
+ }
1874
+
1875
+ private void InvokeRegistrationReplayQueue()
1876
+ {
1877
+ try
1878
+ {
1879
+ foreach (StagedRegistration staged in _registrationReplayQueue)
1880
+ {
1881
+ Registration registration = staged.Registration;
1882
+ if (registration == null)
1883
+ {
1884
+ continue;
1885
+ }
1886
+
1887
+ MessageHandler.HandlerDeregistration actualDeregistration =
1888
+ registration.Register();
1889
+ AddDeregistration(staged.Handle, actualDeregistration);
1890
+ }
1891
+ }
1892
+ finally
1893
+ {
1894
+ _registrationReplayQueue.Clear();
1895
+ }
1896
+ }
1897
+
1898
+ /// <summary>
1899
+ /// A staged registration captured for replay: the handle plus the unified
1900
+ /// per-handle <see cref="Registration"/> object that (re)registers the handler
1901
+ /// and returns its <see cref="MessageHandler.HandlerDeregistration"/>.
1902
+ /// Snapshotting the object reference (rather than a per-registration wrapper
1903
+ /// closure) preserves the original re-entrancy semantics while keeping the
1904
+ /// staging state in a single object per registration.
1905
+ /// </summary>
1906
+ private readonly struct StagedRegistration
1907
+ {
1908
+ public readonly MessageRegistrationHandle Handle;
1909
+ public readonly Registration Registration;
1910
+
1911
+ public StagedRegistration(MessageRegistrationHandle handle, Registration registration)
1912
+ {
1913
+ Handle = handle;
1914
+ Registration = registration;
1915
+ }
1916
+ }
1917
+
1918
+ private Exception InvokeDeregistrationQueue(
1919
+ Dictionary<MessageRegistrationHandle, int> baselineCounts = null
1920
+ )
1921
+ {
1922
+ if (_deregistrations.Count == 0)
1923
+ {
1924
+ return null;
1925
+ }
1926
+
1927
+ bool scopedToAddedDeregistrations = baselineCounts != null;
1928
+ _handleQueue.Clear();
1929
+ _handleQueue.AddRange(_deregistrations.Keys);
1930
+ Exception firstException = null;
1931
+ try
1932
+ {
1933
+ foreach (MessageRegistrationHandle handle in _handleQueue)
1934
+ {
1935
+ if (!_deregistrations.TryGetValue(handle, out object value))
1936
+ {
1937
+ continue;
1938
+ }
1939
+
1940
+ int startIndex = 0;
1941
+ if (scopedToAddedDeregistrations)
1942
+ {
1943
+ if (baselineCounts.TryGetValue(handle, out int baselineCount))
1944
+ {
1945
+ startIndex = baselineCount;
1946
+ }
1947
+
1948
+ if (startIndex >= DeregistrationCount(value))
1949
+ {
1950
+ continue;
1951
+ }
1952
+ }
1953
+
1954
+ Exception exception = InvokeDeregistration(
1955
+ value,
1956
+ startIndex,
1957
+ out bool shouldRemove
1958
+ );
1959
+ if (exception != null)
1960
+ {
1961
+ firstException ??= exception;
1962
+ }
1963
+
1964
+ if (shouldRemove)
1965
+ {
1966
+ _deregistrations.Remove(handle);
1967
+ }
1968
+ }
1969
+ }
1970
+ finally
1971
+ {
1972
+ _handleQueue.Clear();
1973
+ }
1974
+
1975
+ return firstException;
1976
+ }
1977
+
1978
+ private void InvokeRegistrationQueueWithRollback()
1979
+ {
1980
+ Dictionary<MessageRegistrationHandle, int> rollbackBaseline =
1981
+ SnapshotDeregistrationCounts();
1982
+ try
1983
+ {
1984
+ InvokeRegistrationReplayQueue();
1985
+ }
1986
+ catch (Exception exception)
1987
+ {
1988
+ RollBackDeregistrationsAfterRegistrationFailure(rollbackBaseline);
1989
+ _enabled = _deregistrations.Count > 0;
1990
+ ExceptionDispatchInfo.Capture(exception).Throw();
1991
+ throw;
1992
+ }
1993
+ }
1994
+
1995
+ private void RollBackDeregistrationsAfterRegistrationFailure(
1996
+ Dictionary<MessageRegistrationHandle, int> rollbackBaseline
1997
+ )
1998
+ {
1999
+ if (_deregistrations.Count == 0)
2000
+ {
2001
+ return;
2002
+ }
2003
+
2004
+ Exception rollbackException = InvokeDeregistrationQueue(rollbackBaseline);
2005
+ if (rollbackException != null && MessagingDebug.enabled)
2006
+ {
2007
+ MessagingDebug.Log(
2008
+ LogLevel.Error,
2009
+ "Failed to roll back partial registration after token replay failure: {0}",
2010
+ rollbackException
2011
+ );
2012
+ }
2013
+ }
2014
+
2015
+ private void ClearDiagnosticState()
2016
+ {
2017
+ _metadata.Clear();
2018
+ // Clear through the backing fields so an inactive (never-materialized)
2019
+ // diagnostics collection is not allocated merely to be emptied.
2020
+ _callCountsBacking?.Clear();
2021
+ _emissionBufferBacking?.Clear();
2022
+ }
2023
+
2024
+ private void PruneRegistrationStateToFailedDeregistrations()
2025
+ {
2026
+ for (int i = _registrationOrder.Count - 1; i >= 0; --i)
2027
+ {
2028
+ MessageRegistrationHandle handle = _registrationOrder[i];
2029
+ if (_deregistrations.ContainsKey(handle))
2030
+ {
2031
+ continue;
2032
+ }
2033
+
2034
+ RemoveRegistrationState(handle);
2035
+ }
2036
+
2037
+ _emissionBufferBacking?.Clear();
2038
+ if (_registrations.Count == 0)
2039
+ {
2040
+ ClearDiagnosticState();
2041
+ }
2042
+ }
2043
+
2044
+ private bool RemoveRegistrationState(MessageRegistrationHandle handle)
2045
+ {
2046
+ bool removedRegistration = _registrations.Remove(handle);
2047
+ _ = _registrationOrder.Remove(handle);
2048
+ _ = _metadata.Remove(handle);
2049
+ _callCountsBacking?.Remove(handle);
2050
+ if (removedRegistration && _registrations.Count == 0)
2051
+ {
2052
+ ClearDiagnosticState();
2053
+ }
2054
+
2055
+ return removedRegistration;
2056
+ }
2057
+
2058
+ /// <summary>
2059
+ /// Removes a single staged registration by handle.
2060
+ /// </summary>
2061
+ /// <param name="handle">Handle returned from a Register* method.</param>
2062
+ /// <example>
2063
+ /// <code>
2064
+ /// var h = token.RegisterUntargeted&lt;SceneLoaded&gt;(OnScene);
2065
+ /// token.RemoveRegistration(h); // de-register just this one
2066
+ /// </code>
2067
+ /// </example>
2068
+ public void RemoveRegistration(MessageRegistrationHandle handle)
2069
+ {
2070
+ if (_deregistrations.TryGetValue(handle, out object value))
2071
+ {
2072
+ Exception deregistrationException = InvokeDeregistration(
2073
+ value,
2074
+ 0,
2075
+ out bool shouldRemove
2076
+ );
2077
+ if (shouldRemove)
2078
+ {
2079
+ _deregistrations.Remove(handle);
2080
+ }
2081
+
2082
+ if (deregistrationException != null)
2083
+ {
2084
+ ExceptionDispatchInfo.Capture(deregistrationException).Throw();
2085
+ }
2086
+ }
2087
+
2088
+ // Drop the matching staged registration and metadata so a later
2089
+ // Disable()/Enable() cycle does not silently re-register the
2090
+ // handler we were just asked to remove.
2091
+ RemoveRegistrationState(handle);
2092
+ }
2093
+
2094
+ // Holds the live de-registration Actions for a single handle in the MULTI-de-registration
2095
+ // case (2+). The common case is EXACTLY ONE de-registration per handle (a staging function
2096
+ // registers once and returns one de-registration Action); that single Action is stored
2097
+ // INLINE as the _deregistrations dictionary value, so the common path allocates no
2098
+ // PendingDeregistration object at all (see AddDeregistration / InvokeDeregistration). This
2099
+ // holder is allocated only when a rare second de-registration accumulates on the same
2100
+ // handle -- a re-entrant retarget-recovery replay can stage one beyond the rollback
2101
+ // baseline -- at which point the inline Action is promoted into this holder's inline head
2102
+ // and the second spills to a lazily-allocated overflow list. This stays a class (mutated in
2103
+ // place through the _deregistrations dictionary), so reference semantics and every
2104
+ // call site are unchanged; only the storage shape changed.
2105
+ //
2106
+ // Invariant: when Count > 0 the head lives in _hasHead/_head and is the LOGICAL
2107
+ // FIRST entry; any further entries follow in _overflow in insertion order. Add
2108
+ // appends to the logical tail; InvokeFrom invokes a contiguous logical tail
2109
+ // [startIndex..Count), removing each success and KEEPING each failure (retryable),
2110
+ // then promotes the first surviving overflow entry into the head slot if the head
2111
+ // was consumed -- preserving the exact ordering, partial-failure, and
2112
+ // rollback-baseline (startIndex) semantics the List<Action> form had.
2113
+ private sealed class PendingDeregistration
2114
+ {
2115
+ private MessageHandler.HandlerDeregistration _head;
2116
+ private bool _hasHead;
2117
+ private List<MessageHandler.HandlerDeregistration> _overflow;
2118
+
2119
+ internal int Count => (_hasHead ? 1 : 0) + (_overflow?.Count ?? 0);
2120
+
2121
+ internal void Add(MessageHandler.HandlerDeregistration action)
2122
+ {
2123
+ // Fill the inline head ONLY when nothing is stored. The empty-overflow
2124
+ // clause matters during the transient window inside InvokeFrom where the
2125
+ // head has been consumed but overflow survivors remain (before they are
2126
+ // promoted): a re-entrant Add then appends to the logical TAIL (overflow),
2127
+ // exactly as the List form did, rather than jumping the new entry ahead of
2128
+ // the survivors into the head slot. The overflow loop re-reads its Count,
2129
+ // so such a tail-appended entry is still invoked in the same pass -- the
2130
+ // List form's behavior preserved. (The stored Actions are pure bus
2131
+ // de-registration callbacks, so this re-entrancy is not reachable through
2132
+ // the public API today; the guard keeps the invariant honest regardless.)
2133
+ if (!_hasHead && (_overflow == null || _overflow.Count == 0))
2134
+ {
2135
+ _head = action;
2136
+ _hasHead = true;
2137
+ return;
2138
+ }
2139
+
2140
+ (_overflow ??= new List<MessageHandler.HandlerDeregistration>()).Add(action);
2141
+ }
2142
+
2143
+ internal Exception InvokeFrom(int startIndex)
2144
+ {
2145
+ if (startIndex < 0)
2146
+ {
2147
+ startIndex = 0;
2148
+ }
2149
+
2150
+ Exception firstException = null;
2151
+
2152
+ // Logical index 0 is the inline head; logical indices 1.. are _overflow.
2153
+ // Invoke the head only when it is within the requested tail (a rollback
2154
+ // pass with startIndex > 0 must leave the baseline head untouched).
2155
+ if (_hasHead && startIndex <= 0)
2156
+ {
2157
+ try
2158
+ {
2159
+ _head?.Deregister();
2160
+ _head = null;
2161
+ _hasHead = false;
2162
+ }
2163
+ catch (Exception exception)
2164
+ {
2165
+ // Keep the failed head (retryable), exactly as the List form did
2166
+ // by advancing past a throwing entry instead of removing it.
2167
+ firstException ??= exception;
2168
+ }
2169
+ }
2170
+
2171
+ if (_overflow is { Count: > 0 })
2172
+ {
2173
+ // Overflow entry j has logical index 1 + j; invoke those at or past
2174
+ // startIndex. Remove successes, keep failures (retryable).
2175
+ int overflowStart = startIndex <= 1 ? 0 : startIndex - 1;
2176
+ for (int j = overflowStart; j < _overflow.Count; )
2177
+ {
2178
+ try
2179
+ {
2180
+ _overflow[j]?.Deregister();
2181
+ _overflow.RemoveAt(j);
2182
+ }
2183
+ catch (Exception exception)
2184
+ {
2185
+ firstException ??= exception;
2186
+ ++j;
2187
+ }
2188
+ }
2189
+ }
2190
+
2191
+ // If the head was consumed but overflow survivors remain, promote the
2192
+ // first survivor into the head slot so the head is always the logical
2193
+ // first entry (keeping Count and future Adds consistent).
2194
+ if (!_hasHead && _overflow is { Count: > 0 })
2195
+ {
2196
+ _head = _overflow[0];
2197
+ _hasHead = true;
2198
+ _overflow.RemoveAt(0);
2199
+ }
2200
+
2201
+ return firstException;
2202
+ }
2203
+ }
2204
+
2205
+ // Discriminates the per-handle Registration object's behaviour. Each value
2206
+ // pins (a) which MessageHandler.Register* method the kind-switch in
2207
+ // Registration.Register() calls and (b) which augmented-invoker body shape
2208
+ // (user-delegate type + by-value vs by-ref call) the bound FastHandler/
2209
+ // FastHandlerWithContext delegate runs. The values map 1:1 onto the former
2210
+ // per-method staging lambdas; the *Action / *Fast suffix mirrors the two
2211
+ // public overloads (Action<T>/Action<InstanceId,T> vs FastHandler<T>/
2212
+ // FastHandlerWithContext<T>) that previously had distinct AugmentedHandler
2213
+ // local functions.
2214
+ private enum RegistrationKind
2215
+ {
2216
+ TargetedHandlerAction,
2217
+ TargetedHandlerFast,
2218
+ TargetedPostProcessorAction,
2219
+ TargetedPostProcessorFast,
2220
+ TargetedWithoutTargetingAction,
2221
+ TargetedWithoutTargetingFast,
2222
+ TargetedWithoutTargetingPostProcessorAction,
2223
+ TargetedWithoutTargetingPostProcessorFast,
2224
+ TargetedInterceptor,
2225
+
2226
+ UntargetedHandlerAction,
2227
+ UntargetedHandlerFast,
2228
+ UntargetedPostProcessorFast,
2229
+ UntargetedInterceptor,
2230
+
2231
+ BroadcastHandlerAction,
2232
+ BroadcastHandlerFast,
2233
+ BroadcastPostProcessorAction,
2234
+ BroadcastPostProcessorFast,
2235
+ BroadcastWithoutSourceAction,
2236
+ BroadcastWithoutSourceFast,
2237
+ BroadcastWithoutSourcePostProcessorAction,
2238
+ BroadcastWithoutSourcePostProcessorFast,
2239
+ BroadcastInterceptor,
2240
+
2241
+ GlobalAcceptAllAction,
2242
+ GlobalAcceptAllFast,
2243
+ }
2244
+
2245
+ // The unified per-handle staging object. Replaces, per registration, the old
2246
+ // staging Func<handle, HandlerDeregistration> display class + delegate AND the
2247
+ // nested AugmentedHandler local-function delegate. The captured staging state
2248
+ // (owning token, handle, user handler delegate, target/source InstanceId,
2249
+ // priority, kind) lives in plain fields; the diagnostics-augmented invoker is
2250
+ // an instance method bound to this object (so MessageHandler still receives a
2251
+ // delegate on the hot path -- no virtual/interface call per dispatch).
2252
+ //
2253
+ // The base is non-generic so _registrations / the replay queue can hold every
2254
+ // registration polymorphically. The constrained MessageHandler.Register*<T>
2255
+ // calls each require T : ITargetedMessage / IUntargetedMessage /
2256
+ // IBroadcastMessage, which a single Registration<T> (T : IMessage) cannot
2257
+ // satisfy; the three concrete subclasses below carry the matching constraint
2258
+ // and each run a kind-SWITCH over only the kinds in their family (per the
2259
+ // user-accepted "unified object, accept the kind-switch" design -- NOT ~14
2260
+ // subclasses). GlobalAcceptAllRegistration is non-generic (its sub-handlers
2261
+ // are the fixed IMessage facades).
2262
+ private abstract class Registration
2263
+ {
2264
+ protected readonly MessageRegistrationToken Token;
2265
+ public MessageRegistrationHandle Handle;
2266
+
2267
+ protected Registration(MessageRegistrationToken token)
2268
+ {
2269
+ Token = token;
2270
+ }
2271
+
2272
+ // Kind-switch: (re)register on the bus and return the matching
2273
+ // HandlerDeregistration, exactly as the former staging lambda did. Reads
2274
+ // the token's CURRENT _messageBus (so Enable()/RetargetMessageBus replay
2275
+ // binds to the active bus, unchanged from when _messageBus was captured by
2276
+ // the staging closure at call time -- the staging closure also read the
2277
+ // field, not a snapshot).
2278
+ public abstract MessageHandler.HandlerDeregistration Register();
2279
+ }
2280
+
2281
+ private sealed class TargetedRegistration<T> : Registration
2282
+ where T : ITargetedMessage
2283
+ {
2284
+ private readonly RegistrationKind _kind;
2285
+ private readonly InstanceId _context;
2286
+ private readonly object _userHandler;
2287
+ private readonly int _priority;
2288
+
2289
+ // Strongly-typed views of _userHandler, resolved ONCE at Register() time
2290
+ // (cold) so the per-dispatch augmented invoker calls a typed field directly
2291
+ // -- no per-dispatch castclass or kind-switch (which would add an O(handlers)
2292
+ // cost on the hot dispatch path). Exactly one is set per registration kind.
2293
+ private Action<T> _scalarAction;
2294
+ private MessageHandler.FastHandler<T> _scalarFast;
2295
+ private Action<InstanceId, T> _contextAction;
2296
+ private MessageHandler.FastHandlerWithContext<T> _contextFast;
2297
+
2298
+ internal TargetedRegistration(
2299
+ MessageRegistrationToken token,
2300
+ RegistrationKind kind,
2301
+ InstanceId context,
2302
+ object userHandler,
2303
+ int priority
2304
+ )
2305
+ : base(token)
2306
+ {
2307
+ _kind = kind;
2308
+ _context = context;
2309
+ _userHandler = userHandler;
2310
+ _priority = priority;
2311
+ }
2312
+
2313
+ public override MessageHandler.HandlerDeregistration Register()
2314
+ {
2315
+ MessageHandler messageHandler = Token._messageHandler;
2316
+ IMessageBus messageBus = Token._messageBus;
2317
+ switch (_kind)
2318
+ {
2319
+ case RegistrationKind.TargetedHandlerAction:
2320
+ _scalarAction = (Action<T>)_userHandler;
2321
+ return messageHandler.RegisterTargetedMessageHandler(
2322
+ _context,
2323
+ _scalarAction,
2324
+ AugmentedScalarAction,
2325
+ priority: _priority,
2326
+ messageBus: messageBus
2327
+ );
2328
+ case RegistrationKind.TargetedHandlerFast:
2329
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2330
+ return messageHandler.RegisterTargetedMessageHandler(
2331
+ _context,
2332
+ _scalarFast,
2333
+ AugmentedScalarFast,
2334
+ priority: _priority,
2335
+ messageBus: messageBus
2336
+ );
2337
+ case RegistrationKind.TargetedPostProcessorAction:
2338
+ _scalarAction = (Action<T>)_userHandler;
2339
+ return messageHandler.RegisterTargetedPostProcessor(
2340
+ _context,
2341
+ _scalarAction,
2342
+ AugmentedScalarAction,
2343
+ _priority,
2344
+ messageBus
2345
+ );
2346
+ case RegistrationKind.TargetedPostProcessorFast:
2347
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2348
+ return messageHandler.RegisterTargetedPostProcessor(
2349
+ _context,
2350
+ _scalarFast,
2351
+ AugmentedScalarFast,
2352
+ _priority,
2353
+ messageBus
2354
+ );
2355
+ case RegistrationKind.TargetedWithoutTargetingAction:
2356
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2357
+ return messageHandler.RegisterTargetedWithoutTargeting(
2358
+ _contextAction,
2359
+ AugmentedContextAction,
2360
+ priority: _priority,
2361
+ messageBus: messageBus
2362
+ );
2363
+ case RegistrationKind.TargetedWithoutTargetingFast:
2364
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2365
+ return messageHandler.RegisterTargetedWithoutTargeting(
2366
+ _contextFast,
2367
+ AugmentedContextFast,
2368
+ priority: _priority,
2369
+ messageBus: messageBus
2370
+ );
2371
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorAction:
2372
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2373
+ return messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2374
+ _contextAction,
2375
+ AugmentedContextAction,
2376
+ _priority,
2377
+ messageBus
2378
+ );
2379
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorFast:
2380
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2381
+ return messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2382
+ _contextFast,
2383
+ AugmentedContextFast,
2384
+ _priority,
2385
+ messageBus
2386
+ );
2387
+ case RegistrationKind.TargetedInterceptor:
2388
+ return messageHandler.RegisterTargetedInterceptor(
2389
+ (IMessageBus.TargetedInterceptor<T>)_userHandler,
2390
+ priority: _priority,
2391
+ messageBus: messageBus
2392
+ );
2393
+ default:
2394
+ throw new InvalidOperationException(
2395
+ $"Unexpected registration kind {_kind} for TargetedRegistration<{typeof(T)}>."
2396
+ );
2397
+ }
2398
+ }
2399
+
2400
+ // Scalar invokers (targeted handler / post-processor). The user's handler is
2401
+ // the identity/dedup key; this flat invoker runs for the default slot. Calls
2402
+ // the typed field directly (no castclass) then records the (message, _context)
2403
+ // emission, matching the former AugmentedHandler bodies exactly.
2404
+ private void AugmentedScalarAction(ref T message)
2405
+ {
2406
+ _scalarAction(message);
2407
+ if (Token._diagnosticMode)
2408
+ {
2409
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2410
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2411
+ }
2412
+ }
2413
+
2414
+ private void AugmentedScalarFast(ref T message)
2415
+ {
2416
+ _scalarFast(ref message);
2417
+ if (Token._diagnosticMode)
2418
+ {
2419
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2420
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2421
+ }
2422
+ }
2423
+
2424
+ // Context invokers (without-targeting handler / post-processor). Emission data
2425
+ // uses the dispatch-supplied target (the ref param), not the stored _context
2426
+ // (default for these kinds), matching the former bodies exactly.
2427
+ private void AugmentedContextAction(ref InstanceId target, ref T message)
2428
+ {
2429
+ _contextAction(target, message);
2430
+ if (Token._diagnosticMode)
2431
+ {
2432
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2433
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2434
+ }
2435
+ }
2436
+
2437
+ private void AugmentedContextFast(ref InstanceId target, ref T message)
2438
+ {
2439
+ _contextFast(ref target, ref message);
2440
+ if (Token._diagnosticMode)
2441
+ {
2442
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2443
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2444
+ }
2445
+ }
2446
+ }
2447
+
2448
+ private sealed class UntargetedRegistration<T> : Registration
2449
+ where T : IUntargetedMessage
2450
+ {
2451
+ private readonly RegistrationKind _kind;
2452
+ private readonly object _userHandler;
2453
+ private readonly int _priority;
2454
+
2455
+ // Typed views of _userHandler, resolved once at Register() time (cold) so the
2456
+ // per-dispatch invoker calls a typed field directly -- no castclass/switch on
2457
+ // the hot path. Exactly one is set per registration kind.
2458
+ private Action<T> _scalarAction;
2459
+ private MessageHandler.FastHandler<T> _scalarFast;
2460
+
2461
+ internal UntargetedRegistration(
2462
+ MessageRegistrationToken token,
2463
+ RegistrationKind kind,
2464
+ object userHandler,
2465
+ int priority
2466
+ )
2467
+ : base(token)
2468
+ {
2469
+ _kind = kind;
2470
+ _userHandler = userHandler;
2471
+ _priority = priority;
2472
+ }
2473
+
2474
+ public override MessageHandler.HandlerDeregistration Register()
2475
+ {
2476
+ MessageHandler messageHandler = Token._messageHandler;
2477
+ IMessageBus messageBus = Token._messageBus;
2478
+ switch (_kind)
2479
+ {
2480
+ case RegistrationKind.UntargetedHandlerAction:
2481
+ _scalarAction = (Action<T>)_userHandler;
2482
+ return messageHandler.RegisterUntargetedMessageHandler(
2483
+ _scalarAction,
2484
+ AugmentedScalarAction,
2485
+ priority: _priority,
2486
+ messageBus: messageBus
2487
+ );
2488
+ case RegistrationKind.UntargetedHandlerFast:
2489
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2490
+ return messageHandler.RegisterUntargetedMessageHandler(
2491
+ _scalarFast,
2492
+ AugmentedScalarFast,
2493
+ priority: _priority,
2494
+ messageBus: messageBus
2495
+ );
2496
+ case RegistrationKind.UntargetedPostProcessorFast:
2497
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2498
+ return messageHandler.RegisterUntargetedPostProcessor(
2499
+ _scalarFast,
2500
+ AugmentedScalarFast,
2501
+ _priority,
2502
+ messageBus
2503
+ );
2504
+ case RegistrationKind.UntargetedInterceptor:
2505
+ return messageHandler.RegisterUntargetedInterceptor(
2506
+ (IMessageBus.UntargetedInterceptor<T>)_userHandler,
2507
+ priority: _priority,
2508
+ messageBus: messageBus
2509
+ );
2510
+ default:
2511
+ throw new InvalidOperationException(
2512
+ $"Unexpected registration kind {_kind} for UntargetedRegistration<{typeof(T)}>."
2513
+ );
2514
+ }
2515
+ }
2516
+
2517
+ // Untargeted scalar invokers. No context: emission data carries the message
2518
+ // only, matching the former AugmentedHandler bodies exactly.
2519
+ private void AugmentedScalarAction(ref T message)
2520
+ {
2521
+ _scalarAction(message);
2522
+ if (Token._diagnosticMode)
2523
+ {
2524
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2525
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2526
+ }
2527
+ }
2528
+
2529
+ private void AugmentedScalarFast(ref T message)
2530
+ {
2531
+ _scalarFast(ref message);
2532
+ if (Token._diagnosticMode)
2533
+ {
2534
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2535
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2536
+ }
2537
+ }
2538
+ }
2539
+
2540
+ private sealed class BroadcastRegistration<T> : Registration
2541
+ where T : IBroadcastMessage
2542
+ {
2543
+ private readonly RegistrationKind _kind;
2544
+ private readonly InstanceId _context;
2545
+ private readonly object _userHandler;
2546
+ private readonly int _priority;
2547
+
2548
+ // Typed views of _userHandler, resolved once at Register() time (cold) so the
2549
+ // per-dispatch invoker calls a typed field directly -- no castclass/switch on
2550
+ // the hot path. Exactly one is set per registration kind.
2551
+ private Action<T> _scalarAction;
2552
+ private MessageHandler.FastHandler<T> _scalarFast;
2553
+ private Action<InstanceId, T> _contextAction;
2554
+ private MessageHandler.FastHandlerWithContext<T> _contextFast;
2555
+
2556
+ internal BroadcastRegistration(
2557
+ MessageRegistrationToken token,
2558
+ RegistrationKind kind,
2559
+ InstanceId context,
2560
+ object userHandler,
2561
+ int priority
2562
+ )
2563
+ : base(token)
2564
+ {
2565
+ _kind = kind;
2566
+ _context = context;
2567
+ _userHandler = userHandler;
2568
+ _priority = priority;
2569
+ }
2570
+
2571
+ public override MessageHandler.HandlerDeregistration Register()
2572
+ {
2573
+ MessageHandler messageHandler = Token._messageHandler;
2574
+ IMessageBus messageBus = Token._messageBus;
2575
+ switch (_kind)
2576
+ {
2577
+ case RegistrationKind.BroadcastHandlerAction:
2578
+ _scalarAction = (Action<T>)_userHandler;
2579
+ return messageHandler.RegisterSourcedBroadcastMessageHandler(
2580
+ _context,
2581
+ _scalarAction,
2582
+ AugmentedScalarAction,
2583
+ priority: _priority,
2584
+ messageBus: messageBus
2585
+ );
2586
+ case RegistrationKind.BroadcastHandlerFast:
2587
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2588
+ return messageHandler.RegisterSourcedBroadcastMessageHandler(
2589
+ _context,
2590
+ _scalarFast,
2591
+ AugmentedScalarFast,
2592
+ priority: _priority,
2593
+ messageBus: messageBus
2594
+ );
2595
+ case RegistrationKind.BroadcastPostProcessorAction:
2596
+ _scalarAction = (Action<T>)_userHandler;
2597
+ return messageHandler.RegisterSourcedBroadcastPostProcessor(
2598
+ _context,
2599
+ _scalarAction,
2600
+ AugmentedScalarAction,
2601
+ _priority,
2602
+ messageBus
2603
+ );
2604
+ case RegistrationKind.BroadcastPostProcessorFast:
2605
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2606
+ return messageHandler.RegisterSourcedBroadcastPostProcessor(
2607
+ _context,
2608
+ _scalarFast,
2609
+ AugmentedScalarFast,
2610
+ _priority,
2611
+ messageBus
2612
+ );
2613
+ case RegistrationKind.BroadcastWithoutSourceAction:
2614
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2615
+ return messageHandler.RegisterSourcedBroadcastWithoutSource(
2616
+ _contextAction,
2617
+ AugmentedContextAction,
2618
+ priority: _priority,
2619
+ messageBus: messageBus
2620
+ );
2621
+ case RegistrationKind.BroadcastWithoutSourceFast:
2622
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2623
+ return messageHandler.RegisterSourcedBroadcastWithoutSource(
2624
+ _contextFast,
2625
+ AugmentedContextFast,
2626
+ priority: _priority,
2627
+ messageBus: messageBus
2628
+ );
2629
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorAction:
2630
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2631
+ return messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
2632
+ _contextAction,
2633
+ AugmentedContextAction,
2634
+ priority: _priority,
2635
+ messageBus
2636
+ );
2637
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorFast:
2638
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2639
+ return messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
2640
+ _contextFast,
2641
+ AugmentedContextFast,
2642
+ priority: _priority,
2643
+ messageBus
2644
+ );
2645
+ case RegistrationKind.BroadcastInterceptor:
2646
+ return messageHandler.RegisterBroadcastInterceptor(
2647
+ (IMessageBus.BroadcastInterceptor<T>)_userHandler,
2648
+ priority: _priority,
2649
+ messageBus: messageBus
2650
+ );
2651
+ default:
2652
+ throw new InvalidOperationException(
2653
+ $"Unexpected registration kind {_kind} for BroadcastRegistration<{typeof(T)}>."
2654
+ );
2655
+ }
2656
+ }
2657
+
2658
+ // Broadcast scalar invokers. Emission data carries the stored source
2659
+ // (_context), matching the former AugmentedHandler bodies exactly.
2660
+ private void AugmentedScalarAction(ref T message)
2661
+ {
2662
+ _scalarAction(message);
2663
+ if (Token._diagnosticMode)
2664
+ {
2665
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2666
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2667
+ }
2668
+ }
2669
+
2670
+ private void AugmentedScalarFast(ref T message)
2671
+ {
2672
+ _scalarFast(ref message);
2673
+ if (Token._diagnosticMode)
2674
+ {
2675
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2676
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2677
+ }
2678
+ }
2679
+
2680
+ // Broadcast context invokers for the without-source kinds. Emission data uses
2681
+ // the dispatch-supplied source (the ref param), not the stored _context
2682
+ // (default for these kinds), matching the former bodies exactly.
2683
+ private void AugmentedContextAction(ref InstanceId source, ref T message)
2684
+ {
2685
+ _contextAction(source, message);
2686
+ if (Token._diagnosticMode)
2687
+ {
2688
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2689
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2690
+ }
2691
+ }
2692
+
2693
+ private void AugmentedContextFast(ref InstanceId source, ref T message)
2694
+ {
2695
+ _contextFast(ref source, ref message);
2696
+ if (Token._diagnosticMode)
2697
+ {
2698
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2699
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2700
+ }
2701
+ }
2702
+ }
2703
+
2704
+ // Global accept-all is non-generic: its three sub-handlers are the fixed
2705
+ // IMessage facades. Stores the three user delegates (as object, since the two
2706
+ // public overloads differ in delegate shape -- Action vs FastHandler/
2707
+ // FastHandlerWithContext) and exposes six augmented sub-invokers (three per
2708
+ // overload shape). The kind-switch picks the matching MessageHandler.
2709
+ // RegisterGlobalAcceptAll overload and binds the three augmented invokers.
2710
+ private sealed class GlobalAcceptAllRegistration : Registration
2711
+ {
2712
+ private readonly RegistrationKind _kind;
2713
+
2714
+ // Typed sub-handlers (one shape-trio is set per kind). The per-dispatch
2715
+ // invokers call these directly -- no castclass on the hot global-dispatch
2716
+ // path (the heaviest fan-out, so the castclass cost there was the worst).
2717
+ private readonly Action<IUntargetedMessage> _untargetedAction;
2718
+ private readonly Action<InstanceId, ITargetedMessage> _targetedAction;
2719
+ private readonly Action<InstanceId, IBroadcastMessage> _broadcastAction;
2720
+ private readonly MessageHandler.FastHandler<IUntargetedMessage> _untargetedFast;
2721
+ private readonly MessageHandler.FastHandlerWithContext<ITargetedMessage> _targetedFast;
2722
+ private readonly MessageHandler.FastHandlerWithContext<IBroadcastMessage> _broadcastFast;
2723
+
2724
+ internal GlobalAcceptAllRegistration(
2725
+ MessageRegistrationToken token,
2726
+ Action<IUntargetedMessage> untargeted,
2727
+ Action<InstanceId, ITargetedMessage> targeted,
2728
+ Action<InstanceId, IBroadcastMessage> broadcast
2729
+ )
2730
+ : base(token)
2731
+ {
2732
+ _kind = RegistrationKind.GlobalAcceptAllAction;
2733
+ _untargetedAction = untargeted;
2734
+ _targetedAction = targeted;
2735
+ _broadcastAction = broadcast;
2736
+ }
2737
+
2738
+ internal GlobalAcceptAllRegistration(
2739
+ MessageRegistrationToken token,
2740
+ MessageHandler.FastHandler<IUntargetedMessage> untargeted,
2741
+ MessageHandler.FastHandlerWithContext<ITargetedMessage> targeted,
2742
+ MessageHandler.FastHandlerWithContext<IBroadcastMessage> broadcast
2743
+ )
2744
+ : base(token)
2745
+ {
2746
+ _kind = RegistrationKind.GlobalAcceptAllFast;
2747
+ _untargetedFast = untargeted;
2748
+ _targetedFast = targeted;
2749
+ _broadcastFast = broadcast;
2750
+ }
2751
+
2752
+ public override MessageHandler.HandlerDeregistration Register()
2753
+ {
2754
+ MessageHandler messageHandler = Token._messageHandler;
2755
+ IMessageBus messageBus = Token._messageBus;
2756
+ if (_kind == RegistrationKind.GlobalAcceptAllAction)
2757
+ {
2758
+ return messageHandler.RegisterGlobalAcceptAll(
2759
+ _untargetedAction,
2760
+ AugmentedUntargetedAction,
2761
+ _targetedAction,
2762
+ AugmentedTargetedAction,
2763
+ _broadcastAction,
2764
+ AugmentedBroadcastAction,
2765
+ messageBus
2766
+ );
2767
+ }
2768
+
2769
+ return messageHandler.RegisterGlobalAcceptAll(
2770
+ _untargetedFast,
2771
+ AugmentedUntargetedFast,
2772
+ _targetedFast,
2773
+ AugmentedTargetedFast,
2774
+ _broadcastFast,
2775
+ AugmentedBroadcastFast,
2776
+ messageBus
2777
+ );
2778
+ }
2779
+
2780
+ private void AugmentedUntargetedAction(IUntargetedMessage message)
2781
+ {
2782
+ _untargetedAction(message);
2783
+ if (Token._diagnosticMode)
2784
+ {
2785
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2786
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2787
+ }
2788
+ }
2789
+
2790
+ private void AugmentedTargetedAction(InstanceId target, ITargetedMessage message)
2791
+ {
2792
+ _targetedAction(target, message);
2793
+ if (Token._diagnosticMode)
2794
+ {
2795
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2796
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2797
+ }
2798
+ }
2799
+
2800
+ private void AugmentedBroadcastAction(InstanceId source, IBroadcastMessage message)
2801
+ {
2802
+ _broadcastAction(source, message);
2803
+ if (Token._diagnosticMode)
2804
+ {
2805
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2806
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2807
+ }
2808
+ }
2809
+
2810
+ private void AugmentedUntargetedFast(ref IUntargetedMessage message)
2811
+ {
2812
+ _untargetedFast(ref message);
2813
+ if (Token._diagnosticMode)
2814
+ {
2815
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2816
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2817
+ }
2818
+ }
2819
+
2820
+ private void AugmentedTargetedFast(ref InstanceId target, ref ITargetedMessage message)
2821
+ {
2822
+ _targetedFast(ref target, ref message);
2823
+ if (Token._diagnosticMode)
2824
+ {
2825
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2826
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2827
+ }
2828
+ }
2829
+
2830
+ private void AugmentedBroadcastFast(
2831
+ ref InstanceId source,
2832
+ ref IBroadcastMessage message
2833
+ )
2834
+ {
2835
+ _broadcastFast(ref source, ref message);
2836
+ if (Token._diagnosticMode)
2837
+ {
2838
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2839
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2840
+ }
2841
+ }
1987
2842
  }
1988
2843
 
1989
2844
  /// <summary>
@@ -2052,7 +2907,7 @@ namespace DxMessaging.Core
2052
2907
  }
2053
2908
 
2054
2909
  /// <summary>
2055
- /// Removes all staged registrations and releases references to the handler.
2910
+ /// Removes all staged registrations and clears token-local diagnostic state.
2056
2911
  /// </summary>
2057
2912
  public void Dispose()
2058
2913
  {