com.wallstop-studios.dxmessaging 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +286 -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.meta +3 -3
  5. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
  6. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
  7. package/Editor/DxMessagingMenu.cs.meta +3 -3
  8. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
  9. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +2 -3
  10. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
  11. package/Editor/SetupCscRsp.cs +232 -163
  12. package/README.md +1 -1
  13. package/Runtime/Analyzers.meta +8 -0
  14. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
  15. package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
  16. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
  17. package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
  18. package/Runtime/Core/IMessage.cs.meta +3 -3
  19. package/Runtime/Core/InstanceId.cs.meta +3 -3
  20. package/Runtime/Core/Internal/TypedSlots.cs +42 -0
  21. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
  22. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
  23. package/Runtime/Core/MessageBus/IMessageBus.cs +63 -28
  24. package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
  25. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
  26. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
  27. package/Runtime/Core/MessageBus/MessageBus.cs +548 -474
  28. package/Runtime/Core/MessageBus/MessageBus.cs.meta +3 -3
  29. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +3 -3
  30. package/Runtime/Core/MessageBus/MessageBusRegistration.cs +207 -0
  31. package/Runtime/Core/MessageBus/MessageBusRegistration.cs.meta +11 -0
  32. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +3 -3
  33. package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +3 -3
  34. package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +3 -3
  35. package/Runtime/Core/MessageHandler.cs +1210 -394
  36. package/Runtime/Core/MessageHandler.cs.meta +3 -3
  37. package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
  38. package/Runtime/Core/MessageRegistrationToken.cs +1296 -819
  39. package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
  40. package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +3 -3
  41. package/Runtime/Core/Messages/ITargetedMessage.cs.meta +3 -3
  42. package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +3 -3
  43. package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +3 -3
  44. package/Runtime/Core/MessagingDebug.cs.meta +3 -3
  45. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +3 -3
  46. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +3 -3
  47. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +3 -3
  48. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +3 -3
  49. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
  50. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
  51. package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
  52. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
  53. package/Runtime/Unity/MessagingComponent.cs.meta +3 -3
  54. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +3 -3
  55. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +3 -3
  56. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +3 -3
  57. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +3 -3
  58. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +3 -3
  59. package/Samples~/Mini Combat/Boot.cs.meta +3 -3
  60. package/Samples~/Mini Combat/Enemy.cs.meta +3 -3
  61. package/Samples~/Mini Combat/Messages.cs.meta +3 -3
  62. package/Samples~/Mini Combat/Player.cs.meta +3 -3
  63. package/Samples~/Mini Combat/UIOverlay.cs.meta +3 -3
  64. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +3 -3
  65. package/Samples~/UI Buttons + Inspector/Messages.cs.meta +3 -3
  66. package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +3 -3
  67. package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +3 -3
  68. package/SourceGenerators/Directory.Build.props +5 -5
  69. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
  70. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +12 -11
  71. package/package.json +6 -3
  72. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  73. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +0 -0
  74. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
  75. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +0 -0
  76. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
  77. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +0 -0
  78. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
  79. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +0 -0
  80. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
  81. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +0 -0
  82. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  83. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -0
  84. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  85. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +0 -0
@@ -58,7 +58,23 @@ namespace DxMessaging.Core
58
58
 
59
59
  private readonly MessageHandler _messageHandler;
60
60
 
61
- private readonly Dictionary<MessageRegistrationHandle, Action> _registrations = new();
61
+ // Maps each staged registration handle to the unified per-handle Registration
62
+ // object the public Register* methods built. Calling Registration.Register()
63
+ // (re)registers the handler on the bus and returns the matching
64
+ // HandlerDeregistration. The Registration object IS the collapsed staging
65
+ // state: it replaces (a) the per-registration staging Func display class, (b)
66
+ // the staging Func delegate itself, and (c) the nested AugmentedHandler
67
+ // local-function delegate -- the captured target/source, user handler,
68
+ // priority, and kind are now plain fields, and the diagnostics-augmented
69
+ // invoker is an instance method bound to the object (the FastHandler<T> handed
70
+ // to MessageHandler is (FastHandler<T>)registration.AugmentedHandlerScalar).
71
+ // Net ~ -2 managed allocations per registration. The central replay loop pairs
72
+ // the Registration with its handle and performs the AddDeregistration. The
73
+ // re-entrancy snapshot semantics are unchanged: the replay queue captures the
74
+ // Registration reference (exactly as it previously captured the staging Func),
75
+ // so a registration removed mid-replay is still replayed if it was already
76
+ // snapshotted.
77
+ private readonly Dictionary<MessageRegistrationHandle, Registration> _registrations = new();
62
78
 
63
79
  // Staged-registration handles in registration order. Dictionary
64
80
  // enumeration order is NOT stable across Remove/Add churn, so Enable()
@@ -70,20 +86,53 @@ namespace DxMessaging.Core
70
86
  // InternalRegister staged them (handle ids are monotonically
71
87
  // increasing, so this list is also sorted by handle id).
72
88
  private readonly List<MessageRegistrationHandle> _registrationOrder = new();
73
- private readonly Dictionary<
74
- MessageRegistrationHandle,
75
- PendingDeregistration
76
- > _deregistrations = new();
77
- private readonly List<Action> _actionQueue = new();
89
+
90
+ // Maps a handle to its live de-registration(s). The common case is EXACTLY ONE
91
+ // de-registration per handle, so the single Action is stored INLINE as the dictionary
92
+ // value (presence == pending, removal == done, keep-on-throw == retryable) -- no
93
+ // per-registration PendingDeregistration object. A PendingDeregistration is allocated and
94
+ // substituted ONLY when a second de-registration accumulates on the same handle (the rare
95
+ // retarget-recovery replay; see AddDeregistration). The value is therefore either an
96
+ // Action (1 de-registration) or a PendingDeregistration (2+).
97
+ private readonly Dictionary<MessageRegistrationHandle, object> _deregistrations = new();
98
+
99
+ // Snapshot of (handle, Registration object) pairs to replay on Enable() /
100
+ // RetargetMessageBus. Snapshotting before invoking lets replay tolerate
101
+ // re-entrant registration mutation (a handler that registers or removes
102
+ // handlers while it runs). The pair carries the handle so the central replay
103
+ // loop can call Registration.Register() and AddDeregistration without a
104
+ // per-registration wrapper closure.
105
+ private readonly List<StagedRegistration> _registrationReplayQueue = new();
78
106
  private readonly List<MessageRegistrationHandle> _handleQueue = new();
79
107
  internal readonly Dictionary<
80
108
  MessageRegistrationHandle,
81
109
  MessageRegistrationMetadata
82
110
  > _metadata = new();
83
- internal readonly Dictionary<MessageRegistrationHandle, int> _callCounts = new();
84
- internal readonly CyclicBuffer<MessageEmissionData> _emissionBuffer = new(
85
- IMessageBus.GlobalMessageBufferSize
86
- );
111
+
112
+ // Diagnostics-only collections, allocated lazily on first use. A token whose
113
+ // owner never enables diagnostics (the default -- GlobalDiagnosticsTargets is
114
+ // Off -- and the common player case) never materializes them, saving the
115
+ // dictionary, the cyclic buffer, and the buffer's two backing lists per token.
116
+ // These are exposed as properties under their original field names so the
117
+ // inspector overlay and the diagnostics tests (which read token._callCounts /
118
+ // token._emissionBuffer) compile and behave unchanged; the getter caches into
119
+ // the backing field, so repeated reads return the same instance and an editor
120
+ // read simply materializes an empty collection. The only production writers are
121
+ // the dispatch-time AugmentedHandler bodies, all guarded by _diagnosticMode, so
122
+ // production-with-diagnostics-off never triggers the allocation. Teardown
123
+ // (ClearDiagnosticState / RemoveRegistrationState /
124
+ // PruneRegistrationStateToFailedDeregistrations) clears through the backing
125
+ // fields to avoid materializing a collection just to empty it.
126
+ private Dictionary<MessageRegistrationHandle, int> _callCountsBacking;
127
+ private CyclicBuffer<MessageEmissionData> _emissionBufferBacking;
128
+
129
+ internal Dictionary<MessageRegistrationHandle, int> _callCounts =>
130
+ _callCountsBacking ??= new Dictionary<MessageRegistrationHandle, int>();
131
+
132
+ internal CyclicBuffer<MessageEmissionData> _emissionBuffer =>
133
+ _emissionBufferBacking ??= new CyclicBuffer<MessageEmissionData>(
134
+ IMessageBus.GlobalMessageBufferSize
135
+ );
87
136
 
88
137
  private IMessageBus _messageBus;
89
138
  private bool _enabled;
@@ -109,33 +158,19 @@ namespace DxMessaging.Core
109
158
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
110
159
  }
111
160
  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
- )
161
+ new TargetedRegistration<T>(
162
+ this,
163
+ RegistrationKind.TargetedHandlerAction,
164
+ target,
165
+ targetedHandler,
166
+ priority
167
+ ),
168
+ new MessageRegistrationMetadata(
169
+ target,
170
+ typeof(T),
171
+ MessageRegistrationType.Targeted,
172
+ priority
173
+ )
139
174
  );
140
175
  }
141
176
 
