com.wallstop-studios.dxmessaging 3.1.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +326 -0
  2. package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +3 -3
  3. package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +3 -3
  4. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +8 -8
  5. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +3 -3
  6. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs +171 -13
  7. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
  8. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +391 -96
  9. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
  10. package/Editor/CustomEditors/MessageAwareComponentInspectorView.cs +279 -0
  11. package/Editor/CustomEditors/MessageAwareComponentInspectorView.cs.meta +11 -0
  12. package/Editor/DxMessagingEditorPalette.cs +66 -0
  13. package/Editor/DxMessagingEditorPalette.cs.meta +11 -0
  14. package/Editor/DxMessagingEditorTheme.cs +200 -0
  15. package/Editor/DxMessagingEditorTheme.cs.meta +11 -0
  16. package/Editor/DxMessagingMenu.cs.meta +3 -3
  17. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
  18. package/Editor/Icons/dxmessaging-icon-256.png +0 -0
  19. package/Editor/Icons/dxmessaging-icon-256.png.meta +156 -0
  20. package/Editor/Icons/dxmessaging-icon-32.png +0 -0
  21. package/Editor/Icons/dxmessaging-icon-32.png.meta +156 -0
  22. package/Editor/Icons/dxmessaging-icon-48.png +0 -0
  23. package/Editor/Icons/dxmessaging-icon-48.png.meta +156 -0
  24. package/Editor/Icons.meta +8 -0
  25. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +2 -3
  26. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
  27. package/Editor/Settings/DxMessagingSettingsProvider.cs +368 -33
  28. package/Editor/SetupCscRsp.cs +232 -163
  29. package/Editor/Testing/MessagingComponentEditorHarness.cs +11 -4
  30. package/Editor/Theme/DxMessagingTheme.uss +255 -0
  31. package/Editor/Theme/DxMessagingTheme.uss.meta +11 -0
  32. package/Editor/Theme/DxTokens.uss +90 -0
  33. package/Editor/Theme/DxTokens.uss.meta +11 -0
  34. package/Editor/Theme.meta +8 -0
  35. package/Editor/Windows/DxMessagingFlowGraphWindow.cs +5513 -0
  36. package/Editor/Windows/DxMessagingFlowGraphWindow.cs.meta +11 -0
  37. package/Editor/Windows/DxMessagingMessageMonitorWindow.cs +2288 -0
  38. package/Editor/Windows/DxMessagingMessageMonitorWindow.cs.meta +11 -0
  39. package/Editor/Windows.meta +8 -0
  40. package/README.md +84 -35
  41. package/Runtime/Analyzers.meta +8 -0
  42. package/Runtime/AssemblyInfo.cs +1 -0
  43. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
  44. package/Runtime/Core/Diagnostics/MessageEmissionData.cs +29 -1
  45. package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
  46. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
  47. package/Runtime/Core/Extensions/MessageExtensions.cs +4 -5
  48. package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
  49. package/Runtime/Core/Helper/MessageCache.cs +56 -23
  50. package/Runtime/Core/IMessage.cs.meta +3 -3
  51. package/Runtime/Core/InstanceId.cs.meta +3 -3
  52. package/Runtime/Core/Internal/FlatDispatch.cs +32 -0
  53. package/Runtime/Core/Internal/TypedSlots.cs +45 -7
  54. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
  55. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
  56. package/Runtime/Core/MessageBus/IMessageBus.cs +63 -28
  57. package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
  58. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
  59. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
  60. package/Runtime/Core/MessageBus/MessageBus.cs +1135 -626
  61. package/Runtime/Core/MessageBus/MessageBus.cs.meta +3 -3
  62. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +3 -3
  63. package/Runtime/Core/MessageBus/MessageBusRegistration.cs +207 -0
  64. package/Runtime/Core/MessageBus/MessageBusRegistration.cs.meta +11 -0
  65. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +3 -3
  66. package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +3 -3
  67. package/Runtime/Core/MessageBus/RegistrationLog.cs +25 -12
  68. package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +3 -3
  69. package/Runtime/Core/MessageHandler.cs +1886 -1082
  70. package/Runtime/Core/MessageHandler.cs.meta +3 -3
  71. package/Runtime/Core/MessageRegistrationHandle.cs +19 -5
  72. package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
  73. package/Runtime/Core/MessageRegistrationToken.cs +1932 -895
  74. package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
  75. package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +3 -3
  76. package/Runtime/Core/Messages/ITargetedMessage.cs.meta +3 -3
  77. package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +3 -3
  78. package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +3 -3
  79. package/Runtime/Core/MessagingDebug.cs.meta +3 -3
  80. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +3 -3
  81. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +3 -3
  82. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +3 -3
  83. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +3 -3
  84. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
  85. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
  86. package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
  87. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
  88. package/Runtime/Unity/MessagingComponent.cs.meta +3 -3
  89. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +3 -3
  90. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +3 -3
  91. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +3 -3
  92. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +3 -3
  93. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +3 -3
  94. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.cs +173 -0
  95. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.cs.meta +11 -0
  96. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.unity +424 -0
  97. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.unity.meta +7 -0
  98. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingReceiver.cs +185 -0
  99. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingReceiver.cs.meta +11 -0
  100. package/Samples~/Diagnostics Tooling Exerciser/Messages.cs +46 -0
  101. package/Samples~/Diagnostics Tooling Exerciser/Messages.cs.meta +11 -0
  102. package/Samples~/Diagnostics Tooling Exerciser/README.md +53 -0
  103. package/Samples~/Diagnostics Tooling Exerciser/README.md.meta +7 -0
  104. package/Samples~/Diagnostics Tooling Exerciser/WallstopStudios.DxMessaging.DiagnosticsToolingExerciser.Sample.asmdef +13 -0
  105. package/Samples~/Diagnostics Tooling Exerciser/WallstopStudios.DxMessaging.DiagnosticsToolingExerciser.Sample.asmdef.meta +7 -0
  106. package/Samples~/Diagnostics Tooling Exerciser.meta +8 -0
  107. package/Samples~/Mini Combat/Boot.cs.meta +3 -3
  108. package/Samples~/Mini Combat/Enemy.cs.meta +3 -3
  109. package/Samples~/Mini Combat/Messages.cs.meta +3 -3
  110. package/Samples~/Mini Combat/Player.cs.meta +3 -3
  111. package/Samples~/Mini Combat/README.md +1 -1
  112. package/Samples~/Mini Combat/UIOverlay.cs.meta +3 -3
  113. package/Samples~/Mini Combat/Walkthrough.md +3 -3
  114. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +3 -3
  115. package/Samples~/UI Buttons + Inspector/Messages.cs.meta +3 -3
  116. package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +3 -3
  117. package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +3 -3
  118. package/SourceGenerators/Directory.Build.props +5 -5
  119. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
  120. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +12 -11
  121. package/docs/images/DxMessaging-banner.svg +79 -0
  122. package/docs/images/DxMessaging-banner.svg.meta +53 -0
  123. package/docs/images.meta +8 -0
  124. package/docs.meta +8 -0
  125. package/package.json +17 -5
  126. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  127. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +0 -0
  128. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
  129. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +0 -0
  130. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
  131. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +0 -0
  132. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
  133. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +0 -0
  134. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
  135. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +0 -0
  136. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  137. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -0
  138. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  139. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +0 -0
@@ -58,32 +58,86 @@ namespace DxMessaging.Core
58
58
 
59
59
  private readonly MessageHandler _messageHandler;
60
60
 
61
- private readonly Dictionary<MessageRegistrationHandle, Action> _registrations = new();
62
-
63
- // Staged-registration handles in registration order. Dictionary
64
- // enumeration order is NOT stable across Remove/Add churn, so Enable()
65
- // (and RetargetMessageBus) replay staged registrations by walking this
66
- // list instead of _registrations.Values; otherwise a Disable()/Enable()
67
- // cycle after churn would silently permute the documented
68
- // "same priority uses registration order" dispatch contract.
69
- // Invariant: contains exactly the keys of _registrations, in the order
70
- // InternalRegister staged them (handle ids are monotonically
71
- // increasing, so this list is also sorted by handle id).
72
- private readonly List<MessageRegistrationHandle> _registrationOrder = new();
73
- private readonly Dictionary<
74
- MessageRegistrationHandle,
75
- PendingDeregistration
76
- > _deregistrations = new();
77
- private readonly List<Action> _actionQueue = new();
78
- private readonly List<MessageRegistrationHandle> _handleQueue = new();
79
- internal readonly Dictionary<
80
- MessageRegistrationHandle,
81
- MessageRegistrationMetadata
82
- > _metadata = new();
83
- internal readonly Dictionary<MessageRegistrationHandle, int> _callCounts = new();
84
- internal readonly CyclicBuffer<MessageEmissionData> _emissionBuffer = new(
85
- IMessageBus.GlobalMessageBufferSize
86
- );
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
+ // The registration also owns its common teardown state, eliminating the separate
72
+ // HandlerDeregistration object; an allocation-backed wrapper is created only for
73
+ // overlapping retry/recovery state. Net ~ -3 managed allocations per registration.
74
+ // The central replay loop pairs
75
+ // the Registration with its handle and performs the AddDeregistration. The
76
+ // re-entrancy snapshot semantics are unchanged: the replay queue captures the
77
+ // Registration reference (exactly as it previously captured the staging Func),
78
+ // so a registration removed mid-replay is still replayed if it was already
79
+ // snapshotted.
80
+ // Token-owned arena. A handle identifies a slot and the globally unique id that
81
+ // occupied it. Both are validated, so stale handles remain harmless after slot reuse.
82
+ // The doubly-linked live list preserves registration order while the singly-linked
83
+ // free list makes insertion and removal O(1).
84
+ private RegistrationSlot[] _slots = Array.Empty<RegistrationSlot>();
85
+ private int _registrationHead = -1;
86
+ private int _registrationTail = -1;
87
+ private int _freeSlotHead = -1;
88
+ private int _registrationCount;
89
+ private int _deregistrationCount;
90
+
91
+ // Lifecycle snapshots are exceptional-path storage and materialize only when a replay,
92
+ // teardown, or retarget actually needs them.
93
+ private List<StagedRegistration> _registrationReplayQueue;
94
+ private List<TeardownIdentity> _teardownQueue;
95
+ private Dictionary<MessageRegistrationHandle, int> _rollbackCounts;
96
+ private List<MessageRegistrationHandle> _activeRetargetHandles;
97
+ private Dictionary<MessageRegistrationHandle, object> _orphanDeregistrations;
98
+
99
+ internal RegistrationMetadataView _metadata => new RegistrationMetadataView(this);
100
+
101
+ // Diagnostics-only collections, allocated lazily on first use. A token whose
102
+ // owner never enables diagnostics (the default -- GlobalDiagnosticsTargets is
103
+ // Off -- and the common player case) never materializes them, saving the
104
+ // dictionary, the cyclic buffer, and the buffer's two backing lists per token.
105
+ // These are exposed as properties under their original field names so the
106
+ // inspector overlay and the diagnostics tests (which read token._callCounts /
107
+ // token._emissionBuffer) compile and behave unchanged; the getter caches into
108
+ // the backing field, so repeated reads return the same instance and an editor
109
+ // read simply materializes an empty collection. The only production writers are
110
+ // the dispatch-time AugmentedHandler bodies, all guarded by _diagnosticMode, so
111
+ // production-with-diagnostics-off never triggers the allocation. Teardown
112
+ // (ClearDiagnosticState / RemoveRegistrationState /
113
+ // PruneRegistrationStateToFailedDeregistrations) clears through the backing
114
+ // fields to avoid materializing a collection just to empty it.
115
+ private Dictionary<MessageRegistrationHandle, int> _callCountsBacking;
116
+ private CyclicBuffer<MessageEmissionData> _emissionBufferBacking;
117
+
118
+ internal Dictionary<MessageRegistrationHandle, int> _callCounts =>
119
+ _callCountsBacking ??= new Dictionary<MessageRegistrationHandle, int>();
120
+
121
+ internal CyclicBuffer<MessageEmissionData> _emissionBuffer =>
122
+ _emissionBufferBacking ??= new CyclicBuffer<MessageEmissionData>(
123
+ IMessageBus.GlobalMessageBufferSize
124
+ );
125
+
126
+ internal void RecordDiagnosticEmission(
127
+ MessageRegistrationHandle handle,
128
+ IMessageBus messageBus,
129
+ IMessage message,
130
+ InstanceId? context = null
131
+ )
132
+ {
133
+ _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
134
+ long traceId =
135
+ messageBus is DxMessaging.Core.MessageBus.MessageBus concreteBus
136
+ && concreteBus.IsDispatching
137
+ ? concreteBus.EmissionId
138
+ : 0;
139
+ _emissionBuffer.Add(new MessageEmissionData(message, context, traceId, handle));
140
+ }
87
141
 
88
142
  private IMessageBus _messageBus;
89
143
  private bool _enabled;
@@ -109,33 +163,13 @@ namespace DxMessaging.Core
109
163
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
110
164
  }
111
165
  return InternalRegister(
112
- handle =>
113
- {
114
- return _messageHandler.RegisterTargetedMessageHandler(
115
- target,
116
- targetedHandler,
117
- AugmentedHandler,
118
- priority: priority,
119
- messageBus: _messageBus
120
- );
121
-
122
- void AugmentedHandler(T message)
123
- {
124
- targetedHandler(message);
125
- if (_diagnosticMode)
126
- {
127
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
128
- _emissionBuffer.Add(new MessageEmissionData(message, target));
129
- }
130
- }
131
- },
132
- () =>
133
- new MessageRegistrationMetadata(
134
- target,
135
- typeof(T),
136
- MessageRegistrationType.Targeted,
137
- priority
138
- )
166
+ new TargetedRegistration<T>(
167
+ this,
168
+ RegistrationKind.TargetedHandlerAction,
169
+ target,
170
+ targetedHandler,
171
+ priority
172
+ )
139
173
  );
140
174
  }
141
175
 
@@ -152,33 +186,13 @@ namespace DxMessaging.Core
152
186
  }
153
187
 
154
188
  return InternalRegister(
155
- handle =>
156
- {
157
- return _messageHandler.RegisterTargetedMessageHandler(
158
- target,
159
- targetedHandler,
160
- AugmentedHandler,
161
- priority: priority,
162
- messageBus: _messageBus
163
- );
164
-
165
- void AugmentedHandler(ref T message)
166
- {
167
- targetedHandler(ref message);
168
- if (_diagnosticMode)
169
- {
170
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
171
- _emissionBuffer.Add(new MessageEmissionData(message, target));
172
- }
173
- }
174
- },
175
- () =>
176
- new MessageRegistrationMetadata(
177
- target,
178
- typeof(T),
179
- MessageRegistrationType.Targeted,
180
- priority
181
- )
189
+ new TargetedRegistration<T>(
190
+ this,
191
+ RegistrationKind.TargetedHandlerFast,
192
+ target,
193
+ targetedHandler,
194
+ priority
195
+ )
182
196
  );
183
197
  }
184
198
 
@@ -300,33 +314,13 @@ namespace DxMessaging.Core
300
314
  where T : ITargetedMessage
301
315
  {
302
316
  return InternalRegister(
303
- handle =>
304
- {
305
- return _messageHandler.RegisterTargetedPostProcessor(
306
- target,
307
- targetedPostProcessor,
308
- AugmentedHandler,
309
- priority,
310
- _messageBus
311
- );
312
-
313
- void AugmentedHandler(ref T message)
314
- {
315
- targetedPostProcessor(ref message);
316
- if (_diagnosticMode)
317
- {
318
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
319
- _emissionBuffer.Add(new MessageEmissionData(message, target));
320
- }
321
- }
322
- },
323
- () =>
324
- new MessageRegistrationMetadata(
325
- target,
326
- typeof(T),
327
- MessageRegistrationType.TargetedPostProcessor,
328
- priority
329
- )
317
+ new TargetedRegistration<T>(
318
+ this,
319
+ RegistrationKind.TargetedPostProcessorFast,
320
+ target,
321
+ targetedPostProcessor,
322
+ priority
323
+ )
330
324
  );
331
325
  }