@@ -152,33 +187,19 @@ namespace DxMessaging.Core
152
187
  }
153
188
 
154
189
  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
- )
190
+ new TargetedRegistration<T>(
191
+ this,
192
+ RegistrationKind.TargetedHandlerFast,
193
+ target,
194
+ targetedHandler,
195
+ priority
196
+ ),
197
+ new MessageRegistrationMetadata(
198
+ target,
199
+ typeof(T),
200
+ MessageRegistrationType.Targeted,
201
+ priority
202
+ )
182
203
  );
183
204
  }
184
205
 
@@ -300,33 +321,19 @@ namespace DxMessaging.Core
300
321
  where T : ITargetedMessage
301
322
  {
302
323
  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
- )
324
+ new TargetedRegistration<T>(
325
+ this,
326
+ RegistrationKind.TargetedPostProcessorFast,
327
+ target,
328
+ targetedPostProcessor,
329
+ priority
330
+ ),
331
+ new MessageRegistrationMetadata(
332
+ target,
333
+ typeof(T),
334
+ MessageRegistrationType.TargetedPostProcessor,
335
+ priority
336
+ )
330
337
  );
331
338
  }
332
339
 
@@ -346,33 +353,19 @@ namespace DxMessaging.Core
346
353
  where T : ITargetedMessage
347
354
  {
348
355
  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
- )
356
+ new TargetedRegistration<T>(
357
+ this,
358
+ RegistrationKind.TargetedPostProcessorFast,
359
+ target,
360
+ targetedPostProcessor,
361
+ priority
362
+ ),
363
+ new MessageRegistrationMetadata(
364
+ target,
365
+ typeof(T),
366
+ MessageRegistrationType.TargetedPostProcessor,
367
+ priority
368
+ )
376
369
  );
377
370
  }
378
371
  #endif
@@ -435,33 +428,19 @@ namespace DxMessaging.Core
435
428
  where T : ITargetedMessage
436
429
  {
437
430
  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
- )
431
+ new TargetedRegistration<T>(
432
+ this,
433
+ RegistrationKind.TargetedPostProcessorFast,
434
+ target,
435
+ targetedPostProcessor,
436
+ priority
437
+ ),
438
+ new MessageRegistrationMetadata(
439
+ target,
440
+ typeof(T),
441
+ MessageRegistrationType.TargetedPostProcessor,
442
+ priority
443
+ )
465
444
  );
466
445
  }
467
446
 
@@ -481,33 +460,19 @@ namespace DxMessaging.Core
481
460
  where T : ITargetedMessage
482
461
  {
483
462
  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
- )
463
+ new TargetedRegistration<T>(
464
+ this,
465
+ RegistrationKind.TargetedPostProcessorAction,
466
+ target,
467
+ targetedPostProcessor,
468
+ priority
469
+ ),
470
+ new MessageRegistrationMetadata(
471
+ target,
472
+ typeof(T),
473
+ MessageRegistrationType.TargetedPostProcessor,
474
+ priority
475
+ )
511
476
  );
512
477
  }
513
478
 
@@ -532,32 +497,19 @@ namespace DxMessaging.Core
532
497
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
533
498
  }
534
499
  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
- )
500
+ new TargetedRegistration<T>(
501
+ this,
502
+ RegistrationKind.TargetedWithoutTargetingAction,
503
+ default,
504
+ messageHandler,
505
+ priority
506
+ ),
507
+ new MessageRegistrationMetadata(
508
+ null,
509
+ typeof(T),
510
+ MessageRegistrationType.TargetedWithoutTargeting,
511
+ priority
512
+ )
561
513
  );
562
514
  }
563
515
 
@@ -582,32 +534,19 @@ namespace DxMessaging.Core
582
534
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
583
535
  }
584
536
  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
- )
537
+ new TargetedRegistration<T>(
538
+ this,
539
+ RegistrationKind.TargetedWithoutTargetingFast,
540
+ default,
541
+ messageHandler,
542
+ priority
543
+ ),
544
+ new MessageRegistrationMetadata(
545
+ null,
546
+ typeof(T),
547
+ MessageRegistrationType.TargetedWithoutTargeting,
548
+ priority
549
+ )
611
550
  );
612
551
  }
613
552
 
@@ -632,32 +571,19 @@ namespace DxMessaging.Core
632
571
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
633
572
  }
634
573
  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
- )
574
+ new TargetedRegistration<T>(
575
+ this,
576
+ RegistrationKind.TargetedWithoutTargetingPostProcessorAction,
577
+ default,
578
+ postProcessor,
579
+ priority
580
+ ),
581
+ new MessageRegistrationMetadata(
582
+ null,
583
+ typeof(T),
584
+ MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
585
+ priority
586
+ )
661
587
  );
662
588
  }
663
589
 
@@ -682,32 +608,19 @@ namespace DxMessaging.Core
682
608
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
683
609
  }
684
610
  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
- )
611
+ new TargetedRegistration<T>(
612
+ this,
613
+ RegistrationKind.TargetedWithoutTargetingPostProcessorFast,
614
+ default,
615
+ postProcessor,
616
+ priority
617
+ ),
618
+ new MessageRegistrationMetadata(
619
+ null,
620
+ typeof(T),
621
+ MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
622
+ priority
623
+ )
711
624
  );
712
625
  }
713
626
 
@@ -739,32 +652,18 @@ namespace DxMessaging.Core
739
652
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
740
653
  }
741
654
  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
- )
655
+ new UntargetedRegistration<T>(
656
+ this,
657
+ RegistrationKind.UntargetedHandlerAction,
658
+ untargetedHandler,
659
+ priority
660
+ ),
661
+ new MessageRegistrationMetadata(
662
+ null,
663
+ typeof(T),
664
+ MessageRegistrationType.Untargeted,
665
+ priority
666
+ )
768
667
  );
769
668
  }
770
669
 
@@ -795,32 +694,18 @@ namespace DxMessaging.Core
795
694
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
796
695
  }
797
696
  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
- )
697
+ new UntargetedRegistration<T>(
698
+ this,
699
+ RegistrationKind.UntargetedHandlerFast,
700
+ untargetedHandler,
701
+ priority
702
+ ),
703
+ new MessageRegistrationMetadata(
704
+ null,
705
+ typeof(T),
706
+ MessageRegistrationType.Untargeted,
707
+ priority
708
+ )
824
709
  );
825
710
  }
826
711
 
@@ -842,32 +727,18 @@ namespace DxMessaging.Core
842
727
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
843
728
  }
844
729
  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
- )
730
+ new UntargetedRegistration<T>(
731
+ this,
732
+ RegistrationKind.UntargetedPostProcessorFast,
733
+ untargetedPostProcessor,
734
+ priority
735
+ ),
736
+ new MessageRegistrationMetadata(
737
+ null,
738
+ typeof(T),
739
+ MessageRegistrationType.UntargetedPostProcessor,
740
+ priority
741
+ )
871
742
  );
872
743
  }
873
744
 
@@ -883,33 +754,19 @@ namespace DxMessaging.Core
883
754
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
884
755
  }
885
756
  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
- )
757
+ new BroadcastRegistration<T>(
758
+ this,
759
+ RegistrationKind.BroadcastHandlerAction,
760
+ source,
761
+ broadcastHandler,
762
+ priority
763
+ ),
764
+ new MessageRegistrationMetadata(
765
+ source,
766
+ typeof(T),
767
+ MessageRegistrationType.Broadcast,
768
+ priority
769
+ )
913
770
  );
914
771
  }
915
772
 
@@ -925,33 +782,19 @@ namespace DxMessaging.Core
925
782
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
926
783
  }
927
784
  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
- )
785
+ new BroadcastRegistration<T>(
786
+ this,
787
+ RegistrationKind.BroadcastHandlerFast,
788
+ source,
789
+ broadcastHandler,
790
+ priority
791
+ ),
792
+ new MessageRegistrationMetadata(
793
+ source,
794
+ typeof(T),
795
+ MessageRegistrationType.Broadcast,
796
+ priority
797
+ )
955
798
  );
956
799
  }
957
800
 
@@ -968,33 +811,19 @@ namespace DxMessaging.Core
968
811
  }
969
812
 
970
813
  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
- )
814
+ new BroadcastRegistration<T>(
815
+ this,
816
+ RegistrationKind.BroadcastPostProcessorAction,
817
+ source,
818
+ broadcastPostProcessor,
819
+ priority
820
+ ),
821
+ new MessageRegistrationMetadata(
822
+ source,
823
+ typeof(T),
824
+ MessageRegistrationType.BroadcastPostProcessor,
825
+ priority
826
+ )
998
827
  );
999
828
  }
1000
829
 
@@ -1010,33 +839,19 @@ namespace DxMessaging.Core
1010
839
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1011
840
  }
1012
841
  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
- )
842
+ new BroadcastRegistration<T>(
843
+ this,
844
+ RegistrationKind.BroadcastPostProcessorFast,
845
+ source,
846
+ broadcastPostProcessor,
847
+ priority
848
+ ),
849
+ new MessageRegistrationMetadata(
850
+ source,
851
+ typeof(T),
852
+ MessageRegistrationType.BroadcastPostProcessor,
853
+ priority
854
+ )
1040
855
  );
1041
856
  }
1042
857
 
@@ -1181,33 +996,19 @@ namespace DxMessaging.Core
1181
996
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1182
997
  }
1183
998
  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
- )
999
+ new BroadcastRegistration<T>(
1000
+ this,
1001
+ RegistrationKind.BroadcastPostProcessorAction,
1002
+ source,
1003
+ broadcastPostProcessor,
1004
+ priority
1005
+ ),
1006
+ new MessageRegistrationMetadata(
1007
+ source,
1008
+ typeof(T),
1009
+ MessageRegistrationType.BroadcastPostProcessor,
1010
+ priority
1011
+ )
1211
1012
  );
1212
1013
  }
1213
1014
 
@@ -1231,33 +1032,19 @@ namespace DxMessaging.Core
1231
1032
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1232
1033
  }
1233
1034
  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
- )
1035
+ new BroadcastRegistration<T>(
1036
+ this,
1037
+ RegistrationKind.BroadcastPostProcessorFast,
1038
+ source,
1039
+ broadcastPostProcessor,
1040
+ priority
1041
+ ),
1042
+ new MessageRegistrationMetadata(
1043
+ source,
1044
+ typeof(T),
1045
+ MessageRegistrationType.BroadcastPostProcessor,
1046
+ priority
1047
+ )
1261
1048
  );
1262
1049
  }
1263
1050
  #endif
@@ -1373,32 +1160,19 @@ namespace DxMessaging.Core
1373
1160
  }
1374
1161
 
1375
1162
  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
- )
1163
+ new BroadcastRegistration<T>(
1164
+ this,
1165
+ RegistrationKind.BroadcastWithoutSourceAction,
1166
+ default,
1167
+ broadcastHandler,
1168
+ priority
1169
+ ),
1170
+ new MessageRegistrationMetadata(
1171
+ null,
1172
+ typeof(T),
1173
+ MessageRegistrationType.BroadcastWithoutSource,
1174
+ priority
1175
+ )
1402
1176
  );
1403
1177
  }
1404
1178
 
@@ -1424,32 +1198,19 @@ namespace DxMessaging.Core
1424
1198
  }
1425
1199
 
1426
1200
  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
- )
1201
+ new BroadcastRegistration<T>(
1202
+ this,
1203
+ RegistrationKind.BroadcastWithoutSourceFast,
1204
+ default,
1205
+ broadcastHandler,
1206
+ priority
1207
+ ),
1208
+ new MessageRegistrationMetadata(
1209
+ null,
1210
+ typeof(T),
1211
+ MessageRegistrationType.BroadcastWithoutSource,
1212
+ priority
1213
+ )
1453
1214
  );
1454
1215
  }
1455
1216
 
@@ -1480,32 +1241,19 @@ namespace DxMessaging.Core
1480
1241
  }
1481
1242
 
1482
1243
  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
- )
1244
+ new BroadcastRegistration<T>(
1245
+ this,
1246
+ RegistrationKind.BroadcastWithoutSourcePostProcessorAction,
1247
+ default,
1248
+ broadcastHandler,
1249
+ priority
1250
+ ),
1251
+ new MessageRegistrationMetadata(
1252
+ null,
1253
+ typeof(T),
1254
+ MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1255
+ priority
1256
+ )
1509
1257
  );
1510
1258
  }
1511
1259
 
@@ -1531,32 +1279,19 @@ namespace DxMessaging.Core
1531
1279
  }
1532
1280
 
1533
1281
  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
- )
1282
+ new BroadcastRegistration<T>(
1283
+ this,
1284
+ RegistrationKind.BroadcastWithoutSourcePostProcessorFast,
1285
+ default,
1286
+ broadcastHandler,
1287
+ priority
1288
+ ),
1289
+ new MessageRegistrationMetadata(
1290
+ null,
1291
+ typeof(T),
1292
+ MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1293
+ priority
1294
+ )
1560
1295
  );
1561
1296
  }
1562
1297
 
@@ -1581,55 +1316,18 @@ namespace DxMessaging.Core
1581
1316
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1582
1317
  }
1583
1318
  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
- )
1319
+ new GlobalAcceptAllRegistration(
1320
+ this,
1321
+ acceptAllUntargeted,
1322
+ acceptAllTargeted,
1323
+ acceptAllBroadcast
1324
+ ),
1325
+ new MessageRegistrationMetadata(
1326
+ null,
1327
+ typeof(IMessage),
1328
+ MessageRegistrationType.GlobalAcceptAll,
1329
+ 0
1330
+ )
1633
1331
  );
1634
1332
  }
1635
1333
 
@@ -1663,55 +1361,18 @@ namespace DxMessaging.Core
1663
1361
  return MessageRegistrationHandle.CreateMessageRegistrationHandle();
1664
1362
  }
1665
1363
  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
- )
1364
+ new GlobalAcceptAllRegistration(
1365
+ this,
1366
+ acceptAllUntargeted,
1367
+ acceptAllTargeted,
1368
+ acceptAllBroadcast
1369
+ ),
1370
+ new MessageRegistrationMetadata(
1371
+ null,
1372
+ typeof(IMessage),
1373
+ MessageRegistrationType.GlobalAcceptAll,
1374
+ 0
1375
+ )
1715
1376
  );
1716
1377
  }
1717
1378
 
@@ -1734,19 +1395,18 @@ namespace DxMessaging.Core
1734
1395
  }
1735
1396
 
1736
1397
  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
- )
1398
+ new UntargetedRegistration<T>(
1399
+ this,
1400
+ RegistrationKind.UntargetedInterceptor,
1401
+ interceptor,
1402
+ priority
1403
+ ),
1404
+ new MessageRegistrationMetadata(
1405
+ null,
1406
+ typeof(T),
1407
+ MessageRegistrationType.UntargetedInterceptor,
1408
+ priority
1409
+ )
1750
1410
  );
1751
1411
  }
1752
1412
 
@@ -1769,19 +1429,19 @@ namespace DxMessaging.Core
1769
1429
  }
1770
1430
 
1771
1431
  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
- )
1432
+ new BroadcastRegistration<T>(
1433
+ this,
1434
+ RegistrationKind.BroadcastInterceptor,
1435
+ default,
1436
+ interceptor,
1437
+ priority
1438
+ ),
1439
+ new MessageRegistrationMetadata(
1440
+ null,
1441
+ typeof(T),
1442
+ MessageRegistrationType.BroadcastInterceptor,
1443
+ priority
1444
+ )
1785
1445
  );
1786
1446
  }
1787
1447
 
@@ -1804,54 +1464,61 @@ namespace DxMessaging.Core
1804
1464
  }
1805
1465
 
1806
1466
  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
- )
1467
+ new TargetedRegistration<T>(
1468
+ this,
1469
+ RegistrationKind.TargetedInterceptor,
1470
+ default,
1471
+ interceptor,
1472
+ priority
1473
+ ),
1474
+ new MessageRegistrationMetadata(
1475
+ null,
1476
+ typeof(T),
1477
+ MessageRegistrationType.TargetedInterceptor,
1478
+ priority
1479
+ )
1820
1480
  );
1821
1481
  }
1822
1482
 
1823
1483
  /// <summary>
1824
1484
  /// Handles the actual [de]registration wrapping and (potential) lazy execution.
1825
1485
  /// </summary>
1826
- /// <param name="registerAndGetDeregistration">Proxied registration function that returns a de-registration function.</param>
1827
- /// <param name="metadataProducer">Opaque metadata producer function.</param>
1486
+ /// <param name="registration">The unified per-handle registration object the public Register* method built. Calling <see cref="Registration.Register"/> (re)registers the handler on the bus and returns the matching de-registration.</param>
1487
+ /// <param name="metadata">Registration metadata recorded for the diagnostics inspector overlay and the registration-count warning.</param>
1828
1488
  /// <returns>A handle that allows for registration and de-registration.</returns>
1829
1489
  private MessageRegistrationHandle InternalRegister(
1830
- Func<MessageRegistrationHandle, Action> registerAndGetDeregistration,
1831
- Func<MessageRegistrationMetadata> metadataProducer
1490
+ Registration registration,
1491
+ MessageRegistrationMetadata metadata
1832
1492
  )
1833
1493
  {
1834
1494
  MessageRegistrationHandle handle =
1835
1495
  MessageRegistrationHandle.CreateMessageRegistrationHandle();
1836
1496
 
1837
- _registrations[handle] = Registration;
1497
+ registration.Handle = handle;
1498
+ _registrations[handle] = registration;
1838
1499
  _registrationOrder.Add(handle);
1839
- _metadata[handle] = metadataProducer();
1840
-
1841
- // Generally, registrations should take place before all calls to enable. Just in case, though...
1500
+ // Metadata is passed by value (a readonly struct) instead of through a
1501
+ // Func<MessageRegistrationMetadata> factory: the factory was invoked
1502
+ // immediately and unconditionally here, so it never deferred any work --
1503
+ // it only cost one delegate (plus its display class) allocation per
1504
+ // registration. Constructing the struct at the call site is identical
1505
+ // work without that closure. _metadata stays populated for every
1506
+ // registration because the inspector overlay and the registration-count
1507
+ // warning read it regardless of diagnostics mode.
1508
+ _metadata[handle] = metadata;
1509
+
1510
+ // Generally, registrations should take place before all calls to enable.
1511
+ // Just in case, though, register immediately if already enabled. We do not
1512
+ // register at staging time when disabled (the owner might not be awake), so
1513
+ // the Registration object is retained in _registrations to lazily
1514
+ // (re)register on Enable().
1842
1515
  if (_enabled)
1843
1516
  {
1844
- Registration();
1517
+ MessageHandler.HandlerDeregistration actualDeregistration = registration.Register();
1518
+ AddDeregistration(handle, actualDeregistration);
1845
1519
  }
1846
1520
 
1847
1521
  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
1522
  }