332
326
 
@@ -346,33 +340,13 @@ namespace DxMessaging.Core
346
340
  where T : ITargetedMessage
347
341
  {
348
342
  return InternalRegister(
349
- handle =>
350
- {
351
- return _messageHandler.RegisterTargetedPostProcessor(
352
- target,
353
- targetedPostProcessor,
354
- AugmentedHandler,
355
- priority,
356
- _messageBus
357
- );
358
-
359
- void AugmentedHandler(ref T message)
360
- {
361
- targetedPostProcessor(ref message);
362
- if (_diagnosticMode)
363
- {
364
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
365
- _emissionBuffer.Add(new MessageEmissionData(message, target));
366
- }
367
- }
368
- },
369
- () =>
370
- new MessageRegistrationMetadata(
371
- target,
372
- typeof(T),
373
- MessageRegistrationType.TargetedPostProcessor,
374
- priority
375
- )
343
+ new TargetedRegistration<T>(
344
+ this,
345
+ RegistrationKind.TargetedPostProcessorFast,
346
+ target,
347
+ targetedPostProcessor,
348
+ priority
349
+ )
376
350
  );
377
351
  }
378
352
  #endif
@@ -435,33 +409,13 @@ namespace DxMessaging.Core
435
409
  where T : ITargetedMessage
436
410
  {
437
411
  return InternalRegister(
438
- handle =>
439
- {
440
- return _messageHandler.RegisterTargetedPostProcessor(
441
- target,
442
- targetedPostProcessor,
443
- AugmentedHandler,
444
- priority,
445
- _messageBus
446
- );
447
-
448
- void AugmentedHandler(ref T message)
449
- {
450
- targetedPostProcessor(ref message);
451
- if (_diagnosticMode)
452
- {
453
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
454
- _emissionBuffer.Add(new MessageEmissionData(message, target));
455
- }
456
- }
457
- },
458
- () =>
459
- new MessageRegistrationMetadata(
460
- target,
461
- typeof(T),
462
- MessageRegistrationType.TargetedPostProcessor,
463
- priority
464
- )
412
+ new TargetedRegistration<T>(
413
+ this,
414
+ RegistrationKind.TargetedPostProcessorFast,
415
+ target,
416
+ targetedPostProcessor,
417
+ priority
418
+ )
465
419
  );
466
420
  }
467
421
 
@@ -481,33 +435,13 @@ namespace DxMessaging.Core
481
435
  where T : ITargetedMessage
482
436
  {
483
437
  return InternalRegister(
484
- handle =>
485
- {
486
- return _messageHandler.RegisterTargetedPostProcessor(
487
- target,
488
- targetedPostProcessor,
489
- AugmentedHandler,
490
- priority,
491
- _messageBus
492
- );
493
-
494
- void AugmentedHandler(T message)
495
- {
496
- targetedPostProcessor(message);
497
- if (_diagnosticMode)
498
- {
499
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
500
- _emissionBuffer.Add(new MessageEmissionData(message, target));
501
- }
502
- }
503
- },
504
- () =>
505
- new MessageRegistrationMetadata(
506
- target,
507
- typeof(T),
508
- MessageRegistrationType.TargetedPostProcessor,
509
- priority
510
- )
438
+ new TargetedRegistration<T>(
439
+ this,
440
+ RegistrationKind.TargetedPostProcessorAction,
441
+ target,
442
+ targetedPostProcessor,
443
+ priority
444
+ )
511
445
  );
512
446
  }
513
447
 
@@ -532,32 +466,13 @@ namespace DxMessaging.Core
532
466
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
533
467
  }
534
468
  return InternalRegister(
535
- handle =>
536
- {
537
- return _messageHandler.RegisterTargetedWithoutTargeting(
538
- messageHandler,
539
- AugmentedHandler,
540
- priority: priority,
541
- messageBus: _messageBus
542
- );
543
-
544
- void AugmentedHandler(InstanceId target, T message)
545
- {
546
- messageHandler(target, message);
547
- if (_diagnosticMode)
548
- {
549
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
550
- _emissionBuffer.Add(new MessageEmissionData(message, target));
551
- }
552
- }
553
- },
554
- () =>
555
- new MessageRegistrationMetadata(
556
- null,
557
- typeof(T),
558
- MessageRegistrationType.TargetedWithoutTargeting,
559
- priority
560
- )
469
+ new TargetedRegistration<T>(
470
+ this,
471
+ RegistrationKind.TargetedWithoutTargetingAction,
472
+ default,
473
+ messageHandler,
474
+ priority
475
+ )
561
476
  );
562
477
  }
563
478
 
@@ -582,32 +497,13 @@ namespace DxMessaging.Core
582
497
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
583
498
  }
584
499
  return InternalRegister(
585
- handle =>
586
- {
587
- return _messageHandler.RegisterTargetedWithoutTargeting(
588
- messageHandler,
589
- AugmentedHandler,
590
- priority: priority,
591
- messageBus: _messageBus
592
- );
593
-
594
- void AugmentedHandler(ref InstanceId target, ref T message)
595
- {
596
- messageHandler(ref target, ref message);
597
- if (_diagnosticMode)
598
- {
599
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
600
- _emissionBuffer.Add(new MessageEmissionData(message, target));
601
- }
602
- }
603
- },
604
- () =>
605
- new MessageRegistrationMetadata(
606
- null,
607
- typeof(T),
608
- MessageRegistrationType.TargetedWithoutTargeting,
609
- priority
610
- )
500
+ new TargetedRegistration<T>(
501
+ this,
502
+ RegistrationKind.TargetedWithoutTargetingFast,
503
+ default,
504
+ messageHandler,
505
+ priority
506
+ )
611
507
  );
612
508
  }
613
509
 
@@ -632,32 +528,13 @@ namespace DxMessaging.Core
632
528
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
633
529
  }
634
530
  return InternalRegister(
635
- handle =>
636
- {
637
- return _messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
638
- postProcessor,
639
- AugmentedHandler,
640
- priority,
641
- _messageBus
642
- );
643
-
644
- void AugmentedHandler(InstanceId target, T message)
645
- {
646
- postProcessor(target, message);
647
- if (_diagnosticMode)
648
- {
649
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
650
- _emissionBuffer.Add(new MessageEmissionData(message, target));
651
- }
652
- }
653
- },
654
- () =>
655
- new MessageRegistrationMetadata(
656
- null,
657
- typeof(T),
658
- MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
659
- priority
660
- )
531
+ new TargetedRegistration<T>(
532
+ this,
533
+ RegistrationKind.TargetedWithoutTargetingPostProcessorAction,
534
+ default,
535
+ postProcessor,
536
+ priority
537
+ )
661
538
  );
662
539
  }
663
540
 
@@ -682,32 +559,13 @@ namespace DxMessaging.Core
682
559
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
683
560
  }
684
561
  return InternalRegister(
685
- handle =>
686
- {
687
- return _messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
688
- postProcessor,
689
- AugmentedHandler,
690
- priority,
691
- _messageBus
692
- );
693
-
694
- void AugmentedHandler(ref InstanceId target, ref T message)
695
- {
696
- postProcessor(ref target, ref message);
697
- if (_diagnosticMode)
698
- {
699
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
700
- _emissionBuffer.Add(new MessageEmissionData(message, target));
701
- }
702
- }
703
- },
704
- () =>
705
- new MessageRegistrationMetadata(
706
- null,
707
- typeof(T),
708
- MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
709
- priority
710
- )
562
+ new TargetedRegistration<T>(
563
+ this,
564
+ RegistrationKind.TargetedWithoutTargetingPostProcessorFast,
565
+ default,
566
+ postProcessor,
567
+ priority
568
+ )
711
569
  );
712
570
  }
713
571
 
@@ -739,32 +597,12 @@ namespace DxMessaging.Core
739
597
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
740
598
  }
741
599
  return InternalRegister(
742
- handle =>
743
- {
744
- return _messageHandler.RegisterUntargetedMessageHandler(
745
- untargetedHandler,
746
- AugmentedHandler,
747
- priority: priority,
748
- messageBus: _messageBus
749
- );
750
-
751
- void AugmentedHandler(T message)
752
- {
753
- untargetedHandler(message);
754
- if (_diagnosticMode)
755
- {
756
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
757
- _emissionBuffer.Add(new MessageEmissionData(message));
758
- }
759
- }
760
- },
761
- () =>
762
- new MessageRegistrationMetadata(
763
- null,
764
- typeof(T),
765
- MessageRegistrationType.Untargeted,
766
- priority
767
- )
600
+ new UntargetedRegistration<T>(
601
+ this,
602
+ RegistrationKind.UntargetedHandlerAction,
603
+ untargetedHandler,
604
+ priority
605
+ )
768
606
  );
769
607
  }
770
608
 
@@ -795,32 +633,12 @@ namespace DxMessaging.Core
795
633
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
796
634
  }
797
635
  return InternalRegister(
798
- handle =>
799
- {
800
- return _messageHandler.RegisterUntargetedMessageHandler(
801
- untargetedHandler,
802
- AugmentedHandler,
803
- priority: priority,
804
- messageBus: _messageBus
805
- );
806
-
807
- void AugmentedHandler(ref T message)
808
- {
809
- untargetedHandler(ref message);
810
- if (_diagnosticMode)
811
- {
812
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
813
- _emissionBuffer.Add(new MessageEmissionData(message));
814
- }
815
- }
816
- },
817
- () =>
818
- new MessageRegistrationMetadata(
819
- null,
820
- typeof(T),
821
- MessageRegistrationType.Untargeted,
822
- priority
823
- )
636
+ new UntargetedRegistration<T>(
637
+ this,
638
+ RegistrationKind.UntargetedHandlerFast,
639
+ untargetedHandler,
640
+ priority
641
+ )
824
642
  );
825
643
  }
826
644
 
@@ -842,32 +660,12 @@ namespace DxMessaging.Core
842
660
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
843
661
  }
844
662
  return InternalRegister(
845
- handle =>
846
- {
847
- return _messageHandler.RegisterUntargetedPostProcessor(
848
- untargetedPostProcessor,
849
- AugmentedHandler,
850
- priority,
851
- _messageBus
852
- );
853
-
854
- void AugmentedHandler(ref T message)
855
- {
856
- untargetedPostProcessor(ref message);
857
- if (_diagnosticMode)
858
- {
859
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
860
- _emissionBuffer.Add(new MessageEmissionData(message));
861
- }
862
- }
863
- },
864
- () =>
865
- new MessageRegistrationMetadata(
866
- null,
867
- typeof(T),
868
- MessageRegistrationType.UntargetedPostProcessor,
869
- priority
870
- )
663
+ new UntargetedRegistration<T>(
664
+ this,
665
+ RegistrationKind.UntargetedPostProcessorFast,
666
+ untargetedPostProcessor,
667
+ priority
668
+ )
871
669
  );
872
670
  }
873
671
 
@@ -883,33 +681,13 @@ namespace DxMessaging.Core
883
681
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
884
682
  }
885
683
  return InternalRegister(
886
- handle =>
887
- {
888
- return _messageHandler.RegisterSourcedBroadcastMessageHandler(
889
- source,
890
- broadcastHandler,
891
- AugmentedHandler,
892
- priority: priority,
893
- messageBus: _messageBus
894
- );
895
-
896
- void AugmentedHandler(T message)
897
- {
898
- broadcastHandler(message);
899
- if (_diagnosticMode)
900
- {
901
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
902
- _emissionBuffer.Add(new MessageEmissionData(message, source));
903
- }
904
- }
905
- },
906
- () =>
907
- new MessageRegistrationMetadata(
908
- source,
909
- typeof(T),
910
- MessageRegistrationType.Broadcast,
911
- priority
912
- )
684
+ new BroadcastRegistration<T>(
685
+ this,
686
+ RegistrationKind.BroadcastHandlerAction,
687
+ source,
688
+ broadcastHandler,
689
+ priority
690
+ )
913
691
  );
914
692
  }
915
693
 
@@ -925,33 +703,13 @@ namespace DxMessaging.Core
925
703
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
926
704
  }
927
705
  return InternalRegister(
928
- handle =>
929
- {
930
- return _messageHandler.RegisterSourcedBroadcastMessageHandler(
931
- source,
932
- broadcastHandler,
933
- AugmentedHandler,
934
- priority: priority,
935
- messageBus: _messageBus
936
- );
937
-
938
- void AugmentedHandler(ref T message)
939
- {
940
- broadcastHandler(ref message);
941
- if (_diagnosticMode)
942
- {
943
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
944
- _emissionBuffer.Add(new MessageEmissionData(message, source));
945
- }
946
- }
947
- },
948
- () =>
949
- new MessageRegistrationMetadata(
950
- source,
951
- typeof(T),
952
- MessageRegistrationType.Broadcast,
953
- priority
954
- )
706
+ new BroadcastRegistration<T>(
707
+ this,
708
+ RegistrationKind.BroadcastHandlerFast,
709
+ source,
710
+ broadcastHandler,
711
+ priority
712
+ )
955
713
  );
956
714
  }
957
715
 
@@ -968,33 +726,13 @@ namespace DxMessaging.Core
968
726
  }
969
727
 
970
728
  return InternalRegister(
971
- handle =>
972
- {
973
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
974
- source,
975
- broadcastPostProcessor,
976
- AugmentedHandler,
977
- priority,
978
- _messageBus
979
- );
980
-
981
- void AugmentedHandler(T message)
982
- {
983
- broadcastPostProcessor(message);
984
- if (_diagnosticMode)
985
- {
986
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
987
- _emissionBuffer.Add(new MessageEmissionData(message, source));
988
- }
989
- }
990
- },
991
- () =>
992
- new MessageRegistrationMetadata(
993
- source,
994
- typeof(T),
995
- MessageRegistrationType.BroadcastPostProcessor,
996
- priority
997
- )
729
+ new BroadcastRegistration<T>(
730
+ this,
731
+ RegistrationKind.BroadcastPostProcessorAction,
732
+ source,
733
+ broadcastPostProcessor,
734
+ priority
735
+ )
998
736
  );
999
737
  }
1000
738
 
@@ -1010,33 +748,13 @@ namespace DxMessaging.Core
1010
748
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1011
749
  }
1012
750
  return InternalRegister(
1013
- handle =>
1014
- {
1015
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
1016
- source,
1017
- broadcastPostProcessor,
1018
- AugmentedHandler,
1019
- priority,
1020
- _messageBus
1021
- );
1022
-
1023
- void AugmentedHandler(ref T message)
1024
- {
1025
- broadcastPostProcessor(ref message);
1026
- if (_diagnosticMode)
1027
- {
1028
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1029
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1030
- }
1031
- }
1032
- },
1033
- () =>
1034
- new MessageRegistrationMetadata(
1035
- source,
1036
- typeof(T),
1037
- MessageRegistrationType.BroadcastPostProcessor,
1038
- priority
1039
- )
751
+ new BroadcastRegistration<T>(
752
+ this,
753
+ RegistrationKind.BroadcastPostProcessorFast,
754
+ source,
755
+ broadcastPostProcessor,
756
+ priority
757
+ )
1040
758
  );
1041
759
  }
1042
760
 
@@ -1181,33 +899,13 @@ namespace DxMessaging.Core
1181
899
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1182
900
  }
1183
901
  return InternalRegister(
1184
- handle =>
1185
- {
1186
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
1187
- source,
1188
- broadcastPostProcessor,
1189
- AugmentedHandler,
1190
- priority: priority,
1191
- _messageBus
1192
- );
1193
-
1194
- void AugmentedHandler(T message)
1195
- {
1196
- broadcastPostProcessor(message);
1197
- if (_diagnosticMode)
1198
- {
1199
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1200
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1201
- }
1202
- }
1203
- },
1204
- () =>
1205
- new MessageRegistrationMetadata(
1206
- source,
1207
- typeof(T),
1208
- MessageRegistrationType.BroadcastPostProcessor,
1209
- priority
1210
- )
902
+ new BroadcastRegistration<T>(
903
+ this,
904
+ RegistrationKind.BroadcastPostProcessorAction,
905
+ source,
906
+ broadcastPostProcessor,
907
+ priority
908
+ )
1211
909
  );