1856
1523
 
1857
1524
  /// <summary>
@@ -1880,7 +1547,7 @@ namespace DxMessaging.Core
1880
1547
  // _registrations.Values enumeration order, which permutes
1881
1548
  // after Remove/Add churn. This preserves the documented
1882
1549
  // equal-priority "registration order" dispatch contract across
1883
- // Disable()/Enable() cycles. Snapshot into _actionQueue first
1550
+ // Disable()/Enable() cycles. Snapshot into _registrationReplayQueue first
1884
1551
  // so replay tolerates re-entrant registration mutation.
1885
1552
  QueueRegistrationsInOrder();
1886
1553
  InvokeRegistrationQueueWithRollback();
@@ -2007,28 +1674,86 @@ namespace DxMessaging.Core
2007
1674
  }
2008
1675
  }
2009
1676
 
2010
- private void AddDeregistration(MessageRegistrationHandle handle, Action deregistration)
1677
+ private void AddDeregistration(
1678
+ MessageRegistrationHandle handle,
1679
+ MessageHandler.HandlerDeregistration deregistration
1680
+ )
1681
+ {
1682
+ // First (and usually only) de-registration: store the object inline in ONE dictionary
1683
+ // operation. TryAdd succeeds on the common first-registration path without the prior
1684
+ // separate TryGetValue-miss + indexer-insert (two probes of the same key); the rare
1685
+ // second-de-registration promotion path below re-reads the existing value.
1686
+ if (_deregistrations.TryAdd(handle, deregistration))
1687
+ {
1688
+ return;
1689
+ }
1690
+
1691
+ object existing = _deregistrations[handle];
1692
+ if (existing is PendingDeregistration pending)
1693
+ {
1694
+ pending.Add(deregistration);
1695
+ return;
1696
+ }
1697
+
1698
+ // A second de-registration accumulated on this handle: promote the inline object to a
1699
+ // PendingDeregistration holder that preserves the ordering / partial-failure / rollback
1700
+ // semantics for the multi-de-registration (retarget-recovery replay) case.
1701
+ PendingDeregistration promoted = new();
1702
+ promoted.Add((MessageHandler.HandlerDeregistration)existing);
1703
+ promoted.Add(deregistration);
1704
+ _deregistrations[handle] = promoted;
1705
+ }
1706
+
1707
+ // Logical de-registration count for a _deregistrations value (inline object == 1).
1708
+ private static int DeregistrationCount(object value) =>
1709
+ value is PendingDeregistration pending ? pending.Count : 1;
1710
+
1711
+ // Invokes the de-registration tail [startIndex..) for a _deregistrations value. For the
1712
+ // inline object (logical Count 1, index 0): on success it is consumed (shouldRemove = true);
1713
+ // on throw it is KEPT (retryable, shouldRemove = false); a rollback pass (startIndex &gt; 0)
1714
+ // leaves the baseline entry untouched. For a holder it delegates to InvokeFrom, mutating the
1715
+ // holder in place. Mirrors the prior PendingDeregistration-only semantics exactly.
1716
+ private static Exception InvokeDeregistration(
1717
+ object value,
1718
+ int startIndex,
1719
+ out bool shouldRemove
1720
+ )
2011
1721
  {
2012
- if (!_deregistrations.TryGetValue(handle, out PendingDeregistration pending))
1722
+ if (value is PendingDeregistration pending)
2013
1723
  {
2014
- pending = new PendingDeregistration();
2015
- _deregistrations[handle] = pending;
1724
+ Exception holderException = pending.InvokeFrom(startIndex);
1725
+ shouldRemove = pending.Count == 0;
1726
+ return holderException;
2016
1727
  }
2017
1728
 
2018
- pending.Add(deregistration);
1729
+ if (startIndex > 0)
1730
+ {
1731
+ // Rollback baseline pass: the inline head (logical index 0) is below the requested
1732
+ // tail, so leave it untouched.
1733
+ shouldRemove = false;
1734
+ return null;
1735
+ }
1736
+
1737
+ try
1738
+ {
1739
+ ((MessageHandler.HandlerDeregistration)value)?.Deregister();
1740
+ shouldRemove = true;
1741
+ return null;
1742
+ }
1743
+ catch (Exception exception)
1744
+ {
1745
+ // Keep the failed de-registration (retryable), exactly as the holder form does.
1746
+ shouldRemove = false;
1747
+ return exception;
1748
+ }
2019
1749
  }
2020
1750
 
2021
1751
  private Dictionary<MessageRegistrationHandle, int> SnapshotDeregistrationCounts()
2022
1752
  {
2023
1753
  Dictionary<MessageRegistrationHandle, int> snapshot = new(_deregistrations.Count);
2024
- foreach (
2025
- KeyValuePair<
2026
- MessageRegistrationHandle,
2027
- PendingDeregistration
2028
- > entry in _deregistrations
2029
- )
1754
+ foreach (KeyValuePair<MessageRegistrationHandle, object> entry in _deregistrations)
2030
1755
  {
2031
- snapshot[entry.Key] = entry.Value.Count;
1756
+ snapshot[entry.Key] = DeregistrationCount(entry.Value);
2032
1757
  }
2033
1758
 
2034
1759
  return snapshot;
@@ -2094,13 +1819,14 @@ namespace DxMessaging.Core
2094
1819
 
2095
1820
  private void QueueRegistrationsInOrder()
2096
1821
  {
2097
- _actionQueue.Clear();
1822
+ _registrationReplayQueue.Clear();
2098
1823
  int registrationCount = _registrationOrder.Count;
2099
1824
  for (int i = 0; i < registrationCount; ++i)
2100
1825
  {
2101
- if (_registrations.TryGetValue(_registrationOrder[i], out Action registration))
1826
+ MessageRegistrationHandle handle = _registrationOrder[i];
1827
+ if (_registrations.TryGetValue(handle, out Registration registration))
2102
1828
  {
2103
- _actionQueue.Add(registration);
1829
+ _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
2104
1830
  }
2105
1831
  }
2106
1832
  }
@@ -2109,7 +1835,7 @@ namespace DxMessaging.Core
2109
1835
  List<MessageRegistrationHandle> activeRetargetHandles
2110
1836
  )
2111
1837
  {
2112
- _actionQueue.Clear();
1838
+ _registrationReplayQueue.Clear();
2113
1839
  int registrationCount = _registrationOrder.Count;
2114
1840
  for (int i = 0; i < registrationCount; ++i)
2115
1841
  {
@@ -2117,47 +1843,75 @@ namespace DxMessaging.Core
2117
1843
  if (
2118
1844
  !activeRetargetHandles.Contains(handle)
2119
1845
  || _deregistrations.ContainsKey(handle)
2120
- || !_registrations.TryGetValue(handle, out Action registration)
1846
+ || !_registrations.TryGetValue(handle, out Registration registration)
2121
1847
  )
2122
1848
  {
2123
1849
  continue;
2124
1850
  }
2125
1851
 
2126
- _actionQueue.Add(registration);
1852
+ _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
2127
1853
  }
2128
1854
  }
2129
1855
 
2130
1856
  private void QueueRegistrationsWithoutRetryableDeregistrationsInOrder()
2131
1857
  {
2132
- _actionQueue.Clear();
1858
+ _registrationReplayQueue.Clear();
2133
1859
  int registrationCount = _registrationOrder.Count;
2134
1860
  for (int i = 0; i < registrationCount; ++i)
2135
1861
  {
2136
1862
  MessageRegistrationHandle handle = _registrationOrder[i];
2137
1863
  if (
2138
1864
  _deregistrations.ContainsKey(handle)
2139
- || !_registrations.TryGetValue(handle, out Action registration)
1865
+ || !_registrations.TryGetValue(handle, out Registration registration)
2140
1866
  )
2141
1867
  {
2142
1868
  continue;
2143
1869
  }
2144
1870
 
2145
- _actionQueue.Add(registration);
1871
+ _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
2146
1872
  }
2147
1873
  }
2148
1874
 
2149
- private void InvokeActionQueue()
1875
+ private void InvokeRegistrationReplayQueue()
2150
1876
  {
2151
1877
  try
2152
1878
  {
2153
- foreach (Action action in _actionQueue)
1879
+ foreach (StagedRegistration staged in _registrationReplayQueue)
2154
1880
  {
2155
- action?.Invoke();
1881
+ Registration registration = staged.Registration;
1882
+ if (registration == null)
1883
+ {
1884
+ continue;
1885
+ }
1886
+
1887
+ MessageHandler.HandlerDeregistration actualDeregistration =
1888
+ registration.Register();
1889
+ AddDeregistration(staged.Handle, actualDeregistration);
2156
1890
  }
2157
1891
  }
2158
1892
  finally
2159
1893
  {
2160
- _actionQueue.Clear();
1894
+ _registrationReplayQueue.Clear();
1895
+ }
1896
+ }
1897
+
1898
+ /// <summary>
1899
+ /// A staged registration captured for replay: the handle plus the unified
1900
+ /// per-handle <see cref="Registration"/> object that (re)registers the handler
1901
+ /// and returns its <see cref="MessageHandler.HandlerDeregistration"/>.
1902
+ /// Snapshotting the object reference (rather than a per-registration wrapper
1903
+ /// closure) preserves the original re-entrancy semantics while keeping the
1904
+ /// staging state in a single object per registration.
1905
+ /// </summary>
1906
+ private readonly struct StagedRegistration
1907
+ {
1908
+ public readonly MessageRegistrationHandle Handle;
1909
+ public readonly Registration Registration;
1910
+
1911
+ public StagedRegistration(MessageRegistrationHandle handle, Registration registration)
1912
+ {
1913
+ Handle = handle;
1914
+ Registration = registration;
2161
1915
  }
2162
1916
  }
2163
1917
 
@@ -2178,7 +1932,7 @@ namespace DxMessaging.Core
2178
1932
  {
2179
1933
  foreach (MessageRegistrationHandle handle in _handleQueue)
2180
1934
  {
2181
- if (!_deregistrations.TryGetValue(handle, out PendingDeregistration pending))
1935
+ if (!_deregistrations.TryGetValue(handle, out object value))
2182
1936
  {
2183
1937
  continue;
2184
1938
  }
@@ -2191,19 +1945,23 @@ namespace DxMessaging.Core
2191
1945
  startIndex = baselineCount;
2192
1946
  }
2193
1947
 
2194
- if (startIndex >= pending.Count)
1948
+ if (startIndex >= DeregistrationCount(value))
2195
1949
  {
2196
1950
  continue;
2197
1951
  }
2198
1952
  }
2199
1953
 
2200
- Exception exception = pending.InvokeFrom(startIndex);
1954
+ Exception exception = InvokeDeregistration(
1955
+ value,
1956
+ startIndex,
1957
+ out bool shouldRemove
1958
+ );
2201
1959
  if (exception != null)
2202
1960
  {
2203
1961
  firstException ??= exception;
2204
1962
  }
2205
1963
 
2206
- if (pending.Count == 0)
1964
+ if (shouldRemove)
2207
1965
  {
2208
1966
  _deregistrations.Remove(handle);
2209
1967
  }
@@ -2223,7 +1981,7 @@ namespace DxMessaging.Core
2223
1981
  SnapshotDeregistrationCounts();
2224
1982
  try
2225
1983
  {
2226
- InvokeActionQueue();
1984
+ InvokeRegistrationReplayQueue();
2227
1985
  }
2228
1986
  catch (Exception exception)
2229
1987
  {
@@ -2257,8 +2015,10 @@ namespace DxMessaging.Core
2257
2015
  private void ClearDiagnosticState()
2258
2016
  {
2259
2017
  _metadata.Clear();
2260
- _callCounts.Clear();
2261
- _emissionBuffer.Clear();
2018
+ // Clear through the backing fields so an inactive (never-materialized)
2019
+ // diagnostics collection is not allocated merely to be emptied.
2020
+ _callCountsBacking?.Clear();
2021
+ _emissionBufferBacking?.Clear();
2262
2022
  }
2263
2023
 
2264
2024
  private void PruneRegistrationStateToFailedDeregistrations()
@@ -2274,7 +2034,7 @@ namespace DxMessaging.Core
2274
2034
  RemoveRegistrationState(handle);
2275
2035
  }
2276
2036
 
2277
- _emissionBuffer.Clear();
2037
+ _emissionBufferBacking?.Clear();
2278
2038
  if (_registrations.Count == 0)