1212
910
  }
1213
911
 
@@ -1231,33 +929,13 @@ namespace DxMessaging.Core
1231
929
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1232
930
  }
1233
931
  return InternalRegister(
1234
- handle =>
1235
- {
1236
- return _messageHandler.RegisterSourcedBroadcastPostProcessor(
1237
- source,
1238
- broadcastPostProcessor,
1239
- AugmentedHandler,
1240
- priority: priority,
1241
- _messageBus
1242
- );
1243
-
1244
- void AugmentedHandler(ref T message)
1245
- {
1246
- broadcastPostProcessor(ref message);
1247
- if (_diagnosticMode)
1248
- {
1249
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1250
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1251
- }
1252
- }
1253
- },
1254
- () =>
1255
- new MessageRegistrationMetadata(
1256
- source,
1257
- typeof(T),
1258
- MessageRegistrationType.BroadcastPostProcessor,
1259
- priority
1260
- )
932
+ new BroadcastRegistration<T>(
933
+ this,
934
+ RegistrationKind.BroadcastPostProcessorFast,
935
+ source,
936
+ broadcastPostProcessor,
937
+ priority
938
+ )
1261
939
  );
1262
940
  }
1263
941
  #endif
@@ -1373,32 +1051,13 @@ namespace DxMessaging.Core
1373
1051
  }
1374
1052
 
1375
1053
  return InternalRegister(
1376
- handle =>
1377
- {
1378
- return _messageHandler.RegisterSourcedBroadcastWithoutSource(
1379
- broadcastHandler,
1380
- AugmentedHandler,
1381
- priority: priority,
1382
- messageBus: _messageBus
1383
- );
1384
-
1385
- void AugmentedHandler(InstanceId source, T message)
1386
- {
1387
- broadcastHandler(source, message);
1388
- if (_diagnosticMode)
1389
- {
1390
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1391
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1392
- }
1393
- }
1394
- },
1395
- () =>
1396
- new MessageRegistrationMetadata(
1397
- null,
1398
- typeof(T),
1399
- MessageRegistrationType.BroadcastWithoutSource,
1400
- priority
1401
- )
1054
+ new BroadcastRegistration<T>(
1055
+ this,
1056
+ RegistrationKind.BroadcastWithoutSourceAction,
1057
+ default,
1058
+ broadcastHandler,
1059
+ priority
1060
+ )
1402
1061
  );
1403
1062
  }
1404
1063
 
@@ -1424,32 +1083,13 @@ namespace DxMessaging.Core
1424
1083
  }
1425
1084
 
1426
1085
  return InternalRegister(
1427
- handle =>
1428
- {
1429
- return _messageHandler.RegisterSourcedBroadcastWithoutSource(
1430
- broadcastHandler,
1431
- AugmentedHandler,
1432
- priority: priority,
1433
- messageBus: _messageBus
1434
- );
1435
-
1436
- void AugmentedHandler(ref InstanceId source, ref T message)
1437
- {
1438
- broadcastHandler(ref source, ref message);
1439
- if (_diagnosticMode)
1440
- {
1441
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1442
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1443
- }
1444
- }
1445
- },
1446
- () =>
1447
- new MessageRegistrationMetadata(
1448
- null,
1449
- typeof(T),
1450
- MessageRegistrationType.BroadcastWithoutSource,
1451
- priority
1452
- )
1086
+ new BroadcastRegistration<T>(
1087
+ this,
1088
+ RegistrationKind.BroadcastWithoutSourceFast,
1089
+ default,
1090
+ broadcastHandler,
1091
+ priority
1092
+ )
1453
1093
  );
1454
1094
  }
1455
1095
 
@@ -1480,32 +1120,13 @@ namespace DxMessaging.Core
1480
1120
  }
1481
1121
 
1482
1122
  return InternalRegister(
1483
- handle =>
1484
- {
1485
- return _messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
1486
- broadcastHandler,
1487
- AugmentedHandler,
1488
- priority: priority,
1489
- _messageBus
1490
- );
1491
-
1492
- void AugmentedHandler(InstanceId source, T message)
1493
- {
1494
- broadcastHandler(source, message);
1495
- if (_diagnosticMode)
1496
- {
1497
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1498
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1499
- }
1500
- }
1501
- },
1502
- () =>
1503
- new MessageRegistrationMetadata(
1504
- null,
1505
- typeof(T),
1506
- MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1507
- priority
1508
- )
1123
+ new BroadcastRegistration<T>(
1124
+ this,
1125
+ RegistrationKind.BroadcastWithoutSourcePostProcessorAction,
1126
+ default,
1127
+ broadcastHandler,
1128
+ priority
1129
+ )
1509
1130
  );
1510
1131
  }
1511
1132
 
@@ -1531,32 +1152,13 @@ namespace DxMessaging.Core
1531
1152
  }
1532
1153
 
1533
1154
  return InternalRegister(
1534
- handle =>
1535
- {
1536
- return _messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
1537
- broadcastHandler,
1538
- AugmentedHandler,
1539
- priority: priority,
1540
- _messageBus
1541
- );
1542
-
1543
- void AugmentedHandler(ref InstanceId source, ref T message)
1544
- {
1545
- broadcastHandler(ref source, ref message);
1546
- if (_diagnosticMode)
1547
- {
1548
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1549
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1550
- }
1551
- }
1552
- },
1553
- () =>
1554
- new MessageRegistrationMetadata(
1555
- null,
1556
- typeof(T),
1557
- MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1558
- priority
1559
- )
1155
+ new BroadcastRegistration<T>(
1156
+ this,
1157
+ RegistrationKind.BroadcastWithoutSourcePostProcessorFast,
1158
+ default,
1159
+ broadcastHandler,
1160
+ priority
1161
+ )
1560
1162
  );
1561
1163
  }
1562
1164
 
@@ -1581,55 +1183,12 @@ namespace DxMessaging.Core
1581
1183
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1582
1184
  }
1583
1185
  return InternalRegister(
1584
- handle =>
1585
- {
1586
- return _messageHandler.RegisterGlobalAcceptAll(
1587
- acceptAllUntargeted,
1588
- AugmentedUntargeted,
1589
- acceptAllTargeted,
1590
- AugmentedTargeted,
1591
- acceptAllBroadcast,
1592
- AugmentedBroadcast,
1593
- _messageBus
1594
- );
1595
-
1596
- void AugmentedUntargeted(IUntargetedMessage message)
1597
- {
1598
- acceptAllUntargeted(message);
1599
- if (_diagnosticMode)
1600
- {
1601
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1602
- _emissionBuffer.Add(new MessageEmissionData(message));
1603
- }
1604
- }
1605
-
1606
- void AugmentedTargeted(InstanceId target, ITargetedMessage message)
1607
- {
1608
- acceptAllTargeted(target, message);
1609
- if (_diagnosticMode)
1610
- {
1611
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1612
- _emissionBuffer.Add(new MessageEmissionData(message, target));
1613
- }
1614
- }
1615
-
1616
- void AugmentedBroadcast(InstanceId source, IBroadcastMessage message)
1617
- {
1618
- acceptAllBroadcast(source, message);
1619
- if (_diagnosticMode)
1620
- {
1621
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1622
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1623
- }
1624
- }
1625
- },
1626
- () =>
1627
- new MessageRegistrationMetadata(
1628
- null,
1629
- typeof(IMessage),
1630
- MessageRegistrationType.GlobalAcceptAll,
1631
- 0
1632
- )
1186
+ new GlobalAcceptAllRegistration(
1187
+ this,
1188
+ acceptAllUntargeted,
1189
+ acceptAllTargeted,
1190
+ acceptAllBroadcast
1191
+ )
1633
1192
  );
1634
1193
  }
1635
1194
 
@@ -1663,55 +1222,12 @@ namespace DxMessaging.Core
1663
1222
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1664
1223
  }
1665
1224
  return InternalRegister(
1666
- handle =>
1667
- {
1668
- return _messageHandler.RegisterGlobalAcceptAll(
1669
- acceptAllUntargeted,
1670
- AugmentedUntargeted,
1671
- acceptAllTargeted,
1672
- AugmentedTargeted,
1673
- acceptAllBroadcast,
1674
- AugmentedBroadcast,
1675
- _messageBus
1676
- );
1677
-
1678
- void AugmentedUntargeted(ref IUntargetedMessage message)
1679
- {
1680
- acceptAllUntargeted(ref message);
1681
- if (_diagnosticMode)
1682
- {
1683
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1684
- _emissionBuffer.Add(new MessageEmissionData(message));
1685
- }
1686
- }
1687
-
1688
- void AugmentedTargeted(ref InstanceId target, ref ITargetedMessage message)
1689
- {
1690
- acceptAllTargeted(ref target, ref message);
1691
- if (_diagnosticMode)
1692
- {
1693
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1694
- _emissionBuffer.Add(new MessageEmissionData(message, target));
1695
- }
1696
- }
1697
-
1698
- void AugmentedBroadcast(ref InstanceId source, ref IBroadcastMessage message)
1699
- {
1700
- acceptAllBroadcast(ref source, ref message);
1701
- if (_diagnosticMode)
1702
- {
1703
- _callCounts[handle] = _callCounts.GetValueOrDefault(handle) + 1;
1704
- _emissionBuffer.Add(new MessageEmissionData(message, source));
1705
- }
1706
- }
1707
- },
1708
- () =>
1709
- new MessageRegistrationMetadata(
1710
- null,
1711
- typeof(IMessage),
1712
- MessageRegistrationType.GlobalAcceptAll,
1713
- 0
1714
- )
1225
+ new GlobalAcceptAllRegistration(
1226
+ this,
1227
+ acceptAllUntargeted,
1228
+ acceptAllTargeted,
1229
+ acceptAllBroadcast
1230
+ )
1715
1231
  );
1716
1232
  }
1717
1233
 
@@ -1734,19 +1250,7 @@ namespace DxMessaging.Core
1734
1250
  }
1735
1251
 
1736
1252
  return InternalRegister(
1737
- _ =>
1738
- _messageHandler.RegisterUntargetedInterceptor(
1739
- interceptor,
1740
- priority: priority,
1741
- messageBus: _messageBus
1742
- ),
1743
- () =>
1744
- new MessageRegistrationMetadata(
1745
- null,
1746
- typeof(T),
1747
- MessageRegistrationType.UntargetedInterceptor,
1748
- priority
1749
- )
1253
+ new UntargetedInterceptorRegistration<T>(this, interceptor, priority)
1750
1254
  );
1751
1255
  }
1752
1256
 
@@ -1769,19 +1273,7 @@ namespace DxMessaging.Core
1769
1273
  }
1770
1274
 
1771
1275
  return InternalRegister(
1772
- _ =>
1773
- _messageHandler.RegisterBroadcastInterceptor(
1774
- interceptor,
1775
- priority: priority,
1776
- messageBus: _messageBus
1777
- ),
1778
- () =>
1779
- new MessageRegistrationMetadata(
1780
- null,
1781
- typeof(T),
1782
- MessageRegistrationType.BroadcastInterceptor,
1783
- priority
1784
- )
1276
+ new BroadcastInterceptorRegistration<T>(this, interceptor, priority)
1785
1277
  );
1786
1278
  }
1787
1279
 
@@ -1804,54 +1296,49 @@ namespace DxMessaging.Core
1804
1296
  }
1805
1297
 
1806
1298
  return InternalRegister(
1807
- _ =>
1808
- _messageHandler.RegisterTargetedInterceptor(
1809
- interceptor,
1810
- priority: priority,
1811
- messageBus: _messageBus
1812
- ),
1813
- () =>
1814
- new MessageRegistrationMetadata(
1815
- null,
1816
- typeof(T),
1817
- MessageRegistrationType.TargetedInterceptor,
1818
- priority
1819
- )
1299
+ new TargetedInterceptorRegistration<T>(this, interceptor, priority)
1820
1300
  );
1821
1301
  }
1822
1302
 
1823
1303
  /// <summary>
1824
1304
  /// Handles the actual [de]registration wrapping and (potential) lazy execution.
1825
1305
  /// </summary>
1826
- /// <param name="registerAndGetDeregistration">Proxied registration function that returns a de-registration function.</param>
1827
- /// <param name="metadataProducer">Opaque metadata producer function.</param>
1306
+ /// <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>
1828
1307
  /// <returns>A handle that allows for registration and de-registration.</returns>
1829
- private MessageRegistrationHandle InternalRegister(
1830
- Func<MessageRegistrationHandle, Action> registerAndGetDeregistration,
1831
- Func<MessageRegistrationMetadata> metadataProducer
1832
- )
1308
+ private MessageRegistrationHandle InternalRegister(Registration registration)
1833
1309
  {
1310
+ int slotIndex = AllocateSlot();
1834
1311
  MessageRegistrationHandle handle =
1835
- MessageRegistrationHandle.CreateMessageRegistrationHandle();
1836
-
1837
- _registrations[handle] = Registration;
1838
- _registrationOrder.Add(handle);
1839
- _metadata[handle] = metadataProducer();
1840
-
1841
- // Generally, registrations should take place before all calls to enable. Just in case, though...
1312
+ MessageRegistrationHandle.CreateMessageRegistrationHandle(slotIndex);
1313
+ registration.Handle = handle;
1314
+ ref RegistrationSlot slot = ref _slots[slotIndex];
1315
+ slot.Id = handle.Id;
1316
+ slot.Registration = registration;
1317
+ slot.Previous = _registrationTail;
1318
+ slot.Next = -1;
1319
+ slot.NextFree = -1;
1320
+ if (_registrationTail >= 0)
1321
+ {
1322
+ _slots[_registrationTail].Next = slotIndex;
1323
+ }
1324
+ else
1325
+ {
1326
+ _registrationHead = slotIndex;
1327
+ }
1328
+ _registrationTail = slotIndex;
1329
+ ++_registrationCount;
1330
+ // Generally, registrations should take place before all calls to enable.
1331
+ // Just in case, though, register immediately if already enabled. We do not
1332
+ // register at staging time when disabled (the owner might not be awake), so
1333
+ // the Registration object is retained in _registrations to lazily
1334
+ // (re)register on Enable().
1842
1335
  if (_enabled)
1843
1336
  {
1844
- Registration();
1337
+ MessageHandler.HandlerDeregistration actualDeregistration = registration.Register();
1338
+ AddDeregistration(slotIndex, handle.Id, actualDeregistration);
1845
1339
  }
1846
1340
 
1847
1341
  return handle;
1848
-
1849
- // 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.
1850
- void Registration()
1851
- {
1852
- Action actualDeregistration = registerAndGetDeregistration(handle);
1853
- AddDeregistration(handle, actualDeregistration);
1854
- }
1855
1342
  }
1856
1343
 
1857
1344
  /// <summary>
@@ -1873,14 +1360,14 @@ namespace DxMessaging.Core
1873
1360
  return;
1874
1361
  }
1875
1362
 
1876
- if (_registrations is { Count: > 0 })
1363
+ if (_registrationCount > 0)
1877
1364
  {
1878
1365
  // Replay staged registrations in original registration order
1879
1366
  // (via _registrationOrder) rather than in
1880
1367
  // _registrations.Values enumeration order, which permutes
1881
1368
  // after Remove/Add churn. This preserves the documented
1882
1369
  // equal-priority "registration order" dispatch contract across
1883
- // Disable()/Enable() cycles. Snapshot into _actionQueue first
1370
+ // Disable()/Enable() cycles. Snapshot into _registrationReplayQueue first
1884
1371
  // so replay tolerates re-entrant registration mutation.
1885
1372
  QueueRegistrationsInOrder();
1886
1373
  InvokeRegistrationQueueWithRollback();
@@ -1902,13 +1389,13 @@ namespace DxMessaging.Core
1902
1389
  /// </example>
1903
1390
  public void Disable()
1904
1391
  {
1905
- if (!_enabled && _deregistrations.Count == 0)
1392
+ if (!_enabled && _deregistrationCount == 0)
1906
1393
  {
1907
1394
  return;
1908
1395
  }
1909
1396
 
1910
1397
  Exception deregistrationException = InvokeDeregistrationQueue();
1911
- _enabled = _deregistrations.Count > 0;
1398
+ _enabled = _deregistrationCount > 0;
1912
1399
  if (deregistrationException != null)
1913
1400
  {
1914
1401
  ExceptionDispatchInfo.Capture(deregistrationException).Throw();
@@ -1931,13 +1418,12 @@ namespace DxMessaging.Core
1931
1418
  if (deregistrationException == null)
1932
1419
  {
1933
1420
  _enabled = false;
1934
- _registrations?.Clear();
1935
- _registrationOrder?.Clear();
1421
+ ClearRegistrationArena();
1936
1422
  ClearDiagnosticState();
1937
1423
  return;
1938
1424
  }
1939
1425
 
1940
- _enabled = _deregistrations.Count > 0;
1426
+ _enabled = _deregistrationCount > 0;
1941
1427
  PruneRegistrationStateToFailedDeregistrations();
1942
1428
  ExceptionDispatchInfo.Capture(deregistrationException).Throw();
1943
1429
  }
@@ -1960,16 +1446,21 @@ namespace DxMessaging.Core
1960
1446
  bool rebindActiveRegistrations =
1961
1447
  effectiveMode == MessageBusRebindMode.RebindActive
1962
1448
  && _enabled
1963
- && _deregistrations is { Count: > 0 };
1449
+ && _deregistrationCount > 0;
1964
1450
  if (sameBus && !rebindActiveRegistrations)
1965
1451
  {
1966
1452
  return;
1967
1453
  }
1968
1454
 
1969
1455
  IMessageBus previousMessageBus = _messageBus;
1970
- List<MessageRegistrationHandle> activeRetargetHandles = rebindActiveRegistrations
1971
- ? new List<MessageRegistrationHandle>(_deregistrations.Keys)
1972
- : null;
1456
+ List<MessageRegistrationHandle> activeRetargetHandles = null;
1457
+ if (rebindActiveRegistrations)
1458
+ {
1459
+ activeRetargetHandles = _activeRetargetHandles ??=
1460
+ new List<MessageRegistrationHandle>();
1461
+ activeRetargetHandles.Clear();
1462
+ QueueActiveHandles(activeRetargetHandles);
1463
+ }
1973
1464
  if (rebindActiveRegistrations)
1974
1465
  {
1975
1466
  Exception deregistrationException = InvokeDeregistrationQueue();
@@ -1979,7 +1470,7 @@ namespace DxMessaging.Core
1979
1470
  previousMessageBus,
1980
1471
  activeRetargetHandles
1981
1472
  );
1982
- _enabled = _deregistrations.Count > 0;
1473
+ _enabled = _deregistrationCount > 0;
1983
1474
  ExceptionDispatchInfo.Capture(deregistrationException).Throw();
1984
1475
  }
1985
1476
 
@@ -1988,7 +1479,7 @@ namespace DxMessaging.Core
1988
1479
 
1989
1480
  _messageBus = messageBus;
1990
1481
 
1991
- if (rebindActiveRegistrations && _registrations is { Count: > 0 })
1482
+ if (rebindActiveRegistrations && _registrationCount > 0)
1992
1483
  {
1993
1484
  // Mirror Enable(): rebind in original registration order so the
1994
1485
  // equal-priority dispatch order survives a bus retarget.
@@ -2007,28 +1498,145 @@ namespace DxMessaging.Core
2007
1498
  }
2008
1499
  }
2009
1500
 
2010
- private void AddDeregistration(MessageRegistrationHandle handle, Action deregistration)
1501
+ private void AddDeregistration(
1502
+ int slotIndex,
1503
+ long id,
1504
+ MessageHandler.HandlerDeregistration deregistration
1505
+ )
1506
+ {
1507
+ if (!TryGetSlot(slotIndex, id, out RegistrationSlot slot))
1508
+ {
1509
+ // The staged registration was removed or its slot was reused while its replay
1510
+ // snapshot was executing. Preserve the returned teardown under the original
1511
+ // identity: consuming it here would lose a throwing teardown instead of keeping it
1512
+ // retryable, while attaching it to the slot would target the new occupant.
1513
+ AddOrphanDeregistration(
1514
+ MessageRegistrationHandle.FromIdentity(id, slotIndex),
1515
+ deregistration
1516
+ );
1517
+ return;
1518
+ }
1519
+
1520
+ object existing = slot.Deregistration;
1521
+ if (existing == null)
1522
+ {
1523
+ _slots[slotIndex].Deregistration = deregistration;
1524
+ ++_deregistrationCount;
1525
+ return;
1526
+ }
1527
+
1528
+ if (existing is PendingDeregistration pending)
1529
+ {
1530
+ pending.Add(deregistration);
1531
+ return;
1532
+ }
1533
+
1534
+ // A second de-registration accumulated on this handle: promote the inline object to a
1535
+ // PendingDeregistration holder that preserves the ordering / partial-failure / rollback
1536
+ // semantics for the multi-de-registration (retarget-recovery replay) case.
1537
+ PendingDeregistration promoted = new();
1538
+ promoted.Add((MessageHandler.HandlerDeregistration)existing);
1539
+ promoted.Add(deregistration);
1540
+ _slots[slotIndex].Deregistration = promoted;
1541
+ }
1542
+
1543
+ private void AddOrphanDeregistration(
1544
+ MessageRegistrationHandle handle,
1545
+ MessageHandler.HandlerDeregistration deregistration
1546
+ )
1547
+ {
1548
+ Dictionary<MessageRegistrationHandle, object> orphans = _orphanDeregistrations ??=
1549
+ new Dictionary<MessageRegistrationHandle, object>();
1550
+ if (!orphans.TryGetValue(handle, out object existing))
1551
+ {
1552
+ orphans.Add(handle, deregistration);
1553
+ ++_deregistrationCount;
1554
+ return;
1555
+ }
1556
+
1557
+ if (existing is PendingDeregistration pending)
1558
+ {
1559
+ pending.Add(deregistration);
1560
+ return;
1561
+ }
1562
+
1563
+ PendingDeregistration promoted = new();
1564
+ promoted.Add((MessageHandler.HandlerDeregistration)existing);
1565
+ promoted.Add(deregistration);
1566
+ orphans[handle] = promoted;
1567
+ }
1568
+
1569
+ // Logical de-registration count for a _deregistrations value (inline object == 1).
1570
+ private static int DeregistrationCount(object value) =>
1571
+ value is PendingDeregistration pending ? pending.Count : 1;
1572
+
1573
+ // Invokes the de-registration tail [startIndex..) for a _deregistrations value. For the
1574
+ // inline object (logical Count 1, index 0): on success it is consumed (shouldRemove = true);
1575
+ // on throw it is KEPT (retryable, shouldRemove = false); a rollback pass (startIndex &gt; 0)
1576
+ // leaves the baseline entry untouched. For a holder it delegates to InvokeFrom, mutating the
1577
+ // holder in place. Mirrors the prior PendingDeregistration-only semantics exactly.
1578
+ private static Exception InvokeDeregistration(
1579
+ object value,
1580
+ int startIndex,
1581
+ out bool shouldRemove
1582
+ )
2011
1583
  {
2012
- if (!_deregistrations.TryGetValue(handle, out PendingDeregistration pending))
1584
+ if (value is PendingDeregistration pending)
1585
+ {
1586
+ Exception holderException = pending.InvokeFrom(startIndex);
1587
+ shouldRemove = pending.Count == 0;
1588
+ return holderException;
1589
+ }
1590
+
1591
+ if (startIndex > 0)
2013
1592
  {
2014
- pending = new PendingDeregistration();
2015
- _deregistrations[handle] = pending;
1593
+ // Rollback baseline pass: the inline head (logical index 0) is below the requested
1594
+ // tail, so leave it untouched.
1595
+ shouldRemove = false;
1596
+ return null;
2016
1597
  }
2017
1598
 
2018
- pending.Add(deregistration);
1599
+ try
1600
+ {
1601
+ ((MessageHandler.HandlerDeregistration)value)?.Deregister();
1602
+ shouldRemove = true;
1603
+ return null;
1604
+ }
1605
+ catch (Exception exception)
1606
+ {
1607
+ // Keep the failed de-registration (retryable), exactly as the holder form does.
1608
+ shouldRemove = false;
1609
+ return exception;
1610
+ }
2019
1611
  }
2020
1612
 
2021
1613
  private Dictionary<MessageRegistrationHandle, int> SnapshotDeregistrationCounts()