2279
2039
  {
2280
2040
  ClearDiagnosticState();
@@ -2286,7 +2046,7 @@ namespace DxMessaging.Core
2286
2046
  bool removedRegistration = _registrations.Remove(handle);
2287
2047
  _ = _registrationOrder.Remove(handle);
2288
2048
  _ = _metadata.Remove(handle);
2289
- _ = _callCounts.Remove(handle);
2049
+ _callCountsBacking?.Remove(handle);
2290
2050
  if (removedRegistration && _registrations.Count == 0)
2291
2051
  {
2292
2052
  ClearDiagnosticState();
@@ -2307,10 +2067,14 @@ namespace DxMessaging.Core
2307
2067
  /// </example>
2308
2068
  public void RemoveRegistration(MessageRegistrationHandle handle)
2309
2069
  {
2310
- if (_deregistrations.TryGetValue(handle, out PendingDeregistration pending))
2070
+ if (_deregistrations.TryGetValue(handle, out object value))
2311
2071
  {
2312
- Exception deregistrationException = pending.InvokeFrom(0);
2313
- if (pending.Count == 0)
2072
+ Exception deregistrationException = InvokeDeregistration(
2073
+ value,
2074
+ 0,
2075
+ out bool shouldRemove
2076
+ );
2077
+ if (shouldRemove)
2314
2078
  {
2315
2079
  _deregistrations.Remove(handle);
2316
2080
  }
@@ -2327,15 +2091,53 @@ namespace DxMessaging.Core
2327
2091
  RemoveRegistrationState(handle);
2328
2092
  }
2329
2093
 
2094
+ // Holds the live de-registration Actions for a single handle in the MULTI-de-registration
2095
+ // case (2+). The common case is EXACTLY ONE de-registration per handle (a staging function
2096
+ // registers once and returns one de-registration Action); that single Action is stored
2097
+ // INLINE as the _deregistrations dictionary value, so the common path allocates no
2098
+ // PendingDeregistration object at all (see AddDeregistration / InvokeDeregistration). This
2099
+ // holder is allocated only when a rare second de-registration accumulates on the same
2100
+ // handle -- a re-entrant retarget-recovery replay can stage one beyond the rollback
2101
+ // baseline -- at which point the inline Action is promoted into this holder's inline head
2102
+ // and the second spills to a lazily-allocated overflow list. This stays a class (mutated in
2103
+ // place through the _deregistrations dictionary), so reference semantics and every
2104
+ // call site are unchanged; only the storage shape changed.
2105
+ //
2106
+ // Invariant: when Count > 0 the head lives in _hasHead/_head and is the LOGICAL
2107
+ // FIRST entry; any further entries follow in _overflow in insertion order. Add
2108
+ // appends to the logical tail; InvokeFrom invokes a contiguous logical tail
2109
+ // [startIndex..Count), removing each success and KEEPING each failure (retryable),
2110
+ // then promotes the first surviving overflow entry into the head slot if the head
2111
+ // was consumed -- preserving the exact ordering, partial-failure, and
2112
+ // rollback-baseline (startIndex) semantics the List<Action> form had.
2330
2113
  private sealed class PendingDeregistration
2331
2114
  {
2332
- private readonly List<Action> _actions = new();
2115
+ private MessageHandler.HandlerDeregistration _head;
2116
+ private bool _hasHead;
2117
+ private List<MessageHandler.HandlerDeregistration> _overflow;
2333
2118
 
2334
- internal int Count => _actions.Count;
2119
+ internal int Count => (_hasHead ? 1 : 0) + (_overflow?.Count ?? 0);
2335
2120
 
2336
- internal void Add(Action action)
2121
+ internal void Add(MessageHandler.HandlerDeregistration action)
2337
2122
  {
2338
- _actions.Add(action);
2123
+ // Fill the inline head ONLY when nothing is stored. The empty-overflow
2124
+ // clause matters during the transient window inside InvokeFrom where the
2125
+ // head has been consumed but overflow survivors remain (before they are
2126
+ // promoted): a re-entrant Add then appends to the logical TAIL (overflow),
2127
+ // exactly as the List form did, rather than jumping the new entry ahead of
2128
+ // the survivors into the head slot. The overflow loop re-reads its Count,
2129
+ // so such a tail-appended entry is still invoked in the same pass -- the
2130
+ // List form's behavior preserved. (The stored Actions are pure bus
2131
+ // de-registration callbacks, so this re-entrancy is not reachable through
2132
+ // the public API today; the guard keeps the invariant honest regardless.)
2133
+ if (!_hasHead && (_overflow == null || _overflow.Count == 0))
2134
+ {
2135
+ _head = action;
2136
+ _hasHead = true;
2137
+ return;
2138
+ }
2139
+
2140
+ (_overflow ??= new List<MessageHandler.HandlerDeregistration>()).Add(action);
2339
2141
  }
2340
2142
 
2341
2143
  internal Exception InvokeFrom(int startIndex)
@@ -2346,24 +2148,699 @@ namespace DxMessaging.Core
2346
2148
  }
2347
2149
 
2348
2150
  Exception firstException = null;
2349
- for (int i = startIndex; i < _actions.Count; )
2151
+
2152
+ // Logical index 0 is the inline head; logical indices 1.. are _overflow.
2153
+ // Invoke the head only when it is within the requested tail (a rollback
2154
+ // pass with startIndex > 0 must leave the baseline head untouched).
2155
+ if (_hasHead && startIndex <= 0)
2350
2156
  {
2351
2157
  try
2352
2158
  {
2353
- _actions[i]?.Invoke();
2354
- _actions.RemoveAt(i);
2159
+ _head?.Deregister();
2160
+ _head = null;
2161
+ _hasHead = false;
2355
2162
  }
2356
2163
  catch (Exception exception)
2357
2164
  {
2165
+ // Keep the failed head (retryable), exactly as the List form did
2166
+ // by advancing past a throwing entry instead of removing it.
2358
2167
  firstException ??= exception;
2359
- ++i;
2360
2168
  }
2361
2169
  }
2362
2170
 
2171
+ if (_overflow is { Count: > 0 })
2172
+ {
2173
+ // Overflow entry j has logical index 1 + j; invoke those at or past
2174
+ // startIndex. Remove successes, keep failures (retryable).
2175
+ int overflowStart = startIndex <= 1 ? 0 : startIndex - 1;
2176
+ for (int j = overflowStart; j < _overflow.Count; )
2177
+ {
2178
+ try
2179
+ {
2180
+ _overflow[j]?.Deregister();
2181
+ _overflow.RemoveAt(j);
2182
+ }
2183
+ catch (Exception exception)
2184
+ {
2185
+ firstException ??= exception;
2186
+ ++j;
2187
+ }
2188
+ }
2189
+ }
2190
+
2191
+ // If the head was consumed but overflow survivors remain, promote the
2192
+ // first survivor into the head slot so the head is always the logical
2193
+ // first entry (keeping Count and future Adds consistent).
2194
+ if (!_hasHead && _overflow is { Count: > 0 })
2195
+ {
2196
+ _head = _overflow[0];
2197
+ _hasHead = true;
2198
+ _overflow.RemoveAt(0);
2199
+ }
2200
+
2363
2201
  return firstException;
2364
2202
  }
2365
2203
  }
2366
2204
 
2205
+ // Discriminates the per-handle Registration object's behaviour. Each value
2206
+ // pins (a) which MessageHandler.Register* method the kind-switch in
2207
+ // Registration.Register() calls and (b) which augmented-invoker body shape
2208
+ // (user-delegate type + by-value vs by-ref call) the bound FastHandler/
2209
+ // FastHandlerWithContext delegate runs. The values map 1:1 onto the former
2210
+ // per-method staging lambdas; the *Action / *Fast suffix mirrors the two
2211
+ // public overloads (Action<T>/Action<InstanceId,T> vs FastHandler<T>/
2212
+ // FastHandlerWithContext<T>) that previously had distinct AugmentedHandler
2213
+ // local functions.
2214
+ private enum RegistrationKind
2215
+ {
2216
+ TargetedHandlerAction,
2217
+ TargetedHandlerFast,
2218
+ TargetedPostProcessorAction,
2219
+ TargetedPostProcessorFast,
2220
+ TargetedWithoutTargetingAction,
2221
+ TargetedWithoutTargetingFast,
2222
+ TargetedWithoutTargetingPostProcessorAction,
2223
+ TargetedWithoutTargetingPostProcessorFast,
2224
+ TargetedInterceptor,
2225
+
2226
+ UntargetedHandlerAction,
2227
+ UntargetedHandlerFast,
2228
+ UntargetedPostProcessorFast,
2229
+ UntargetedInterceptor,
2230
+
2231
+ BroadcastHandlerAction,
2232
+ BroadcastHandlerFast,
2233
+ BroadcastPostProcessorAction,
2234
+ BroadcastPostProcessorFast,
2235
+ BroadcastWithoutSourceAction,
2236
+ BroadcastWithoutSourceFast,
2237
+ BroadcastWithoutSourcePostProcessorAction,
2238
+ BroadcastWithoutSourcePostProcessorFast,
2239
+ BroadcastInterceptor,
2240
+
2241
+ GlobalAcceptAllAction,
2242
+ GlobalAcceptAllFast,
2243
+ }
2244
+
2245
+ // The unified per-handle staging object. Replaces, per registration, the old
2246
+ // staging Func<handle, HandlerDeregistration> display class + delegate AND the
2247
+ // nested AugmentedHandler local-function delegate. The captured staging state
2248
+ // (owning token, handle, user handler delegate, target/source InstanceId,
2249
+ // priority, kind) lives in plain fields; the diagnostics-augmented invoker is
2250
+ // an instance method bound to this object (so MessageHandler still receives a
2251
+ // delegate on the hot path -- no virtual/interface call per dispatch).
2252
+ //
2253
+ // The base is non-generic so _registrations / the replay queue can hold every
2254
+ // registration polymorphically. The constrained MessageHandler.Register*<T>
2255
+ // calls each require T : ITargetedMessage / IUntargetedMessage /
2256
+ // IBroadcastMessage, which a single Registration<T> (T : IMessage) cannot
2257
+ // satisfy; the three concrete subclasses below carry the matching constraint
2258
+ // and each run a kind-SWITCH over only the kinds in their family (per the
2259
+ // user-accepted "unified object, accept the kind-switch" design -- NOT ~14
2260
+ // subclasses). GlobalAcceptAllRegistration is non-generic (its sub-handlers
2261
+ // are the fixed IMessage facades).
2262
+ private abstract class Registration
2263
+ {
2264
+ protected readonly MessageRegistrationToken Token;
2265
+ public MessageRegistrationHandle Handle;
2266
+
2267
+ protected Registration(MessageRegistrationToken token)
2268
+ {
2269
+ Token = token;
2270
+ }
2271
+
2272
+ // Kind-switch: (re)register on the bus and return the matching
2273
+ // HandlerDeregistration, exactly as the former staging lambda did. Reads
2274
+ // the token's CURRENT _messageBus (so Enable()/RetargetMessageBus replay
2275
+ // binds to the active bus, unchanged from when _messageBus was captured by
2276
+ // the staging closure at call time -- the staging closure also read the
2277
+ // field, not a snapshot).
2278
+ public abstract MessageHandler.HandlerDeregistration Register();
2279
+ }
2280
+
2281
+ private sealed class TargetedRegistration<T> : Registration
2282
+ where T : ITargetedMessage
2283
+ {
2284
+ private readonly RegistrationKind _kind;
2285
+ private readonly InstanceId _context;
2286
+ private readonly object _userHandler;
2287
+ private readonly int _priority;
2288
+
2289
+ // Strongly-typed views of _userHandler, resolved ONCE at Register() time
2290
+ // (cold) so the per-dispatch augmented invoker calls a typed field directly
2291
+ // -- no per-dispatch castclass or kind-switch (which would add an O(handlers)
2292
+ // cost on the hot dispatch path). Exactly one is set per registration kind.
2293
+ private Action<T> _scalarAction;
2294
+ private MessageHandler.FastHandler<T> _scalarFast;
2295
+ private Action<InstanceId, T> _contextAction;
2296
+ private MessageHandler.FastHandlerWithContext<T> _contextFast;
2297
+
2298
+ internal TargetedRegistration(
2299
+ MessageRegistrationToken token,
2300
+ RegistrationKind kind,
2301
+ InstanceId context,
2302
+ object userHandler,
2303
+ int priority
2304
+ )
2305
+ : base(token)
2306
+ {
2307
+ _kind = kind;
2308
+ _context = context;
2309
+ _userHandler = userHandler;
2310
+ _priority = priority;
2311
+ }
2312
+
2313
+ public override MessageHandler.HandlerDeregistration Register()
2314
+ {
2315
+ MessageHandler messageHandler = Token._messageHandler;
2316
+ IMessageBus messageBus = Token._messageBus;
2317
+ switch (_kind)
2318
+ {
2319
+ case RegistrationKind.TargetedHandlerAction:
2320
+ _scalarAction = (Action<T>)_userHandler;
2321
+ return messageHandler.RegisterTargetedMessageHandler(
2322
+ _context,
2323
+ _scalarAction,
2324
+ AugmentedScalarAction,
2325
+ priority: _priority,
2326
+ messageBus: messageBus
2327
+ );
2328
+ case RegistrationKind.TargetedHandlerFast:
2329
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2330
+ return messageHandler.RegisterTargetedMessageHandler(
2331
+ _context,
2332
+ _scalarFast,
2333
+ AugmentedScalarFast,
2334
+ priority: _priority,
2335
+ messageBus: messageBus
2336
+ );
2337
+ case RegistrationKind.TargetedPostProcessorAction:
2338
+ _scalarAction = (Action<T>)_userHandler;
2339
+ return messageHandler.RegisterTargetedPostProcessor(
2340
+ _context,
2341
+ _scalarAction,
2342
+ AugmentedScalarAction,
2343
+ _priority,
2344
+ messageBus
2345
+ );
2346
+ case RegistrationKind.TargetedPostProcessorFast:
2347
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2348
+ return messageHandler.RegisterTargetedPostProcessor(
2349
+ _context,
2350
+ _scalarFast,
2351
+ AugmentedScalarFast,
2352
+ _priority,
2353
+ messageBus
2354
+ );
2355
+ case RegistrationKind.TargetedWithoutTargetingAction:
2356
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2357
+ return messageHandler.RegisterTargetedWithoutTargeting(
2358
+ _contextAction,
2359
+ AugmentedContextAction,
2360
+ priority: _priority,
2361
+ messageBus: messageBus
2362
+ );
2363
+ case RegistrationKind.TargetedWithoutTargetingFast:
2364
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2365
+ return messageHandler.RegisterTargetedWithoutTargeting(
2366
+ _contextFast,
2367
+ AugmentedContextFast,
2368
+ priority: _priority,
2369
+ messageBus: messageBus
2370
+ );
2371
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorAction:
2372
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2373
+ return messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2374
+ _contextAction,
2375
+ AugmentedContextAction,
2376
+ _priority,
2377
+ messageBus
2378
+ );
2379
+ case RegistrationKind.TargetedWithoutTargetingPostProcessorFast:
2380
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2381
+ return messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2382
+ _contextFast,
2383
+ AugmentedContextFast,
2384
+ _priority,
2385
+ messageBus
2386
+ );
2387
+ case RegistrationKind.TargetedInterceptor:
2388
+ return messageHandler.RegisterTargetedInterceptor(
2389
+ (IMessageBus.TargetedInterceptor<T>)_userHandler,
2390
+ priority: _priority,
2391
+ messageBus: messageBus
2392
+ );
2393
+ default:
2394
+ throw new InvalidOperationException(
2395
+ $"Unexpected registration kind {_kind} for TargetedRegistration<{typeof(T)}>."
2396
+ );
2397
+ }
2398
+ }
2399
+
2400
+ // Scalar invokers (targeted handler / post-processor). The user's handler is
2401
+ // the identity/dedup key; this flat invoker runs for the default slot. Calls
2402
+ // the typed field directly (no castclass) then records the (message, _context)
2403
+ // emission, matching the former AugmentedHandler bodies exactly.
2404
+ private void AugmentedScalarAction(ref T message)
2405
+ {
2406
+ _scalarAction(message);
2407
+ if (Token._diagnosticMode)
2408
+ {
2409
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2410
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2411
+ }
2412
+ }
2413
+
2414
+ private void AugmentedScalarFast(ref T message)
2415
+ {
2416
+ _scalarFast(ref message);
2417
+ if (Token._diagnosticMode)
2418
+ {
2419
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2420
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2421
+ }
2422
+ }
2423
+
2424
+ // Context invokers (without-targeting handler / post-processor). Emission data
2425
+ // uses the dispatch-supplied target (the ref param), not the stored _context
2426
+ // (default for these kinds), matching the former bodies exactly.
2427
+ private void AugmentedContextAction(ref InstanceId target, ref T message)
2428
+ {
2429
+ _contextAction(target, message);
2430
+ if (Token._diagnosticMode)
2431
+ {
2432
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2433
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2434
+ }
2435
+ }
2436
+
2437
+ private void AugmentedContextFast(ref InstanceId target, ref T message)
2438
+ {
2439
+ _contextFast(ref target, ref message);
2440
+ if (Token._diagnosticMode)
2441
+ {
2442
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2443
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2444
+ }
2445
+ }
2446
+ }
2447
+
2448
+ private sealed class UntargetedRegistration<T> : Registration
2449
+ where T : IUntargetedMessage
2450
+ {
2451
+ private readonly RegistrationKind _kind;
2452
+ private readonly object _userHandler;
2453
+ private readonly int _priority;
2454
+
2455
+ // Typed views of _userHandler, resolved once at Register() time (cold) so the
2456
+ // per-dispatch invoker calls a typed field directly -- no castclass/switch on
2457
+ // the hot path. Exactly one is set per registration kind.
2458
+ private Action<T> _scalarAction;
2459
+ private MessageHandler.FastHandler<T> _scalarFast;
2460
+
2461
+ internal UntargetedRegistration(
2462
+ MessageRegistrationToken token,
2463
+ RegistrationKind kind,
2464
+ object userHandler,
2465
+ int priority
2466
+ )
2467
+ : base(token)
2468
+ {
2469
+ _kind = kind;
2470
+ _userHandler = userHandler;
2471
+ _priority = priority;
2472
+ }
2473
+
2474
+ public override MessageHandler.HandlerDeregistration Register()
2475
+ {
2476
+ MessageHandler messageHandler = Token._messageHandler;
2477
+ IMessageBus messageBus = Token._messageBus;
2478
+ switch (_kind)
2479
+ {
2480
+ case RegistrationKind.UntargetedHandlerAction:
2481
+ _scalarAction = (Action<T>)_userHandler;
2482
+ return messageHandler.RegisterUntargetedMessageHandler(
2483
+ _scalarAction,
2484
+ AugmentedScalarAction,
2485
+ priority: _priority,
2486
+ messageBus: messageBus
2487
+ );
2488
+ case RegistrationKind.UntargetedHandlerFast:
2489
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2490
+ return messageHandler.RegisterUntargetedMessageHandler(
2491
+ _scalarFast,
2492
+ AugmentedScalarFast,
2493
+ priority: _priority,
2494
+ messageBus: messageBus
2495
+ );
2496
+ case RegistrationKind.UntargetedPostProcessorFast:
2497
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2498
+ return messageHandler.RegisterUntargetedPostProcessor(
2499
+ _scalarFast,
2500
+ AugmentedScalarFast,
2501
+ _priority,
2502
+ messageBus
2503
+ );
2504
+ case RegistrationKind.UntargetedInterceptor:
2505
+ return messageHandler.RegisterUntargetedInterceptor(
2506
+ (IMessageBus.UntargetedInterceptor<T>)_userHandler,
2507
+ priority: _priority,
2508
+ messageBus: messageBus
2509
+ );
2510
+ default:
2511
+ throw new InvalidOperationException(
2512
+ $"Unexpected registration kind {_kind} for UntargetedRegistration<{typeof(T)}>."
2513
+ );
2514
+ }
2515
+ }
2516
+
2517
+ // Untargeted scalar invokers. No context: emission data carries the message
2518
+ // only, matching the former AugmentedHandler bodies exactly.
2519
+ private void AugmentedScalarAction(ref T message)
2520
+ {
2521
+ _scalarAction(message);
2522
+ if (Token._diagnosticMode)
2523
+ {
2524
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2525
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2526
+ }
2527
+ }
2528
+
2529
+ private void AugmentedScalarFast(ref T message)
2530
+ {
2531
+ _scalarFast(ref message);
2532
+ if (Token._diagnosticMode)
2533
+ {
2534
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2535
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2536
+ }
2537
+ }
2538
+ }
2539
+
2540
+ private sealed class BroadcastRegistration<T> : Registration
2541
+ where T : IBroadcastMessage
2542
+ {
2543
+ private readonly RegistrationKind _kind;
2544
+ private readonly InstanceId _context;
2545
+ private readonly object _userHandler;
2546
+ private readonly int _priority;
2547
+
2548
+ // Typed views of _userHandler, resolved once at Register() time (cold) so the
2549
+ // per-dispatch invoker calls a typed field directly -- no castclass/switch on
2550
+ // the hot path. Exactly one is set per registration kind.
2551
+ private Action<T> _scalarAction;
2552
+ private MessageHandler.FastHandler<T> _scalarFast;
2553
+ private Action<InstanceId, T> _contextAction;
2554
+ private MessageHandler.FastHandlerWithContext<T> _contextFast;
2555
+
2556
+ internal BroadcastRegistration(
2557
+ MessageRegistrationToken token,
2558
+ RegistrationKind kind,
2559
+ InstanceId context,
2560
+ object userHandler,
2561
+ int priority
2562
+ )
2563
+ : base(token)
2564
+ {
2565
+ _kind = kind;
2566
+ _context = context;
2567
+ _userHandler = userHandler;
2568
+ _priority = priority;
2569
+ }
2570
+
2571
+ public override MessageHandler.HandlerDeregistration Register()
2572
+ {
2573
+ MessageHandler messageHandler = Token._messageHandler;
2574
+ IMessageBus messageBus = Token._messageBus;
2575
+ switch (_kind)
2576
+ {
2577
+ case RegistrationKind.BroadcastHandlerAction:
2578
+ _scalarAction = (Action<T>)_userHandler;
2579
+ return messageHandler.RegisterSourcedBroadcastMessageHandler(
2580
+ _context,
2581
+ _scalarAction,
2582
+ AugmentedScalarAction,
2583
+ priority: _priority,
2584
+ messageBus: messageBus
2585
+ );
2586
+ case RegistrationKind.BroadcastHandlerFast:
2587
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2588
+ return messageHandler.RegisterSourcedBroadcastMessageHandler(
2589
+ _context,
2590
+ _scalarFast,
2591
+ AugmentedScalarFast,
2592
+ priority: _priority,
2593
+ messageBus: messageBus
2594
+ );
2595
+ case RegistrationKind.BroadcastPostProcessorAction:
2596
+ _scalarAction = (Action<T>)_userHandler;
2597
+ return messageHandler.RegisterSourcedBroadcastPostProcessor(
2598
+ _context,
2599
+ _scalarAction,
2600
+ AugmentedScalarAction,
2601
+ _priority,
2602
+ messageBus
2603
+ );
2604
+ case RegistrationKind.BroadcastPostProcessorFast:
2605
+ _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2606
+ return messageHandler.RegisterSourcedBroadcastPostProcessor(
2607
+ _context,
2608
+ _scalarFast,
2609
+ AugmentedScalarFast,
2610
+ _priority,
2611
+ messageBus
2612
+ );
2613
+ case RegistrationKind.BroadcastWithoutSourceAction:
2614
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2615
+ return messageHandler.RegisterSourcedBroadcastWithoutSource(
2616
+ _contextAction,
2617
+ AugmentedContextAction,
2618
+ priority: _priority,
2619
+ messageBus: messageBus
2620
+ );
2621
+ case RegistrationKind.BroadcastWithoutSourceFast:
2622
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2623
+ return messageHandler.RegisterSourcedBroadcastWithoutSource(
2624
+ _contextFast,
2625
+ AugmentedContextFast,
2626
+ priority: _priority,
2627
+ messageBus: messageBus
2628
+ );
2629
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorAction:
2630
+ _contextAction = (Action<InstanceId, T>)_userHandler;
2631
+ return messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
2632
+ _contextAction,
2633
+ AugmentedContextAction,
2634
+ priority: _priority,
2635
+ messageBus
2636
+ );
2637
+ case RegistrationKind.BroadcastWithoutSourcePostProcessorFast:
2638
+ _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2639
+ return messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
2640
+ _contextFast,
2641
+ AugmentedContextFast,
2642
+ priority: _priority,
2643
+ messageBus
2644
+ );
2645
+ case RegistrationKind.BroadcastInterceptor:
2646
+ return messageHandler.RegisterBroadcastInterceptor(
2647
+ (IMessageBus.BroadcastInterceptor<T>)_userHandler,
2648
+ priority: _priority,
2649
+ messageBus: messageBus
2650
+ );
2651
+ default:
2652
+ throw new InvalidOperationException(
2653
+ $"Unexpected registration kind {_kind} for BroadcastRegistration<{typeof(T)}>."
2654
+ );
2655
+ }
2656
+ }
2657
+
2658
+ // Broadcast scalar invokers. Emission data carries the stored source
2659
+ // (_context), matching the former AugmentedHandler bodies exactly.
2660
+ private void AugmentedScalarAction(ref T message)
2661
+ {
2662
+ _scalarAction(message);
2663
+ if (Token._diagnosticMode)
2664
+ {
2665
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2666
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2667
+ }
2668
+ }
2669
+
2670
+ private void AugmentedScalarFast(ref T message)
2671
+ {
2672
+ _scalarFast(ref message);
2673
+ if (Token._diagnosticMode)
2674
+ {
2675
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2676
+ Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2677
+ }
2678
+ }
2679
+
2680
+ // Broadcast context invokers for the without-source kinds. Emission data uses
2681
+ // the dispatch-supplied source (the ref param), not the stored _context
2682
+ // (default for these kinds), matching the former bodies exactly.
2683
+ private void AugmentedContextAction(ref InstanceId source, ref T message)
2684
+ {
2685
+ _contextAction(source, message);
2686
+ if (Token._diagnosticMode)
2687
+ {
2688
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2689
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2690
+ }
2691
+ }
2692
+
2693
+ private void AugmentedContextFast(ref InstanceId source, ref T message)
2694
+ {
2695
+ _contextFast(ref source, ref message);
2696
+ if (Token._diagnosticMode)
2697
+ {
2698
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2699
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2700
+ }
2701
+ }
2702
+ }
2703
+
2704
+ // Global accept-all is non-generic: its three sub-handlers are the fixed
2705
+ // IMessage facades. Stores the three user delegates (as object, since the two
2706
+ // public overloads differ in delegate shape -- Action vs FastHandler/
2707
+ // FastHandlerWithContext) and exposes six augmented sub-invokers (three per
2708
+ // overload shape). The kind-switch picks the matching MessageHandler.
2709
+ // RegisterGlobalAcceptAll overload and binds the three augmented invokers.
2710
+ private sealed class GlobalAcceptAllRegistration : Registration
2711
+ {
2712
+ private readonly RegistrationKind _kind;
2713
+
2714
+ // Typed sub-handlers (one shape-trio is set per kind). The per-dispatch
2715
+ // invokers call these directly -- no castclass on the hot global-dispatch
2716
+ // path (the heaviest fan-out, so the castclass cost there was the worst).
2717
+ private readonly Action<IUntargetedMessage> _untargetedAction;
2718
+ private readonly Action<InstanceId, ITargetedMessage> _targetedAction;
2719
+ private readonly Action<InstanceId, IBroadcastMessage> _broadcastAction;
2720
+ private readonly MessageHandler.FastHandler<IUntargetedMessage> _untargetedFast;
2721
+ private readonly MessageHandler.FastHandlerWithContext<ITargetedMessage> _targetedFast;
2722
+ private readonly MessageHandler.FastHandlerWithContext<IBroadcastMessage> _broadcastFast;
2723
+
2724
+ internal GlobalAcceptAllRegistration(
2725
+ MessageRegistrationToken token,
2726
+ Action<IUntargetedMessage> untargeted,
2727
+ Action<InstanceId, ITargetedMessage> targeted,
2728
+ Action<InstanceId, IBroadcastMessage> broadcast
2729
+ )
2730
+ : base(token)
2731
+ {
2732
+ _kind = RegistrationKind.GlobalAcceptAllAction;
2733
+ _untargetedAction = untargeted;
2734
+ _targetedAction = targeted;
2735
+ _broadcastAction = broadcast;
2736
+ }
2737
+
2738
+ internal GlobalAcceptAllRegistration(
2739
+ MessageRegistrationToken token,
2740
+ MessageHandler.FastHandler<IUntargetedMessage> untargeted,
2741
+ MessageHandler.FastHandlerWithContext<ITargetedMessage> targeted,
2742
+ MessageHandler.FastHandlerWithContext<IBroadcastMessage> broadcast
2743
+ )
2744
+ : base(token)
2745
+ {
2746
+ _kind = RegistrationKind.GlobalAcceptAllFast;
2747
+ _untargetedFast = untargeted;
2748
+ _targetedFast = targeted;
2749
+ _broadcastFast = broadcast;
2750
+ }
2751
+
2752
+ public override MessageHandler.HandlerDeregistration Register()
2753
+ {
2754
+ MessageHandler messageHandler = Token._messageHandler;
2755
+ IMessageBus messageBus = Token._messageBus;
2756
+ if (_kind == RegistrationKind.GlobalAcceptAllAction)
2757
+ {
2758
+ return messageHandler.RegisterGlobalAcceptAll(
2759
+ _untargetedAction,
2760
+ AugmentedUntargetedAction,
2761
+ _targetedAction,
2762
+ AugmentedTargetedAction,
2763
+ _broadcastAction,
2764
+ AugmentedBroadcastAction,
2765
+ messageBus
2766
+ );
2767
+ }
2768
+
2769
+ return messageHandler.RegisterGlobalAcceptAll(
2770
+ _untargetedFast,
2771
+ AugmentedUntargetedFast,
2772
+ _targetedFast,
2773
+ AugmentedTargetedFast,
2774
+ _broadcastFast,
2775
+ AugmentedBroadcastFast,
2776
+ messageBus
2777
+ );
2778
+ }
2779
+
2780
+ private void AugmentedUntargetedAction(IUntargetedMessage message)
2781
+ {
2782
+ _untargetedAction(message);
2783
+ if (Token._diagnosticMode)
2784
+ {
2785
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2786
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2787
+ }
2788
+ }
2789
+
2790
+ private void AugmentedTargetedAction(InstanceId target, ITargetedMessage message)
2791
+ {
2792
+ _targetedAction(target, message);
2793
+ if (Token._diagnosticMode)
2794
+ {
2795
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2796
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2797
+ }
2798
+ }
2799
+
2800
+ private void AugmentedBroadcastAction(InstanceId source, IBroadcastMessage message)
2801
+ {
2802
+ _broadcastAction(source, message);
2803
+ if (Token._diagnosticMode)
2804
+ {
2805
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2806
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2807
+ }
2808
+ }
2809
+
2810
+ private void AugmentedUntargetedFast(ref IUntargetedMessage message)
2811
+ {
2812
+ _untargetedFast(ref message);
2813
+ if (Token._diagnosticMode)
2814
+ {
2815
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2816
+ Token._emissionBuffer.Add(new MessageEmissionData(message));
2817
+ }
2818
+ }
2819
+
2820
+ private void AugmentedTargetedFast(ref InstanceId target, ref ITargetedMessage message)
2821
+ {
2822
+ _targetedFast(ref target, ref message);
2823
+ if (Token._diagnosticMode)
2824
+ {
2825
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2826
+ Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2827
+ }
2828
+ }
2829
+
2830
+ private void AugmentedBroadcastFast(
2831
+ ref InstanceId source,
2832
+ ref IBroadcastMessage message
2833
+ )
2834
+ {
2835
+ _broadcastFast(ref source, ref message);
2836
+ if (Token._diagnosticMode)
2837
+ {
2838
+ Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2839
+ Token._emissionBuffer.Add(new MessageEmissionData(message, source));
2840
+ }
2841
+ }
2842
+ }
2843
+
2367
2844
  /// <summary>
2368
2845
  /// Wraps a registration handle in an <see cref="IDisposable"/> that removes it on dispose.
2369
2846
  /// </summary>