2022
1614
  {
2023
- Dictionary<MessageRegistrationHandle, int> snapshot = new(_deregistrations.Count);
2024
- foreach (
2025
- KeyValuePair<
2026
- MessageRegistrationHandle,
2027
- PendingDeregistration
2028
- > entry in _deregistrations
1615
+ Dictionary<MessageRegistrationHandle, int> snapshot = _rollbackCounts ??=
1616
+ new Dictionary<MessageRegistrationHandle, int>();
1617
+ snapshot.Clear();
1618
+ for (
1619
+ int slotIndex = _registrationHead;
1620
+ slotIndex >= 0;
1621
+ slotIndex = _slots[slotIndex].Next
2029
1622
  )
2030
1623
  {
2031
- snapshot[entry.Key] = entry.Value.Count;
1624
+ RegistrationSlot slot = _slots[slotIndex];
1625
+ if (slot.Deregistration != null)
1626
+ {
1627
+ snapshot[MessageRegistrationHandle.FromIdentity(slot.Id, slotIndex)] =
1628
+ DeregistrationCount(slot.Deregistration);
1629
+ }
1630
+ }
1631
+
1632
+ if (_orphanDeregistrations != null)
1633
+ {
1634
+ foreach (
1635
+ KeyValuePair<MessageRegistrationHandle, object> entry in _orphanDeregistrations
1636
+ )
1637
+ {
1638
+ snapshot[entry.Key] = DeregistrationCount(entry.Value);
1639
+ }
2032
1640
  }
2033
1641
 
2034
1642
  return snapshot;
@@ -2066,9 +1674,9 @@ namespace DxMessaging.Core
2066
1674
  private void RestoreRegistrationsAfterRetargetFailure(IMessageBus previousMessageBus)
2067
1675
  {
2068
1676
  _messageBus = previousMessageBus;
2069
- if (_registrations.Count == 0)
1677
+ if (_registrationCount == 0)
2070
1678
  {
2071
- _enabled = false;
1679
+ _enabled = _deregistrationCount > 0;
2072
1680
  return;
2073
1681
  }
2074
1682
 
@@ -2080,7 +1688,7 @@ namespace DxMessaging.Core
2080
1688
  }
2081
1689
  catch (Exception restoreException)
2082
1690
  {
2083
- _enabled = _deregistrations.Count > 0;
1691
+ _enabled = _deregistrationCount > 0;
2084
1692
  if (MessagingDebug.enabled)
2085
1693
  {
2086
1694
  MessagingDebug.Log(
@@ -2094,14 +1702,16 @@ namespace DxMessaging.Core
2094
1702
 
2095
1703
  private void QueueRegistrationsInOrder()
2096
1704
  {
2097
- _actionQueue.Clear();
2098
- int registrationCount = _registrationOrder.Count;
2099
- for (int i = 0; i < registrationCount; ++i)
1705
+ List<StagedRegistration> queue = GetRegistrationReplayQueue();
1706
+ queue.Clear();
1707
+ for (
1708
+ int slotIndex = _registrationHead;
1709
+ slotIndex >= 0;
1710
+ slotIndex = _slots[slotIndex].Next
1711
+ )
2100
1712
  {
2101
- if (_registrations.TryGetValue(_registrationOrder[i], out Action registration))
2102
- {
2103
- _actionQueue.Add(registration);
2104
- }
1713
+ RegistrationSlot slot = _slots[slotIndex];
1714
+ queue.Add(new StagedRegistration(slotIndex, slot.Id, slot.Registration));
2105
1715
  }
2106
1716
  }
2107
1717
 
@@ -2109,55 +1719,87 @@ namespace DxMessaging.Core
2109
1719
  List<MessageRegistrationHandle> activeRetargetHandles
2110
1720
  )
2111
1721
  {
2112
- _actionQueue.Clear();
2113
- int registrationCount = _registrationOrder.Count;
2114
- for (int i = 0; i < registrationCount; ++i)
1722
+ List<StagedRegistration> queue = GetRegistrationReplayQueue();
1723
+ queue.Clear();
1724
+ for (
1725
+ int slotIndex = _registrationHead;
1726
+ slotIndex >= 0;
1727
+ slotIndex = _slots[slotIndex].Next
1728
+ )
2115
1729
  {
2116
- MessageRegistrationHandle handle = _registrationOrder[i];
2117
- if (
2118
- !activeRetargetHandles.Contains(handle)
2119
- || _deregistrations.ContainsKey(handle)
2120
- || !_registrations.TryGetValue(handle, out Action registration)
2121
- )
1730
+ RegistrationSlot slot = _slots[slotIndex];
1731
+ MessageRegistrationHandle handle = slot.Registration.Handle;
1732
+ if (!activeRetargetHandles.Contains(handle) || slot.Deregistration != null)
2122
1733
  {
2123
1734
  continue;
2124
1735
  }
2125
1736
 
2126
- _actionQueue.Add(registration);
1737
+ queue.Add(new StagedRegistration(slotIndex, slot.Id, slot.Registration));
2127
1738
  }
2128
1739
  }
2129
1740
 
2130
1741
  private void QueueRegistrationsWithoutRetryableDeregistrationsInOrder()
2131
1742
  {
2132
- _actionQueue.Clear();
2133
- int registrationCount = _registrationOrder.Count;
2134
- for (int i = 0; i < registrationCount; ++i)
1743
+ List<StagedRegistration> queue = GetRegistrationReplayQueue();
1744
+ queue.Clear();
1745
+ for (
1746
+ int slotIndex = _registrationHead;
1747
+ slotIndex >= 0;
1748
+ slotIndex = _slots[slotIndex].Next
1749
+ )
2135
1750
  {
2136
- MessageRegistrationHandle handle = _registrationOrder[i];
2137
- if (
2138
- _deregistrations.ContainsKey(handle)
2139
- || !_registrations.TryGetValue(handle, out Action registration)
2140
- )
1751
+ RegistrationSlot slot = _slots[slotIndex];
1752
+ if (slot.Deregistration != null)
2141
1753
  {
2142
1754
  continue;
2143
1755
  }
2144
1756
 
2145
- _actionQueue.Add(registration);
1757
+ queue.Add(new StagedRegistration(slotIndex, slot.Id, slot.Registration));
2146
1758
  }
2147
1759
  }
2148
1760
 
2149
- private void InvokeActionQueue()
1761
+ private void InvokeRegistrationReplayQueue()
2150
1762
  {
2151
1763
  try
2152
1764
  {
2153
- foreach (Action action in _actionQueue)
1765
+ foreach (StagedRegistration staged in GetRegistrationReplayQueue())
2154
1766
  {
2155
- action?.Invoke();
1767
+ Registration registration = staged.Registration;
1768
+ if (registration == null)
1769
+ {
1770
+ continue;
1771
+ }
1772
+
1773
+ MessageHandler.HandlerDeregistration actualDeregistration =
1774
+ registration.Register();
1775
+ AddDeregistration(staged.Slot, staged.Id, actualDeregistration);
2156
1776
  }
2157
1777
  }
2158
1778
  finally
2159
1779
  {
2160
- _actionQueue.Clear();
1780
+ _registrationReplayQueue?.Clear();
1781
+ }
1782
+ }
1783
+
1784
+ /// <summary>
1785
+ /// A staged registration captured for replay: the handle plus the unified
1786
+ /// per-handle <see cref="Registration"/> object that (re)registers the handler
1787
+ /// and returns its <see cref="MessageHandler.HandlerDeregistration"/>.
1788
+ /// Snapshotting the object reference (rather than a per-registration wrapper
1789
+ /// closure) preserves the original re-entrancy semantics while keeping the
1790
+ /// staging state in a single object per registration.
1791
+ /// </summary>
1792
+ private readonly struct StagedRegistration
1793
+ {
1794
+ public readonly int Slot;
1795
+ public readonly long Id;
1796
+ public readonly Registration Registration;
1797
+
1798
+ public StagedRegistration(int slot, long id, Registration registration)
1799
+ {
1800
+ Slot = slot;
1801
+ Id = id;
1802
+ Registration = registration;
2161
1803
  }
2162
1804
  }
2163
1805
 
@@ -2165,22 +1807,76 @@ namespace DxMessaging.Core
2165
1807
  Dictionary<MessageRegistrationHandle, int> baselineCounts = null
2166
1808
  )
2167
1809
  {
2168
- if (_deregistrations.Count == 0)
1810
+ if (_deregistrationCount == 0)
2169
1811
  {
2170
1812
  return null;
2171
1813
  }
2172
1814
 
2173
1815
  bool scopedToAddedDeregistrations = baselineCounts != null;
2174
- _handleQueue.Clear();
2175
- _handleQueue.AddRange(_deregistrations.Keys);
1816
+ List<TeardownIdentity> teardownQueue = _teardownQueue ??= new List<TeardownIdentity>();
1817
+ teardownQueue.Clear();
1818
+ for (
1819
+ int slotIndex = _registrationHead;
1820
+ slotIndex >= 0;
1821
+ slotIndex = _slots[slotIndex].Next
1822
+ )
1823
+ {
1824
+ RegistrationSlot slot = _slots[slotIndex];
1825
+ if (slot.Deregistration != null)
1826
+ {
1827
+ teardownQueue.Add(new TeardownIdentity(slotIndex, slot.Id, isOrphan: false));
1828
+ }
1829
+ }
1830
+
1831
+ bool hasOrphanSnapshot =
1832
+ _orphanDeregistrations != null && _orphanDeregistrations.Count > 0;
1833
+ if (hasOrphanSnapshot)
1834
+ {
1835
+ foreach (MessageRegistrationHandle handle in _orphanDeregistrations.Keys)
1836
+ {
1837
+ teardownQueue.Add(new TeardownIdentity(handle.Slot, handle.Id, isOrphan: true));
1838
+ }
1839
+ }
1840
+
1841
+ // Global handle ids are monotonic and therefore encode original registration order.
1842
+ // Capture and sort BEFORE invoking anything: reentrant teardown additions are deferred
1843
+ // to the next pass rather than joining this snapshot.
1844
+ if (hasOrphanSnapshot)
1845
+ {
1846
+ teardownQueue.Sort(TeardownIdentityComparer.Instance);
1847
+ }
2176
1848
  Exception firstException = null;
2177
1849
  try
2178
1850
  {
2179
- foreach (MessageRegistrationHandle handle in _handleQueue)
1851
+ foreach (TeardownIdentity identity in teardownQueue)
2180
1852
  {
2181
- if (!_deregistrations.TryGetValue(handle, out PendingDeregistration pending))
1853
+ MessageRegistrationHandle handle = MessageRegistrationHandle.FromIdentity(
1854
+ identity.Id,
1855
+ identity.Slot
1856
+ );
1857
+ object value;
1858
+ if (identity.IsOrphan)
2182
1859
  {
2183
- continue;
1860
+ if (
1861
+ _orphanDeregistrations == null
1862
+ || !_orphanDeregistrations.TryGetValue(handle, out value)
1863
+ )
1864
+ {
1865
+ continue;
1866
+ }
1867
+ }
1868
+ else
1869
+ {
1870
+ if (!TryGetSlot(identity.Slot, identity.Id, out RegistrationSlot slot))
1871
+ {
1872
+ continue;
1873
+ }
1874
+
1875
+ value = slot.Deregistration;
1876
+ if (value == null)
1877
+ {
1878
+ continue;
1879
+ }
2184
1880
  }
2185
1881
 
2186
1882
  int startIndex = 0;
@@ -2191,27 +1887,42 @@ namespace DxMessaging.Core
2191
1887
  startIndex = baselineCount;
2192
1888
  }
2193
1889
 
2194
- if (startIndex >= pending.Count)
1890
+ if (startIndex >= DeregistrationCount(value))
2195
1891
  {
2196
1892
  continue;
2197
1893
  }
2198
1894
  }
2199
1895
 
2200
- Exception exception = pending.InvokeFrom(startIndex);
1896
+ Exception exception = InvokeDeregistration(
1897
+ value,
1898
+ startIndex,
1899
+ out bool shouldRemove
1900
+ );
2201
1901
  if (exception != null)
2202
1902
  {
2203
1903
  firstException ??= exception;
2204
1904
  }
2205
1905
 
2206
- if (pending.Count == 0)
1906
+ if (shouldRemove)
2207
1907
  {
2208
- _deregistrations.Remove(handle);
1908
+ if (identity.IsOrphan)
1909
+ {
1910
+ _orphanDeregistrations.Remove(handle);
1911
+ --_deregistrationCount;
1912
+ }
1913
+ else if (
1914
+ TryGetSlot(identity.Slot, identity.Id, out RegistrationSlot current)
1915
+ )
1916
+ {
1917
+ _slots[identity.Slot].Deregistration = null;
1918
+ --_deregistrationCount;
1919
+ }
2209
1920
  }
2210
1921
  }
2211
1922
  }
2212
1923
  finally
2213
1924
  {
2214
- _handleQueue.Clear();
1925
+ teardownQueue.Clear();
2215
1926
  }
2216
1927
 
2217
1928
  return firstException;
@@ -2223,12 +1934,12 @@ namespace DxMessaging.Core
2223
1934
  SnapshotDeregistrationCounts();
2224
1935
  try
2225
1936
  {
2226
- InvokeActionQueue();
1937
+ InvokeRegistrationReplayQueue();
2227
1938
  }
2228
1939
  catch (Exception exception)
2229
1940
  {
2230
1941
  RollBackDeregistrationsAfterRegistrationFailure(rollbackBaseline);
2231
- _enabled = _deregistrations.Count > 0;
1942
+ _enabled = _deregistrationCount > 0;
2232
1943
  ExceptionDispatchInfo.Capture(exception).Throw();
2233
1944
  throw;
2234
1945
  }
@@ -2238,7 +1949,7 @@ namespace DxMessaging.Core
2238
1949
  Dictionary<MessageRegistrationHandle, int> rollbackBaseline
2239
1950
  )
2240
1951
  {
2241
- if (_deregistrations.Count == 0)
1952
+ if (_deregistrationCount == 0)
2242
1953
  {
2243
1954
  return;
2244
1955
  }
@@ -2256,26 +1967,28 @@ namespace DxMessaging.Core
2256
1967
 
2257
1968
  private void ClearDiagnosticState()
2258
1969
  {
2259
- _metadata.Clear();
2260
- _callCounts.Clear();
2261
- _emissionBuffer.Clear();
1970
+ // Clear through the backing fields so an inactive (never-materialized)
1971
+ // diagnostics collection is not allocated merely to be emptied.
1972
+ _callCountsBacking?.Clear();
1973
+ _emissionBufferBacking?.Clear();
2262
1974
  }
2263
1975
 
2264
1976
  private void PruneRegistrationStateToFailedDeregistrations()
2265
1977
  {
2266
- for (int i = _registrationOrder.Count - 1; i >= 0; --i)
1978
+ int slotIndex = _registrationTail;
1979
+ while (slotIndex >= 0)
2267
1980
  {
2268
- MessageRegistrationHandle handle = _registrationOrder[i];
2269
- if (_deregistrations.ContainsKey(handle))
1981
+ int previous = _slots[slotIndex].Previous;
1982
+ if (_slots[slotIndex].Deregistration == null)
2270
1983
  {
2271
- continue;
1984
+ RemoveRegistrationState(slotIndex, _slots[slotIndex].Id);
2272
1985
  }
2273
1986
 
2274
- RemoveRegistrationState(handle);
1987
+ slotIndex = previous;
2275
1988
  }
2276
1989
 
2277
- _emissionBuffer.Clear();
2278
- if (_registrations.Count == 0)
1990
+ _emissionBufferBacking?.Clear();
1991
+ if (_registrationCount == 0)
2279
1992
  {
2280
1993
  ClearDiagnosticState();
2281
1994
  }
@@ -2283,16 +1996,51 @@ namespace DxMessaging.Core
2283
1996
 
2284
1997
  private bool RemoveRegistrationState(MessageRegistrationHandle handle)
2285
1998
  {
2286
- bool removedRegistration = _registrations.Remove(handle);
2287
- _ = _registrationOrder.Remove(handle);
2288
- _ = _metadata.Remove(handle);
2289
- _ = _callCounts.Remove(handle);
2290
- if (removedRegistration && _registrations.Count == 0)
1999
+ return RemoveRegistrationState(handle.Slot, handle.Id);
2000
+ }
2001
+
2002
+ private bool RemoveRegistrationState(int slotIndex, long id)
2003
+ {
2004
+ if (!TryGetSlot(slotIndex, id, out RegistrationSlot slot))
2005
+ {
2006
+ return false;
2007
+ }
2008
+
2009
+ if (slot.Previous >= 0)
2010
+ {
2011
+ _slots[slot.Previous].Next = slot.Next;
2012
+ }
2013
+ else
2014
+ {
2015
+ _registrationHead = slot.Next;
2016
+ }
2017
+
2018
+ if (slot.Next >= 0)
2019
+ {
2020
+ _slots[slot.Next].Previous = slot.Previous;
2021
+ }
2022
+ else
2023
+ {
2024
+ _registrationTail = slot.Previous;
2025
+ }
2026
+
2027
+ MessageRegistrationHandle handle = MessageRegistrationHandle.FromIdentity(
2028
+ id,
2029
+ slotIndex
2030
+ );
2031
+ _callCountsBacking?.Remove(handle);
2032
+ _slots[slotIndex] = default;
2033
+ _slots[slotIndex].Previous = -1;
2034
+ _slots[slotIndex].Next = -1;
2035
+ _slots[slotIndex].NextFree = _freeSlotHead;
2036
+ _freeSlotHead = slotIndex;
2037
+ --_registrationCount;
2038
+ if (_registrationCount == 0)
2291
2039
  {
2292
2040
  ClearDiagnosticState();
2293
2041
  }
2294
2042
 
2295
- return removedRegistration;
2043
+ return true;
2296
2044
  }
2297
2045
 
2298
2046
  /// <summary>
@@ -2307,12 +2055,27 @@ namespace DxMessaging.Core
2307
2055
  /// </example>
2308
2056
  public void RemoveRegistration(MessageRegistrationHandle handle)
2309
2057
  {
2310
- if (_deregistrations.TryGetValue(handle, out PendingDeregistration pending))
2058
+ if (!TryGetSlot(handle.Slot, handle.Id, out RegistrationSlot slot))
2311
2059
  {
2312
- Exception deregistrationException = pending.InvokeFrom(0);
2313
- if (pending.Count == 0)
2060
+ RemoveOrphanDeregistration(handle);
2061
+ return;
2062
+ }
2063
+
2064
+ object value = slot.Deregistration;
2065
+ if (value != null)
2066
+ {
2067
+ Exception deregistrationException = InvokeDeregistration(
2068
+ value,
2069
+ 0,
2070
+ out bool shouldRemove
2071
+ );
2072
+ if (shouldRemove)
2314
2073
  {
2315
- _deregistrations.Remove(handle);
2074
+ if (TryGetSlot(handle.Slot, handle.Id, out RegistrationSlot current))
2075
+ {
2076
+ _slots[handle.Slot].Deregistration = null;
2077
+ --_deregistrationCount;
2078
+ }
2316
2079
  }
2317
2080
 
2318
2081
  if (deregistrationException != null)
@@ -2327,15 +2090,292 @@ namespace DxMessaging.Core
2327
2090
  RemoveRegistrationState(handle);
2328
2091
  }
2329
2092
 
2093
+ private void RemoveOrphanDeregistration(MessageRegistrationHandle handle)
2094
+ {
2095
+ if (
2096
+ _orphanDeregistrations == null
2097
+ || !_orphanDeregistrations.TryGetValue(handle, out object value)
2098
+ )
2099
+ {
2100
+ return;
2101
+ }
2102
+
2103
+ Exception exception = InvokeDeregistration(value, 0, out bool shouldRemove);
2104
+ if (shouldRemove)
2105
+ {
2106
+ _orphanDeregistrations.Remove(handle);
2107
+ --_deregistrationCount;
2108
+ }
2109
+
2110
+ if (exception != null)
2111
+ {
2112
+ ExceptionDispatchInfo.Capture(exception).Throw();
2113
+ }
2114
+ }
2115
+
2116
+ private int AllocateSlot()
2117
+ {
2118
+ if (_freeSlotHead < 0)
2119
+ {
2120
+ int oldLength = _slots.Length;
2121
+ int newLength = oldLength == 0 ? 4 : oldLength << 1;
2122
+ Array.Resize(ref _slots, newLength);
2123
+ for (int i = newLength - 1; i >= oldLength; --i)
2124
+ {
2125
+ _slots[i].Previous = -1;
2126
+ _slots[i].Next = -1;
2127
+ _slots[i].NextFree = _freeSlotHead;
2128
+ _freeSlotHead = i;
2129
+ }
2130
+ }
2131
+
2132
+ int slotIndex = _freeSlotHead;
2133
+ _freeSlotHead = _slots[slotIndex].NextFree;
2134
+ return slotIndex;
2135
+ }
2136
+
2137
+ private bool TryGetSlot(int slotIndex, long id, out RegistrationSlot slot)
2138
+ {
2139
+ if ((uint)slotIndex < (uint)_slots.Length)
2140
+ {
2141
+ slot = _slots[slotIndex];
2142
+ if (slot.Registration != null && slot.Id == id)
2143
+ {
2144
+ return true;
2145
+ }
2146
+ }
2147
+
2148
+ slot = default;
2149
+ return false;
2150
+ }
2151
+
2152
+ private void ClearRegistrationArena()
2153
+ {
2154
+ if (_slots.Length > 0)
2155
+ {
2156
+ Array.Clear(_slots, 0, _slots.Length);
2157
+ _freeSlotHead = -1;
2158
+ for (int i = _slots.Length - 1; i >= 0; --i)
2159
+ {
2160
+ _slots[i].Previous = -1;
2161
+ _slots[i].Next = -1;
2162
+ _slots[i].NextFree = _freeSlotHead;
2163
+ _freeSlotHead = i;
2164
+ }
2165
+ }
2166
+ else
2167
+ {
2168
+ _freeSlotHead = -1;
2169
+ }
2170
+
2171
+ _registrationHead = -1;
2172
+ _registrationTail = -1;
2173
+ _registrationCount = 0;
2174
+ _deregistrationCount = 0;
2175
+ _registrationReplayQueue?.Clear();
2176
+ _teardownQueue?.Clear();
2177
+ _rollbackCounts?.Clear();
2178
+ _activeRetargetHandles?.Clear();
2179
+ _orphanDeregistrations?.Clear();
2180
+ }
2181
+
2182
+ private List<StagedRegistration> GetRegistrationReplayQueue()
2183
+ {
2184
+ return _registrationReplayQueue ??= new List<StagedRegistration>();
2185
+ }
2186
+
2187
+ private void QueueActiveHandles(List<MessageRegistrationHandle> destination)
2188
+ {
2189
+ for (
2190
+ int slotIndex = _registrationHead;
2191
+ slotIndex >= 0;
2192
+ slotIndex = _slots[slotIndex].Next
2193
+ )
2194
+ {
2195
+ RegistrationSlot slot = _slots[slotIndex];
2196
+ if (slot.Deregistration != null)
2197
+ {
2198
+ destination.Add(MessageRegistrationHandle.FromIdentity(slot.Id, slotIndex));
2199
+ }
2200
+ }
2201
+
2202
+ if (_orphanDeregistrations != null)
2203
+ {
2204
+ destination.AddRange(_orphanDeregistrations.Keys);
2205
+ }
2206
+ }
2207
+
2208
+ private struct RegistrationSlot
2209
+ {
2210
+ public Registration Registration;
2211
+ public object Deregistration;
2212
+ public long Id;
2213
+ public int Previous;
2214
+ public int Next;
2215
+ public int NextFree;
2216
+ }
2217
+
2218
+ private readonly struct TeardownIdentity
2219
+ {
2220
+ public readonly int Slot;
2221
+ public readonly long Id;
2222
+ public readonly bool IsOrphan;
2223
+
2224
+ public TeardownIdentity(int slot, long id, bool isOrphan)
2225
+ {
2226
+ Slot = slot;
2227
+ Id = id;
2228
+ IsOrphan = isOrphan;
2229
+ }
2230
+ }
2231
+
2232
+ private sealed class TeardownIdentityComparer : IComparer<TeardownIdentity>
2233
+ {
2234
+ internal static readonly TeardownIdentityComparer Instance = new();
2235
+
2236
+ public int Compare(TeardownIdentity left, TeardownIdentity right)
2237
+ {
2238
+ return left.Id.CompareTo(right.Id);
2239
+ }
2240
+ }
2241
+
2242
+ internal readonly struct RegistrationMetadataView
2243
+ : IEnumerable<KeyValuePair<MessageRegistrationHandle, MessageRegistrationMetadata>>
2244
+ {
2245
+ private readonly MessageRegistrationToken _token;
2246
+
2247
+ internal RegistrationMetadataView(MessageRegistrationToken token)
2248
+ {
2249
+ _token = token;
2250
+ }
2251
+
2252
+ public int Count => _token._registrationCount;
2253
+
2254
+ public bool ContainsKey(MessageRegistrationHandle handle)
2255
+ {
2256
+ return _token.TryGetSlot(handle.Slot, handle.Id, out RegistrationSlot slot);
2257
+ }
2258
+
2259
+ public Enumerator GetEnumerator()
2260
+ {
2261
+ return new Enumerator(_token);
2262
+ }
2263
+
2264
+ IEnumerator<
2265
+ KeyValuePair<MessageRegistrationHandle, MessageRegistrationMetadata>
2266
+ > IEnumerable<
2267
+ KeyValuePair<MessageRegistrationHandle, MessageRegistrationMetadata>
2268
+ >.GetEnumerator()
2269
+ {
2270
+ return GetEnumerator();
2271
+ }
2272
+
2273
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
2274
+ {
2275
+ return GetEnumerator();
2276
+ }
2277
+
2278
+ internal struct Enumerator
2279
+ : IEnumerator<KeyValuePair<MessageRegistrationHandle, MessageRegistrationMetadata>>
2280
+ {
2281
+ private readonly MessageRegistrationToken _token;
2282
+ private int _next;
2283
+ private KeyValuePair<
2284
+ MessageRegistrationHandle,
2285
+ MessageRegistrationMetadata
2286
+ > _current;
2287
+
2288
+ internal Enumerator(MessageRegistrationToken token)
2289
+ {
2290
+ _token = token;
2291
+ _next = token._registrationHead;
2292
+ _current = default;
2293
+ }
2294
+
2295
+ public KeyValuePair<
2296
+ MessageRegistrationHandle,
2297
+ MessageRegistrationMetadata
2298
+ > Current => _current;
2299
+
2300
+ object System.Collections.IEnumerator.Current => _current;
2301
+
2302
+ public bool MoveNext()
2303
+ {
2304
+ if (_next < 0)
2305
+ {
2306
+ return false;
2307
+ }
2308
+
2309
+ int slotIndex = _next;
2310
+ RegistrationSlot slot = _token._slots[slotIndex];
2311
+ _next = slot.Next;
2312
+ _current = new KeyValuePair<
2313
+ MessageRegistrationHandle,
2314
+ MessageRegistrationMetadata
2315
+ >(
2316
+ MessageRegistrationHandle.FromIdentity(slot.Id, slotIndex),
2317
+ slot.Registration.Metadata
2318
+ );
2319
+ return true;
2320
+ }
2321
+
2322
+ public void Reset()
2323
+ {
2324
+ _next = _token._registrationHead;
2325
+ _current = default;
2326
+ }
2327
+
2328
+ public void Dispose() { }
2329
+ }
2330
+ }
2331
+
2332
+ // Holds the live de-registration Actions for a single handle in the MULTI-de-registration
2333
+ // case (2+). The common case is EXACTLY ONE de-registration per handle (the existing
2334
+ // Registration object executes its embedded teardown state); that Registration is stored
2335
+ // INLINE as the slot value, so the common path allocates no
2336
+ // PendingDeregistration object at all (see AddDeregistration / InvokeDeregistration). This
2337
+ // holder is allocated only when a rare second de-registration accumulates on the same
2338
+ // handle -- a re-entrant retarget-recovery replay can stage one beyond the rollback
2339
+ // baseline -- at which point the inline Registration is promoted into this holder's head
2340
+ // and the second spills to a lazily-allocated overflow list. This stays a class (mutated in
2341
+ // place through the _deregistrations dictionary), so reference semantics and every
2342
+ // call site are unchanged; only the storage shape changed.
2343
+ //
2344
+ // Invariant: when Count > 0 the head lives in _hasHead/_head and is the LOGICAL
2345
+ // FIRST entry; any further entries follow in _overflow in insertion order. Add
2346
+ // appends to the logical tail; InvokeFrom invokes a contiguous logical tail
2347
+ // [startIndex..Count), removing each success and KEEPING each failure (retryable),
2348
+ // then promotes the first surviving overflow entry into the head slot if the head
2349
+ // was consumed -- preserving the exact ordering, partial-failure, and
2350
+ // rollback-baseline (startIndex) semantics the List<Action> form had.
2330
2351
  private sealed class PendingDeregistration
2331
2352
  {
2332
- private readonly List<Action> _actions = new();
2353
+ private MessageHandler.HandlerDeregistration _head;
2354
+ private bool _hasHead;
2355
+ private List<MessageHandler.HandlerDeregistration> _overflow;
2333
2356
 
2334
- internal int Count => _actions.Count;
2357
+ internal int Count => (_hasHead ? 1 : 0) + (_overflow?.Count ?? 0);
2335
2358
 
2336
- internal void Add(Action action)
2359
+ internal void Add(MessageHandler.HandlerDeregistration action)
2337
2360
  {
2338
- _actions.Add(action);
2361
+ // Fill the inline head ONLY when nothing is stored. The empty-overflow
2362
+ // clause matters during the transient window inside InvokeFrom where the
2363
+ // head has been consumed but overflow survivors remain (before they are
2364
+ // promoted): a re-entrant Add then appends to the logical TAIL (overflow),
2365
+ // exactly as the List form did, rather than jumping the new entry ahead of
2366
+ // the survivors into the head slot. The overflow loop re-reads its Count,
2367
+ // so such a tail-appended entry is still invoked in the same pass -- the
2368
+ // List form's behavior preserved. (The stored Actions are pure bus
2369
+ // de-registration callbacks, so this re-entrancy is not reachable through
2370
+ // the public API today; the guard keeps the invariant honest regardless.)
2371
+ if (!_hasHead && (_overflow == null || _overflow.Count == 0))
2372
+ {
2373
+ _head = action;
2374
+ _hasHead = true;
2375
+ return;
2376
+ }
2377
+
2378
+ (_overflow ??= new List<MessageHandler.HandlerDeregistration>()).Add(action);
2339
2379
  }
2340
2380
 
2341
2381
  internal Exception InvokeFrom(int startIndex)
@@ -2346,21 +2386,1018 @@ namespace DxMessaging.Core
2346
2386
  }
2347
2387
 
2348
2388
  Exception firstException = null;
2349
- for (int i = startIndex; i < _actions.Count; )
2389
+
2390
+ // Logical index 0 is the inline head; logical indices 1.. are _overflow.
2391
+ // Invoke the head only when it is within the requested tail (a rollback
2392
+ // pass with startIndex > 0 must leave the baseline head untouched).
2393
+ if (_hasHead && startIndex <= 0)
2350
2394
  {
2351
2395
  try
2352
2396
  {
2353
- _actions[i]?.Invoke();
2354
- _actions.RemoveAt(i);
2397
+ _head?.Deregister();
2398
+ _head = null;
2399
+ _hasHead = false;
2355
2400
  }
2356
2401
  catch (Exception exception)
2357
2402
  {
2403
+ // Keep the failed head (retryable), exactly as the List form did
2404
+ // by advancing past a throwing entry instead of removing it.
2358
2405
  firstException ??= exception;
2359
- ++i;
2360
2406
  }
2361
2407
  }
2362
2408
 
2363
- return firstException;
2409
+ if (_overflow is { Count: > 0 })
2410
+ {
2411
+ // Overflow entry j has logical index 1 + j; invoke those at or past
2412
+ // startIndex. Remove successes, keep failures (retryable).
2413
+ int overflowStart = startIndex <= 1 ? 0 : startIndex - 1;
2414
+ for (int j = overflowStart; j < _overflow.Count; )
2415
+ {
2416
+ try
2417
+ {
2418
+ _overflow[j]?.Deregister();
2419
+ _overflow.RemoveAt(j);
2420
+ }
2421
+ catch (Exception exception)
2422
+ {
2423
+ firstException ??= exception;
2424
+ ++j;
2425
+ }
2426
+ }
2427
+ }
2428
+
2429
+ // If the head was consumed but overflow survivors remain, promote the
2430
+ // first survivor into the head slot so the head is always the logical
2431
+ // first entry (keeping Count and future Adds consistent).
2432
+ if (!_hasHead && _overflow is { Count: > 0 })
2433
+ {
2434
+ _head = _overflow[0];
2435
+ _hasHead = true;
2436
+ _overflow.RemoveAt(0);
2437
+ }
2438
+
2439
+ return firstException;
2440
+ }
2441
+ }
2442
+
2443
+ // Discriminates the per-handle Registration object's behaviour. Each value
2444
+ // pins (a) which MessageHandler.Register* method the kind-switch in
2445
+ // Registration.Register() calls and (b) which augmented-invoker body shape
2446
+ // (user-delegate type + by-value vs by-ref call) the bound FastHandler/
2447
+ // FastHandlerWithContext delegate runs. The values map 1:1 onto the former
2448
+ // per-method staging lambdas; the *Action / *Fast suffix mirrors the two
2449
+ // public overloads (Action<T>/Action<InstanceId,T> vs FastHandler<T>/
2450
+ // FastHandlerWithContext<T>) that previously had distinct AugmentedHandler
2451
+ // local functions.
2452
+ private enum RegistrationKind
2453
+ {
2454
+ TargetedHandlerAction,
2455
+ TargetedHandlerFast,
2456
+ TargetedPostProcessorAction,
2457
+ TargetedPostProcessorFast,
2458
+ TargetedWithoutTargetingAction,
2459
+ TargetedWithoutTargetingFast,
2460
+ TargetedWithoutTargetingPostProcessorAction,
2461
+ TargetedWithoutTargetingPostProcessorFast,
2462
+ TargetedInterceptor,
2463
+
2464
+ UntargetedHandlerAction,
2465
+ UntargetedHandlerFast,
2466
+ UntargetedPostProcessorFast,
2467
+ UntargetedInterceptor,
2468
+
2469
+ BroadcastHandlerAction,
2470
+ BroadcastHandlerFast,
2471
+ BroadcastPostProcessorAction,
2472
+ BroadcastPostProcessorFast,
2473
+ BroadcastWithoutSourceAction,
2474
+ BroadcastWithoutSourceFast,
2475
+ BroadcastWithoutSourcePostProcessorAction,
2476
+ BroadcastWithoutSourcePostProcessorFast,
2477
+ BroadcastInterceptor,
2478
+
2479
+ GlobalAcceptAllAction,
2480
+ GlobalAcceptAllFast,
2481
+ }
2482
+
2483
+ // The unified per-handle staging object. Replaces, per registration, the old
2484
+ // staging Func<handle, HandlerDeregistration> display class + delegate AND the
2485
+ // nested AugmentedHandler local-function delegate. The captured staging state
2486
+ // (owning token, handle, user handler delegate, target/source InstanceId,
2487
+ // priority, kind) lives in plain fields; the diagnostics-augmented invoker is
2488
+ // an instance method bound to this object (so MessageHandler still receives a
2489
+ // delegate on the hot path -- no virtual/interface call per dispatch).
2490
+ //
2491
+ // The base is non-generic so _registrations / the replay queue can hold every
2492
+ // registration polymorphically. The constrained MessageHandler.Register*<T>
2493
+ // calls each require T : ITargetedMessage / IUntargetedMessage /
2494
+ // IBroadcastMessage, which a single Registration<T> (T : IMessage) cannot
2495
+ // satisfy; the three concrete subclasses below carry the matching constraint
2496
+ // and each run a kind-SWITCH over only the kinds in their family (per the
2497
+ // user-accepted "unified object, accept the kind-switch" design -- NOT ~14
2498
+ // subclasses). GlobalAcceptAllRegistration is non-generic (its sub-handlers
2499
+ // are the fixed IMessage facades).
2500
+ private abstract class Registration : MessageHandler.HandlerDeregistration
2501
+ {
2502
+ protected readonly MessageRegistrationToken Token;
2503
+ public MessageRegistrationHandle Handle;
2504
+ private IMessageBus _registeredMessageBus;
2505
+
2506
+ protected Registration(MessageRegistrationToken token)
2507
+ {
2508
+ Token = token;
2509
+ }
2510
+
2511
+ // Kind-switch: (re)register on the bus and return the matching
2512
+ // HandlerDeregistration, exactly as the former staging lambda did. Reads
2513
+ // the token's CURRENT _messageBus (so Enable()/RetargetMessageBus replay
2514
+ // binds to the active bus, unchanged from when _messageBus was captured by
2515
+ // the staging closure at call time -- the staging closure also read the
2516
+ // field, not a snapshot).
2517
+ public abstract MessageHandler.HandlerDeregistration Register();
2518
+
2519
+ public abstract MessageRegistrationMetadata Metadata { get; }
2520
+
2521
+ protected static MessageRegistrationType GetRegistrationType(RegistrationKind kind)
2522
+ {
2523
+ switch (kind)
2524
+ {
2525
+ case RegistrationKind.TargetedHandlerAction:
2526
+ case RegistrationKind.TargetedHandlerFast:
2527
+ return MessageRegistrationType.Targeted;
2528
+ case RegistrationKind.TargetedPostProcessorAction:
2529
+ case RegistrationKind.TargetedPostProcessorFast:
2530
+ return MessageRegistrationType.TargetedPostProcessor;
2531
+ case RegistrationKind.TargetedWithoutTargetingAction:
2532
+ case RegistrationKind.TargetedWithoutTargetingFast:
2533
+ return MessageRegistrationType.TargetedWithoutTargeting;
2534
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorAction:
2535
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorFast:
2536
+ return MessageRegistrationType.TargetedWithoutTargetingPostProcessor;
2537
+ case RegistrationKind.TargetedInterceptor:
2538
+ return MessageRegistrationType.TargetedInterceptor;
2539
+ case RegistrationKind.UntargetedHandlerAction:
2540
+ case RegistrationKind.UntargetedHandlerFast:
2541
+ return MessageRegistrationType.Untargeted;
2542
+ case RegistrationKind.UntargetedPostProcessorFast:
2543
+ return MessageRegistrationType.UntargetedPostProcessor;
2544
+ case RegistrationKind.UntargetedInterceptor:
2545
+ return MessageRegistrationType.UntargetedInterceptor;
2546
+ case RegistrationKind.BroadcastHandlerAction:
2547
+ case RegistrationKind.BroadcastHandlerFast:
2548
+ return MessageRegistrationType.Broadcast;
2549
+ case RegistrationKind.BroadcastPostProcessorAction:
2550
+ case RegistrationKind.BroadcastPostProcessorFast:
2551
+ return MessageRegistrationType.BroadcastPostProcessor;
2552
+ case RegistrationKind.BroadcastWithoutSourceAction:
2553
+ case RegistrationKind.BroadcastWithoutSourceFast:
2554
+ return MessageRegistrationType.BroadcastWithoutSource;
2555
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorAction:
2556
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorFast:
2557
+ return MessageRegistrationType.BroadcastWithoutSourcePostProcessor;
2558
+ case RegistrationKind.BroadcastInterceptor:
2559
+ return MessageRegistrationType.BroadcastInterceptor;
2560
+ case RegistrationKind.GlobalAcceptAllAction:
2561
+ case RegistrationKind.GlobalAcceptAllFast:
2562
+ return MessageRegistrationType.GlobalAcceptAll;
2563
+ default:
2564
+ return MessageRegistrationType.None;
2565
+ }
2566
+ }
2567
+
2568
+ protected IMessageBus ResolveRegistrationMessageBus()
2569
+ {
2570
+ IMessageBus messageBus = Token._messageBus;
2571
+ _registeredMessageBus = messageBus ?? Token._messageHandler?.DefaultMessageBus;
2572
+ return messageBus;
2573
+ }
2574
+
2575
+ protected void RecordDiagnosticEmission(IMessage message, InstanceId? context = null)
2576
+ {
2577
+ Token.RecordDiagnosticEmission(Handle, _registeredMessageBus, message, context);
2578
+ }
2579
+ }
2580
+
2581
+ private abstract class Registration<T> : Registration
2582
+ where T : IMessage
2583
+ {
2584
+ private MessageHandler.TypedHandler<T>.TypedHandlerDeregistrationState _typedDeregistration;
2585
+ private bool _hasInlineDeregistration;
2586
+
2587
+ protected Registration(MessageRegistrationToken token)
2588
+ : base(token) { }
2589
+
2590
+ protected MessageHandler.HandlerDeregistration StoreDeregistration(
2591
+ MessageHandler.TypedHandler<T>.TypedHandlerDeregistrationState deregistration
2592
+ )
2593
+ {
2594
+ if (_hasInlineDeregistration)
2595
+ {
2596
+ return deregistration;
2597
+ }
2598
+
2599
+ _typedDeregistration = deregistration;
2600
+ _hasInlineDeregistration = true;
2601
+ return this;
2602
+ }
2603
+
2604
+ internal override void Deregister()
2605
+ {
2606
+ if (!_hasInlineDeregistration)
2607
+ {
2608
+ return;
2609
+ }
2610
+
2611
+ _typedDeregistration.Deregister();
2612
+
2613
+ // Clear only after successful teardown. If a custom bus throws, this
2614
+ // registration remains the retryable inline action and any recovery
2615
+ // registration spills into an independent compatibility wrapper.
2616
+ _hasInlineDeregistration = false;
2617
+ }
2618
+ }
2619
+
2620
+ private sealed class TargetedRegistration<T> : Registration<T>
2621
+ where T : ITargetedMessage
2622
+ {
2623
+ private readonly RegistrationKind _kind;
2624
+ private readonly InstanceId _context;
2625
+ private readonly object _userHandler;
2626
+ private readonly int _priority;
2627
+
2628
+ // Strongly-typed views of _userHandler, resolved ONCE at Register() time
2629
+ // (cold) so the per-dispatch augmented invoker calls a typed field directly
2630
+ // -- no per-dispatch castclass or kind-switch (which would add an O(handlers)
2631
+ // cost on the hot dispatch path). Exactly one is set per registration kind.
2632
+ private Action<T> _scalarAction;
2633
+ private MessageHandler.FastHandler<T> _scalarFast;
2634
+ private Action<InstanceId, T> _contextAction;
2635
+ private MessageHandler.FastHandlerWithContext<T> _contextFast;
2636
+
2637
+ internal TargetedRegistration(
2638
+ MessageRegistrationToken token,
2639
+ RegistrationKind kind,
2640
+ InstanceId context,
2641
+ object userHandler,
2642
+ int priority
2643
+ )
2644
+ : base(token)
2645
+ {
2646
+ _kind = kind;
2647
+ _context = context;
2648
+ _userHandler = userHandler;
2649
+ _priority = priority;
2650
+ }
2651
+
2652
+ public override MessageRegistrationMetadata Metadata =>
2653
+ new MessageRegistrationMetadata(
2654
+ _kind == RegistrationKind.TargetedHandlerAction
2655
+ || _kind == RegistrationKind.TargetedHandlerFast
2656
+ || _kind == RegistrationKind.TargetedPostProcessorAction
2657
+ || _kind == RegistrationKind.TargetedPostProcessorFast
2658
+ ? _context
2659
+ : (InstanceId?)null,
2660
+ typeof(T),
2661
+ GetRegistrationType(_kind),
2662
+ _priority
2663
+ );
2664
+
2665
+ public override MessageHandler.HandlerDeregistration Register()
2666
+ {
2667
+ MessageHandler messageHandler = Token._messageHandler;
2668
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
2669
+ switch (_kind)
2670
+ {
2671
+ case RegistrationKind.TargetedHandlerAction:
2672
+ _scalarAction = (Action<T>)_userHandler;
2673
+ return StoreDeregistration(
2674
+ messageHandler.RegisterTargetedMessageHandler(
2675
+ _context,
2676
+ _scalarAction,
2677
+ AugmentedScalarAction,
2678
+ priority: _priority,
2679
+ messageBus: messageBus
2680
+ )
2681
+ );
2682
+ case RegistrationKind.TargetedHandlerFast:
2683
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2684
+ return StoreDeregistration(
2685
+ messageHandler.RegisterTargetedMessageHandler(
2686
+ _context,
2687
+ _scalarFast,
2688
+ AugmentedScalarFast,
2689
+ priority: _priority,
2690
+ messageBus: messageBus
2691
+ )
2692
+ );
2693
+ case RegistrationKind.TargetedPostProcessorAction:
2694
+ _scalarAction = (Action<T>)_userHandler;
2695
+ return StoreDeregistration(
2696
+ messageHandler.RegisterTargetedPostProcessor(
2697
+ _context,
2698
+ _scalarAction,
2699
+ AugmentedScalarAction,
2700
+ _priority,
2701
+ messageBus
2702
+ )
2703
+ );
2704
+ case RegistrationKind.TargetedPostProcessorFast:
2705
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2706
+ return StoreDeregistration(
2707
+ messageHandler.RegisterTargetedPostProcessor(
2708
+ _context,
2709
+ _scalarFast,
2710
+ AugmentedScalarFast,
2711
+ _priority,
2712
+ messageBus
2713
+ )
2714
+ );
2715
+ case RegistrationKind.TargetedWithoutTargetingAction:
2716
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2717
+ return StoreDeregistration(
2718
+ messageHandler.RegisterTargetedWithoutTargeting(
2719
+ _contextAction,
2720
+ AugmentedContextAction,
2721
+ priority: _priority,
2722
+ messageBus: messageBus
2723
+ )
2724
+ );
2725
+ case RegistrationKind.TargetedWithoutTargetingFast:
2726
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2727
+ return StoreDeregistration(
2728
+ messageHandler.RegisterTargetedWithoutTargeting(
2729
+ _contextFast,
2730
+ AugmentedContextFast,
2731
+ priority: _priority,
2732
+ messageBus: messageBus
2733
+ )
2734
+ );
2735
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorAction:
2736
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2737
+ return StoreDeregistration(
2738
+ messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2739
+ _contextAction,
2740
+ AugmentedContextAction,
2741
+ _priority,
2742
+ messageBus
2743
+ )
2744
+ );
2745
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorFast:
2746
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2747
+ return StoreDeregistration(
2748
+ messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2749
+ _contextFast,
2750
+ AugmentedContextFast,
2751
+ _priority,
2752
+ messageBus
2753
+ )
2754
+ );
2755
+ default:
2756
+ throw new InvalidOperationException(
2757
+ $"Unexpected registration kind {_kind} for TargetedRegistration<{typeof(T)}>."
2758
+ );
2759
+ }
2760
+ }
2761
+
2762
+ // Scalar invokers (targeted handler / post-processor). The user's handler is
2763
+ // the identity/dedup key; this flat invoker runs for the default slot. Calls
2764
+ // the typed field directly (no castclass) then records the (message, _context)
2765
+ // emission, matching the former AugmentedHandler bodies exactly.
2766
+ private void AugmentedScalarAction(ref T message)
2767
+ {
2768
+ _scalarAction(message);
2769
+ if (Token._diagnosticMode)
2770
+ {
2771
+ RecordDiagnosticEmission(message, _context);
2772
+ }
2773
+ }
2774
+
2775
+ private void AugmentedScalarFast(ref T message)
2776
+ {
2777
+ _scalarFast(ref message);
2778
+ if (Token._diagnosticMode)
2779
+ {
2780
+ RecordDiagnosticEmission(message, _context);
2781
+ }
2782
+ }
2783
+
2784
+ // Context invokers (without-targeting handler / post-processor). Emission data
2785
+ // uses the dispatch-supplied target (the ref param), not the stored _context
2786
+ // (default for these kinds), matching the former bodies exactly.
2787
+ private void AugmentedContextAction(ref InstanceId target, ref T message)
2788
+ {
2789
+ _contextAction(target, message);
2790
+ if (Token._diagnosticMode)
2791
+ {
2792
+ RecordDiagnosticEmission(message, target);
2793
+ }
2794
+ }
2795
+
2796
+ private void AugmentedContextFast(ref InstanceId target, ref T message)
2797
+ {
2798
+ _contextFast(ref target, ref message);
2799
+ if (Token._diagnosticMode)
2800
+ {
2801
+ RecordDiagnosticEmission(message, target);
2802
+ }
2803
+ }
2804
+ }
2805
+
2806
+ private sealed class UntargetedRegistration<T> : Registration<T>
2807
+ where T : IUntargetedMessage
2808
+ {
2809
+ private readonly RegistrationKind _kind;
2810
+ private readonly object _userHandler;
2811
+ private readonly int _priority;
2812
+
2813
+ // Typed views of _userHandler, resolved once at Register() time (cold) so the
2814
+ // per-dispatch invoker calls a typed field directly -- no castclass/switch on
2815
+ // the hot path. Exactly one is set per registration kind.
2816
+ private Action<T> _scalarAction;
2817
+ private MessageHandler.FastHandler<T> _scalarFast;
2818
+
2819
+ internal UntargetedRegistration(
2820
+ MessageRegistrationToken token,
2821
+ RegistrationKind kind,
2822
+ object userHandler,
2823
+ int priority
2824
+ )
2825
+ : base(token)
2826
+ {
2827
+ _kind = kind;
2828
+ _userHandler = userHandler;
2829
+ _priority = priority;
2830
+ }
2831
+
2832
+ public override MessageRegistrationMetadata Metadata =>
2833
+ new MessageRegistrationMetadata(
2834
+ null,
2835
+ typeof(T),
2836
+ GetRegistrationType(_kind),
2837
+ _priority
2838
+ );
2839
+
2840
+ public override MessageHandler.HandlerDeregistration Register()
2841
+ {
2842
+ MessageHandler messageHandler = Token._messageHandler;
2843
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
2844
+ switch (_kind)
2845
+ {
2846
+ case RegistrationKind.UntargetedHandlerAction:
2847
+ _scalarAction = (Action<T>)_userHandler;
2848
+ return StoreDeregistration(
2849
+ messageHandler.RegisterUntargetedMessageHandler(
2850
+ _scalarAction,
2851
+ AugmentedScalarAction,
2852
+ priority: _priority,
2853
+ messageBus: messageBus
2854
+ )
2855
+ );
2856
+ case RegistrationKind.UntargetedHandlerFast:
2857
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2858
+ return StoreDeregistration(
2859
+ messageHandler.RegisterUntargetedMessageHandler(
2860
+ _scalarFast,
2861
+ AugmentedScalarFast,
2862
+ priority: _priority,
2863
+ messageBus: messageBus
2864
+ )
2865
+ );
2866
+ case RegistrationKind.UntargetedPostProcessorFast:
2867
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2868
+ return StoreDeregistration(
2869
+ messageHandler.RegisterUntargetedPostProcessor(
2870
+ _scalarFast,
2871
+ AugmentedScalarFast,
2872
+ _priority,
2873
+ messageBus
2874
+ )
2875
+ );
2876
+ default:
2877
+ throw new InvalidOperationException(
2878
+ $"Unexpected registration kind {_kind} for UntargetedRegistration<{typeof(T)}>."
2879
+ );
2880
+ }
2881
+ }
2882
+
2883
+ // Untargeted scalar invokers. No context: emission data carries the message
2884
+ // only, matching the former AugmentedHandler bodies exactly.
2885
+ private void AugmentedScalarAction(ref T message)
2886
+ {
2887
+ _scalarAction(message);
2888
+ if (Token._diagnosticMode)
2889
+ {
2890
+ RecordDiagnosticEmission(message);
2891
+ }
2892
+ }
2893
+
2894
+ private void AugmentedScalarFast(ref T message)
2895
+ {
2896
+ _scalarFast(ref message);
2897
+ if (Token._diagnosticMode)
2898
+ {
2899
+ RecordDiagnosticEmission(message);
2900
+ }
2901
+ }
2902
+ }
2903
+
2904
+ private sealed class BroadcastRegistration<T> : Registration<T>
2905
+ where T : IBroadcastMessage
2906
+ {
2907
+ private readonly RegistrationKind _kind;
2908
+ private readonly InstanceId _context;
2909
+ private readonly object _userHandler;
2910
+ private readonly int _priority;
2911
+
2912
+ // Typed views of _userHandler, resolved once at Register() time (cold) so the
2913
+ // per-dispatch invoker calls a typed field directly -- no castclass/switch on
2914
+ // the hot path. Exactly one is set per registration kind.
2915
+ private Action<T> _scalarAction;
2916
+ private MessageHandler.FastHandler<T> _scalarFast;
2917
+ private Action<InstanceId, T> _contextAction;
2918
+ private MessageHandler.FastHandlerWithContext<T> _contextFast;
2919
+
2920
+ internal BroadcastRegistration(
2921
+ MessageRegistrationToken token,
2922
+ RegistrationKind kind,
2923
+ InstanceId context,
2924
+ object userHandler,
2925
+ int priority
2926
+ )
2927
+ : base(token)
2928
+ {
2929
+ _kind = kind;
2930
+ _context = context;
2931
+ _userHandler = userHandler;
2932
+ _priority = priority;
2933
+ }
2934
+
2935
+ public override MessageRegistrationMetadata Metadata =>
2936
+ new MessageRegistrationMetadata(
2937
+ _kind == RegistrationKind.BroadcastHandlerAction
2938
+ || _kind == RegistrationKind.BroadcastHandlerFast
2939
+ || _kind == RegistrationKind.BroadcastPostProcessorAction
2940
+ || _kind == RegistrationKind.BroadcastPostProcessorFast
2941
+ ? _context
2942
+ : (InstanceId?)null,
2943
+ typeof(T),
2944
+ GetRegistrationType(_kind),
2945
+ _priority
2946
+ );
2947
+
2948
+ public override MessageHandler.HandlerDeregistration Register()
2949
+ {
2950
+ MessageHandler messageHandler = Token._messageHandler;
2951
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
2952
+ switch (_kind)
2953
+ {
2954
+ case RegistrationKind.BroadcastHandlerAction:
2955
+ _scalarAction = (Action<T>)_userHandler;
2956
+ return StoreDeregistration(
2957
+ messageHandler.RegisterSourcedBroadcastMessageHandler(
2958
+ _context,
2959
+ _scalarAction,
2960
+ AugmentedScalarAction,
2961
+ priority: _priority,
2962
+ messageBus: messageBus
2963
+ )
2964
+ );
2965
+ case RegistrationKind.BroadcastHandlerFast:
2966
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2967
+ return StoreDeregistration(
2968
+ messageHandler.RegisterSourcedBroadcastMessageHandler(
2969
+ _context,
2970
+ _scalarFast,
2971
+ AugmentedScalarFast,
2972
+ priority: _priority,
2973
+ messageBus: messageBus
2974
+ )
2975
+ );
2976
+ case RegistrationKind.BroadcastPostProcessorAction:
2977
+ _scalarAction = (Action<T>)_userHandler;
2978
+ return StoreDeregistration(
2979
+ messageHandler.RegisterSourcedBroadcastPostProcessor(
2980
+ _context,
2981
+ _scalarAction,
2982
+ AugmentedScalarAction,
2983
+ _priority,
2984
+ messageBus
2985
+ )
2986
+ );
2987
+ case RegistrationKind.BroadcastPostProcessorFast:
2988
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2989
+ return StoreDeregistration(
2990
+ messageHandler.RegisterSourcedBroadcastPostProcessor(
2991
+ _context,
2992
+ _scalarFast,
2993
+ AugmentedScalarFast,
2994
+ _priority,
2995
+ messageBus
2996
+ )
2997
+ );
2998
+ case RegistrationKind.BroadcastWithoutSourceAction:
2999
+ _contextAction = (Action<InstanceId, T>)_userHandler;
3000
+ return StoreDeregistration(
3001
+ messageHandler.RegisterSourcedBroadcastWithoutSource(
3002
+ _contextAction,
3003
+ AugmentedContextAction,
3004
+ priority: _priority,
3005
+ messageBus: messageBus
3006
+ )
3007
+ );
3008
+ case RegistrationKind.BroadcastWithoutSourceFast:
3009
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
3010
+ return StoreDeregistration(
3011
+ messageHandler.RegisterSourcedBroadcastWithoutSource(
3012
+ _contextFast,
3013
+ AugmentedContextFast,
3014
+ priority: _priority,
3015
+ messageBus: messageBus
3016
+ )
3017
+ );
3018
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorAction:
3019
+ _contextAction = (Action<InstanceId, T>)_userHandler;
3020
+ return StoreDeregistration(
3021
+ messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
3022
+ _contextAction,
3023
+ AugmentedContextAction,
3024
+ priority: _priority,
3025
+ messageBus
3026
+ )
3027
+ );
3028
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorFast:
3029
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
3030
+ return StoreDeregistration(
3031
+ messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
3032
+ _contextFast,
3033
+ AugmentedContextFast,
3034
+ priority: _priority,
3035
+ messageBus
3036
+ )
3037
+ );
3038
+ default:
3039
+ throw new InvalidOperationException(
3040
+ $"Unexpected registration kind {_kind} for BroadcastRegistration<{typeof(T)}>."
3041
+ );
3042
+ }
3043
+ }
3044
+
3045
+ // Broadcast scalar invokers. Emission data carries the stored source
3046
+ // (_context), matching the former AugmentedHandler bodies exactly.
3047
+ private void AugmentedScalarAction(ref T message)
3048
+ {
3049
+ _scalarAction(message);
3050
+ if (Token._diagnosticMode)
3051
+ {
3052
+ RecordDiagnosticEmission(message, _context);
3053
+ }
3054
+ }
3055
+
3056
+ private void AugmentedScalarFast(ref T message)
3057
+ {
3058
+ _scalarFast(ref message);
3059
+ if (Token._diagnosticMode)
3060
+ {
3061
+ RecordDiagnosticEmission(message, _context);
3062
+ }
3063
+ }
3064
+
3065
+ // Broadcast context invokers for the without-source kinds. Emission data uses
3066
+ // the dispatch-supplied source (the ref param), not the stored _context
3067
+ // (default for these kinds), matching the former bodies exactly.
3068
+ private void AugmentedContextAction(ref InstanceId source, ref T message)
3069
+ {
3070
+ _contextAction(source, message);
3071
+ if (Token._diagnosticMode)
3072
+ {
3073
+ RecordDiagnosticEmission(message, source);
3074
+ }
3075
+ }
3076
+
3077
+ private void AugmentedContextFast(ref InstanceId source, ref T message)
3078
+ {
3079
+ _contextFast(ref source, ref message);
3080
+ if (Token._diagnosticMode)
3081
+ {
3082
+ RecordDiagnosticEmission(message, source);
3083
+ }
3084
+ }
3085
+ }
3086
+
3087
+ private abstract class InterceptorRegistration<T> : Registration
3088
+ where T : IMessage
3089
+ {
3090
+ protected readonly int Priority;
3091
+ private MessageHandler.InterceptorDeregistrationState<T> _deregistration;
3092
+ private bool _hasInlineDeregistration;
3093
+
3094
+ protected InterceptorRegistration(MessageRegistrationToken token, int priority)
3095
+ : base(token)
3096
+ {
3097
+ Priority = priority;
3098
+ }
3099
+
3100
+ protected MessageHandler.HandlerDeregistration StoreDeregistration(
3101
+ MessageHandler.InterceptorDeregistrationState<T> deregistration
3102
+ )
3103
+ {
3104
+ if (_hasInlineDeregistration)
3105
+ {
3106
+ return deregistration;
3107
+ }
3108
+
3109
+ _deregistration = deregistration;
3110
+ _hasInlineDeregistration = true;
3111
+ return this;
3112
+ }
3113
+
3114
+ internal override void Deregister()
3115
+ {
3116
+ if (!_hasInlineDeregistration)
3117
+ {
3118
+ return;
3119
+ }
3120
+
3121
+ _deregistration.Deregister();
3122
+ _hasInlineDeregistration = false;
3123
+ }
3124
+ }
3125
+
3126
+ private sealed class UntargetedInterceptorRegistration<T> : InterceptorRegistration<T>
3127
+ where T : IUntargetedMessage
3128
+ {
3129
+ private readonly IMessageBus.UntargetedInterceptor<T> _interceptor;
3130
+
3131
+ internal UntargetedInterceptorRegistration(
3132
+ MessageRegistrationToken token,
3133
+ IMessageBus.UntargetedInterceptor<T> interceptor,
3134
+ int priority
3135
+ )
3136
+ : base(token, priority)
3137
+ {
3138
+ _interceptor = interceptor;
3139
+ }
3140
+
3141
+ public override MessageRegistrationMetadata Metadata =>
3142
+ new MessageRegistrationMetadata(
3143
+ null,
3144
+ typeof(T),
3145
+ MessageRegistrationType.UntargetedInterceptor,
3146
+ Priority
3147
+ );
3148
+
3149
+ public override MessageHandler.HandlerDeregistration Register()
3150
+ {
3151
+ return StoreDeregistration(
3152
+ Token._messageHandler.RegisterUntargetedInterceptor(
3153
+ _interceptor,
3154
+ Priority,
3155
+ ResolveRegistrationMessageBus()
3156
+ )
3157
+ );
3158
+ }
3159
+ }
3160
+
3161
+ private sealed class TargetedInterceptorRegistration<T> : InterceptorRegistration<T>
3162
+ where T : ITargetedMessage
3163
+ {
3164
+ private readonly IMessageBus.TargetedInterceptor<T> _interceptor;
3165
+
3166
+ internal TargetedInterceptorRegistration(
3167
+ MessageRegistrationToken token,
3168
+ IMessageBus.TargetedInterceptor<T> interceptor,
3169
+ int priority
3170
+ )
3171
+ : base(token, priority)
3172
+ {
3173
+ _interceptor = interceptor;
3174
+ }
3175
+
3176
+ public override MessageRegistrationMetadata Metadata =>
3177
+ new MessageRegistrationMetadata(
3178
+ null,
3179
+ typeof(T),
3180
+ MessageRegistrationType.TargetedInterceptor,
3181
+ Priority
3182
+ );
3183
+
3184
+ public override MessageHandler.HandlerDeregistration Register()
3185
+ {
3186
+ return StoreDeregistration(
3187
+ Token._messageHandler.RegisterTargetedInterceptor(
3188
+ _interceptor,
3189
+ Priority,
3190
+ ResolveRegistrationMessageBus()
3191
+ )
3192
+ );
3193
+ }
3194
+ }
3195
+
3196
+ private sealed class BroadcastInterceptorRegistration<T> : InterceptorRegistration<T>
3197
+ where T : IBroadcastMessage
3198
+ {
3199
+ private readonly IMessageBus.BroadcastInterceptor<T> _interceptor;
3200
+
3201
+ internal BroadcastInterceptorRegistration(
3202
+ MessageRegistrationToken token,
3203
+ IMessageBus.BroadcastInterceptor<T> interceptor,
3204
+ int priority
3205
+ )
3206
+ : base(token, priority)
3207
+ {
3208
+ _interceptor = interceptor;
3209
+ }
3210
+
3211
+ public override MessageRegistrationMetadata Metadata =>
3212
+ new MessageRegistrationMetadata(
3213
+ null,
3214
+ typeof(T),
3215
+ MessageRegistrationType.BroadcastInterceptor,
3216
+ Priority
3217
+ );
3218
+
3219
+ public override MessageHandler.HandlerDeregistration Register()
3220
+ {
3221
+ return StoreDeregistration(
3222
+ Token._messageHandler.RegisterBroadcastInterceptor(
3223
+ _interceptor,
3224
+ Priority,
3225
+ ResolveRegistrationMessageBus()
3226
+ )
3227
+ );
3228
+ }
3229
+ }
3230
+
3231
+ // Global accept-all is non-generic: its three sub-handlers are the fixed
3232
+ // IMessage facades. Stores the three user delegates (as object, since the two
3233
+ // public overloads differ in delegate shape -- Action vs FastHandler/
3234
+ // FastHandlerWithContext) and exposes six augmented sub-invokers (three per
3235
+ // overload shape). The kind-switch picks the matching MessageHandler.
3236
+ // RegisterGlobalAcceptAll overload and binds the three augmented invokers.
3237
+ private sealed class GlobalAcceptAllRegistration : Registration
3238
+ {
3239
+ private readonly RegistrationKind _kind;
3240
+
3241
+ // Typed sub-handlers (one shape-trio is set per kind). The per-dispatch
3242
+ // invokers call these directly -- no castclass on the hot global-dispatch
3243
+ // path (the heaviest fan-out, so the castclass cost there was the worst).
3244
+ private readonly Action<IUntargetedMessage> _untargetedAction;
3245
+ private readonly Action<InstanceId, ITargetedMessage> _targetedAction;
3246
+ private readonly Action<InstanceId, IBroadcastMessage> _broadcastAction;
3247
+ private readonly MessageHandler.FastHandler<IUntargetedMessage> _untargetedFast;
3248
+ private readonly MessageHandler.FastHandlerWithContext<ITargetedMessage> _targetedFast;
3249
+ private readonly MessageHandler.FastHandlerWithContext<IBroadcastMessage> _broadcastFast;
3250
+ private MessageHandler.GlobalAcceptAllDeregistrationState _deregistration;
3251
+ private bool _hasInlineDeregistration;
3252
+
3253
+ public override MessageRegistrationMetadata Metadata =>
3254
+ new MessageRegistrationMetadata(
3255
+ null,
3256
+ typeof(IMessage),
3257
+ MessageRegistrationType.GlobalAcceptAll,
3258
+ 0
3259
+ );
3260
+
3261
+ internal GlobalAcceptAllRegistration(
3262
+ MessageRegistrationToken token,
3263
+ Action<IUntargetedMessage> untargeted,
3264
+ Action<InstanceId, ITargetedMessage> targeted,
3265
+ Action<InstanceId, IBroadcastMessage> broadcast
3266
+ )
3267
+ : base(token)
3268
+ {
3269
+ _kind = RegistrationKind.GlobalAcceptAllAction;
3270
+ _untargetedAction = untargeted;
3271
+ _targetedAction = targeted;
3272
+ _broadcastAction = broadcast;
3273
+ }
3274
+
3275
+ internal GlobalAcceptAllRegistration(
3276
+ MessageRegistrationToken token,
3277
+ MessageHandler.FastHandler<IUntargetedMessage> untargeted,
3278
+ MessageHandler.FastHandlerWithContext<ITargetedMessage> targeted,
3279
+ MessageHandler.FastHandlerWithContext<IBroadcastMessage> broadcast
3280
+ )
3281
+ : base(token)
3282
+ {
3283
+ _kind = RegistrationKind.GlobalAcceptAllFast;
3284
+ _untargetedFast = untargeted;
3285
+ _targetedFast = targeted;
3286
+ _broadcastFast = broadcast;
3287
+ }
3288
+
3289
+ public override MessageHandler.HandlerDeregistration Register()
3290
+ {
3291
+ MessageHandler messageHandler = Token._messageHandler;
3292
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
3293
+ if (_kind == RegistrationKind.GlobalAcceptAllAction)
3294
+ {
3295
+ return StoreDeregistration(
3296
+ messageHandler.RegisterGlobalAcceptAll(
3297
+ _untargetedAction,
3298
+ AugmentedUntargetedAction,
3299
+ _targetedAction,
3300
+ AugmentedTargetedAction,
3301
+ _broadcastAction,
3302
+ AugmentedBroadcastAction,
3303
+ messageBus
3304
+ )
3305
+ );
3306
+ }
3307
+
3308
+ return StoreDeregistration(
3309
+ messageHandler.RegisterGlobalAcceptAll(
3310
+ _untargetedFast,
3311
+ AugmentedUntargetedFast,
3312
+ _targetedFast,
3313
+ AugmentedTargetedFast,
3314
+ _broadcastFast,
3315
+ AugmentedBroadcastFast,
3316
+ messageBus
3317
+ )
3318
+ );
3319
+ }
3320
+
3321
+ internal override void Deregister()
3322
+ {
3323
+ if (!_hasInlineDeregistration)
3324
+ {
3325
+ return;
3326
+ }
3327
+
3328
+ _deregistration.Deregister();
3329
+ _hasInlineDeregistration = false;
3330
+ }
3331
+
3332
+ private MessageHandler.HandlerDeregistration StoreDeregistration(
3333
+ MessageHandler.GlobalAcceptAllDeregistrationState deregistration
3334
+ )
3335
+ {
3336
+ if (_hasInlineDeregistration)
3337
+ {
3338
+ return deregistration;
3339
+ }
3340
+
3341
+ _deregistration = deregistration;
3342
+ _hasInlineDeregistration = true;
3343
+ return this;
3344
+ }
3345
+
3346
+ private void AugmentedUntargetedAction(IUntargetedMessage message)
3347
+ {
3348
+ _untargetedAction(message);
3349
+ if (Token._diagnosticMode)
3350
+ {
3351
+ RecordDiagnosticEmission(message);
3352
+ }
3353
+ }
3354
+
3355
+ private void AugmentedTargetedAction(InstanceId target, ITargetedMessage message)
3356
+ {
3357
+ _targetedAction(target, message);
3358
+ if (Token._diagnosticMode)
3359
+ {
3360
+ RecordDiagnosticEmission(message, target);
3361
+ }
3362
+ }
3363
+
3364
+ private void AugmentedBroadcastAction(InstanceId source, IBroadcastMessage message)
3365
+ {
3366
+ _broadcastAction(source, message);
3367
+ if (Token._diagnosticMode)
3368
+ {
3369
+ RecordDiagnosticEmission(message, source);
3370
+ }
3371
+ }
3372
+
3373
+ private void AugmentedUntargetedFast(ref IUntargetedMessage message)
3374
+ {
3375
+ _untargetedFast(ref message);
3376
+ if (Token._diagnosticMode)
3377
+ {
3378
+ RecordDiagnosticEmission(message);
3379
+ }
3380
+ }
3381
+
3382
+ private void AugmentedTargetedFast(ref InstanceId target, ref ITargetedMessage message)
3383
+ {
3384
+ _targetedFast(ref target, ref message);
3385
+ if (Token._diagnosticMode)
3386
+ {
3387
+ RecordDiagnosticEmission(message, target);
3388
+ }
3389
+ }
3390
+
3391
+ private void AugmentedBroadcastFast(
3392
+ ref InstanceId source,
3393
+ ref IBroadcastMessage message
3394
+ )
3395
+ {
3396
+ _broadcastFast(ref source, ref message);
3397
+ if (Token._diagnosticMode)
3398
+ {
3399
+ RecordDiagnosticEmission(message, source);
3400
+ }
2364
3401
  }
2365
3402
  }
2366
3403