com.wallstop-studios.dxmessaging 3.2.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 (61) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +8 -8
  3. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs +171 -13
  4. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +391 -96
  5. package/Editor/CustomEditors/MessageAwareComponentInspectorView.cs +279 -0
  6. package/Editor/CustomEditors/MessageAwareComponentInspectorView.cs.meta +11 -0
  7. package/Editor/DxMessagingEditorPalette.cs +66 -0
  8. package/Editor/DxMessagingEditorPalette.cs.meta +11 -0
  9. package/Editor/DxMessagingEditorTheme.cs +200 -0
  10. package/Editor/DxMessagingEditorTheme.cs.meta +11 -0
  11. package/Editor/Icons/dxmessaging-icon-256.png +0 -0
  12. package/Editor/Icons/dxmessaging-icon-256.png.meta +156 -0
  13. package/Editor/Icons/dxmessaging-icon-32.png +0 -0
  14. package/Editor/Icons/dxmessaging-icon-32.png.meta +156 -0
  15. package/Editor/Icons/dxmessaging-icon-48.png +0 -0
  16. package/Editor/Icons/dxmessaging-icon-48.png.meta +156 -0
  17. package/Editor/Icons.meta +8 -0
  18. package/Editor/Settings/DxMessagingSettingsProvider.cs +368 -33
  19. package/Editor/Testing/MessagingComponentEditorHarness.cs +11 -4
  20. package/Editor/Theme/DxMessagingTheme.uss +255 -0
  21. package/Editor/Theme/DxMessagingTheme.uss.meta +11 -0
  22. package/Editor/Theme/DxTokens.uss +90 -0
  23. package/Editor/Theme/DxTokens.uss.meta +11 -0
  24. package/Editor/Theme.meta +8 -0
  25. package/Editor/Windows/DxMessagingFlowGraphWindow.cs +5513 -0
  26. package/Editor/Windows/DxMessagingFlowGraphWindow.cs.meta +11 -0
  27. package/Editor/Windows/DxMessagingMessageMonitorWindow.cs +2288 -0
  28. package/Editor/Windows/DxMessagingMessageMonitorWindow.cs.meta +11 -0
  29. package/Editor/Windows.meta +8 -0
  30. package/README.md +84 -35
  31. package/Runtime/AssemblyInfo.cs +1 -0
  32. package/Runtime/Core/Diagnostics/MessageEmissionData.cs +29 -1
  33. package/Runtime/Core/Extensions/MessageExtensions.cs +4 -5
  34. package/Runtime/Core/Helper/MessageCache.cs +56 -23
  35. package/Runtime/Core/Internal/FlatDispatch.cs +32 -0
  36. package/Runtime/Core/Internal/TypedSlots.cs +3 -7
  37. package/Runtime/Core/MessageBus/MessageBus.cs +577 -142
  38. package/Runtime/Core/MessageBus/RegistrationLog.cs +25 -12
  39. package/Runtime/Core/MessageHandler.cs +774 -786
  40. package/Runtime/Core/MessageRegistrationHandle.cs +19 -5
  41. package/Runtime/Core/MessageRegistrationToken.cs +1080 -520
  42. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.cs +173 -0
  43. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.cs.meta +11 -0
  44. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.unity +424 -0
  45. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.unity.meta +7 -0
  46. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingReceiver.cs +185 -0
  47. package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingReceiver.cs.meta +11 -0
  48. package/Samples~/Diagnostics Tooling Exerciser/Messages.cs +46 -0
  49. package/Samples~/Diagnostics Tooling Exerciser/Messages.cs.meta +11 -0
  50. package/Samples~/Diagnostics Tooling Exerciser/README.md +53 -0
  51. package/Samples~/Diagnostics Tooling Exerciser/README.md.meta +7 -0
  52. package/Samples~/Diagnostics Tooling Exerciser/WallstopStudios.DxMessaging.DiagnosticsToolingExerciser.Sample.asmdef +13 -0
  53. package/Samples~/Diagnostics Tooling Exerciser/WallstopStudios.DxMessaging.DiagnosticsToolingExerciser.Sample.asmdef.meta +7 -0
  54. package/Samples~/Diagnostics Tooling Exerciser.meta +8 -0
  55. package/Samples~/Mini Combat/README.md +1 -1
  56. package/Samples~/Mini Combat/Walkthrough.md +3 -3
  57. package/docs/images/DxMessaging-banner.svg +79 -0
  58. package/docs/images/DxMessaging-banner.svg.meta +53 -0
  59. package/docs/images.meta +8 -0
  60. package/docs.meta +8 -0
  61. package/package.json +13 -4
@@ -68,46 +68,35 @@ namespace DxMessaging.Core
68
68
  // priority, and kind are now plain fields, and the diagnostics-augmented
69
69
  // invoker is an instance method bound to the object (the FastHandler<T> handed
70
70
  // to MessageHandler is (FastHandler<T>)registration.AugmentedHandlerScalar).
71
- // Net ~ -2 managed allocations per registration. The central replay loop pairs
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
72
75
  // the Registration with its handle and performs the AddDeregistration. The
73
76
  // re-entrancy snapshot semantics are unchanged: the replay queue captures the
74
77
  // Registration reference (exactly as it previously captured the staging Func),
75
78
  // so a registration removed mid-replay is still replayed if it was already
76
79
  // snapshotted.
77
- private readonly Dictionary<MessageRegistrationHandle, Registration> _registrations = new();
78
-
79
- // Staged-registration handles in registration order. Dictionary
80
- // enumeration order is NOT stable across Remove/Add churn, so Enable()
81
- // (and RetargetMessageBus) replay staged registrations by walking this
82
- // list instead of _registrations.Values; otherwise a Disable()/Enable()
83
- // cycle after churn would silently permute the documented
84
- // "same priority uses registration order" dispatch contract.
85
- // Invariant: contains exactly the keys of _registrations, in the order
86
- // InternalRegister staged them (handle ids are monotonically
87
- // increasing, so this list is also sorted by handle id).
88
- private readonly List<MessageRegistrationHandle> _registrationOrder = new();
89
-
90
- // Maps a handle to its live de-registration(s). The common case is EXACTLY ONE
91
- // de-registration per handle, so the single Action is stored INLINE as the dictionary
92
- // value (presence == pending, removal == done, keep-on-throw == retryable) -- no
93
- // per-registration PendingDeregistration object. A PendingDeregistration is allocated and
94
- // substituted ONLY when a second de-registration accumulates on the same handle (the rare
95
- // retarget-recovery replay; see AddDeregistration). The value is therefore either an
96
- // Action (1 de-registration) or a PendingDeregistration (2+).
97
- private readonly Dictionary<MessageRegistrationHandle, object> _deregistrations = new();
98
-
99
- // Snapshot of (handle, Registration object) pairs to replay on Enable() /
100
- // RetargetMessageBus. Snapshotting before invoking lets replay tolerate
101
- // re-entrant registration mutation (a handler that registers or removes
102
- // handlers while it runs). The pair carries the handle so the central replay
103
- // loop can call Registration.Register() and AddDeregistration without a
104
- // per-registration wrapper closure.
105
- private readonly List<StagedRegistration> _registrationReplayQueue = new();
106
- private readonly List<MessageRegistrationHandle> _handleQueue = new();
107
- internal readonly Dictionary<
108
- MessageRegistrationHandle,
109
- MessageRegistrationMetadata
110
- > _metadata = new();
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);
111
100
 
112
101
  // Diagnostics-only collections, allocated lazily on first use. A token whose
113
102
  // owner never enables diagnostics (the default -- GlobalDiagnosticsTargets is
@@ -134,6 +123,22 @@ namespace DxMessaging.Core
134
123
  IMessageBus.GlobalMessageBufferSize
135
124
  );
136
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
+ }
141
+
137
142
  private IMessageBus _messageBus;
138
143
  private bool _enabled;
139
144
  private bool _diagnosticMode = IMessageBus.ShouldEnableDiagnostics();
@@ -164,12 +169,6 @@ namespace DxMessaging.Core
164
169
  target,
165
170
  targetedHandler,
166
171
  priority
167
- ),
168
- new MessageRegistrationMetadata(
169
- target,
170
- typeof(T),
171
- MessageRegistrationType.Targeted,
172
- priority
173
172
  )
174
173
  );
175
174
  }
@@ -193,12 +192,6 @@ namespace DxMessaging.Core
193
192
  target,
194
193
  targetedHandler,
195
194
  priority
196
- ),
197
- new MessageRegistrationMetadata(
198
- target,
199
- typeof(T),
200
- MessageRegistrationType.Targeted,
201
- priority
202
195
  )
203
196
  );
204
197
  }
@@ -327,12 +320,6 @@ namespace DxMessaging.Core
327
320
  target,
328
321
  targetedPostProcessor,
329
322
  priority
330
- ),
331
- new MessageRegistrationMetadata(
332
- target,
333
- typeof(T),
334
- MessageRegistrationType.TargetedPostProcessor,
335
- priority
336
323
  )
337
324
  );
338
325
  }
@@ -359,12 +346,6 @@ namespace DxMessaging.Core
359
346
  target,
360
347
  targetedPostProcessor,
361
348
  priority
362
- ),
363
- new MessageRegistrationMetadata(
364
- target,
365
- typeof(T),
366
- MessageRegistrationType.TargetedPostProcessor,
367
- priority
368
349
  )
369
350
  );
370
351
  }
@@ -434,12 +415,6 @@ namespace DxMessaging.Core
434
415
  target,
435
416
  targetedPostProcessor,
436
417
  priority
437
- ),
438
- new MessageRegistrationMetadata(
439
- target,
440
- typeof(T),
441
- MessageRegistrationType.TargetedPostProcessor,
442
- priority
443
418
  )
444
419
  );
445
420
  }
@@ -466,12 +441,6 @@ namespace DxMessaging.Core
466
441
  target,
467
442
  targetedPostProcessor,
468
443
  priority
469
- ),
470
- new MessageRegistrationMetadata(
471
- target,
472
- typeof(T),
473
- MessageRegistrationType.TargetedPostProcessor,
474
- priority
475
444
  )
476
445
  );
477
446
  }
@@ -503,12 +472,6 @@ namespace DxMessaging.Core
503
472
  default,
504
473
  messageHandler,
505
474
  priority
506
- ),
507
- new MessageRegistrationMetadata(
508
- null,
509
- typeof(T),
510
- MessageRegistrationType.TargetedWithoutTargeting,
511
- priority
512
475
  )
513
476
  );
514
477
  }
@@ -540,12 +503,6 @@ namespace DxMessaging.Core
540
503
  default,
541
504
  messageHandler,
542
505
  priority
543
- ),
544
- new MessageRegistrationMetadata(
545
- null,
546
- typeof(T),
547
- MessageRegistrationType.TargetedWithoutTargeting,
548
- priority
549
506
  )
550
507
  );
551
508
  }
@@ -577,12 +534,6 @@ namespace DxMessaging.Core
577
534
  default,
578
535
  postProcessor,
579
536
  priority
580
- ),
581
- new MessageRegistrationMetadata(
582
- null,
583
- typeof(T),
584
- MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
585
- priority
586
537
  )
587
538
  );
588
539
  }
@@ -614,12 +565,6 @@ namespace DxMessaging.Core
614
565
  default,
615
566
  postProcessor,
616
567
  priority
617
- ),
618
- new MessageRegistrationMetadata(
619
- null,
620
- typeof(T),
621
- MessageRegistrationType.TargetedWithoutTargetingPostProcessor,
622
- priority
623
568
  )
624
569
  );
625
570
  }
@@ -657,12 +602,6 @@ namespace DxMessaging.Core
657
602
  RegistrationKind.UntargetedHandlerAction,
658
603
  untargetedHandler,
659
604
  priority
660
- ),
661
- new MessageRegistrationMetadata(
662
- null,
663
- typeof(T),
664
- MessageRegistrationType.Untargeted,
665
- priority
666
605
  )
667
606
  );
668
607
  }
@@ -699,12 +638,6 @@ namespace DxMessaging.Core
699
638
  RegistrationKind.UntargetedHandlerFast,
700
639
  untargetedHandler,
701
640
  priority
702
- ),
703
- new MessageRegistrationMetadata(
704
- null,
705
- typeof(T),
706
- MessageRegistrationType.Untargeted,
707
- priority
708
641
  )
709
642
  );
710
643
  }
@@ -732,12 +665,6 @@ namespace DxMessaging.Core
732
665
  RegistrationKind.UntargetedPostProcessorFast,
733
666
  untargetedPostProcessor,
734
667
  priority
735
- ),
736
- new MessageRegistrationMetadata(
737
- null,
738
- typeof(T),
739
- MessageRegistrationType.UntargetedPostProcessor,
740
- priority
741
668
  )
742
669
  );
743
670
  }
@@ -760,12 +687,6 @@ namespace DxMessaging.Core
760
687
  source,
761
688
  broadcastHandler,
762
689
  priority
763
- ),
764
- new MessageRegistrationMetadata(
765
- source,
766
- typeof(T),
767
- MessageRegistrationType.Broadcast,
768
- priority
769
690
  )
770
691
  );
771
692
  }
@@ -788,12 +709,6 @@ namespace DxMessaging.Core
788
709
  source,
789
710
  broadcastHandler,
790
711
  priority
791
- ),
792
- new MessageRegistrationMetadata(
793
- source,
794
- typeof(T),
795
- MessageRegistrationType.Broadcast,
796
- priority
797
712
  )
798
713
  );
799
714
  }
@@ -817,12 +732,6 @@ namespace DxMessaging.Core
817
732
  source,
818
733
  broadcastPostProcessor,
819
734
  priority
820
- ),
821
- new MessageRegistrationMetadata(
822
- source,
823
- typeof(T),
824
- MessageRegistrationType.BroadcastPostProcessor,
825
- priority
826
735
  )
827
736
  );
828
737
  }
@@ -845,12 +754,6 @@ namespace DxMessaging.Core
845
754
  source,
846
755
  broadcastPostProcessor,
847
756
  priority
848
- ),
849
- new MessageRegistrationMetadata(
850
- source,
851
- typeof(T),
852
- MessageRegistrationType.BroadcastPostProcessor,
853
- priority
854
757
  )
855
758
  );
856
759
  }
@@ -1002,12 +905,6 @@ namespace DxMessaging.Core
1002
905
  source,
1003
906
  broadcastPostProcessor,
1004
907
  priority
1005
- ),
1006
- new MessageRegistrationMetadata(
1007
- source,
1008
- typeof(T),
1009
- MessageRegistrationType.BroadcastPostProcessor,
1010
- priority
1011
908
  )
1012
909
  );
1013
910
  }
@@ -1038,12 +935,6 @@ namespace DxMessaging.Core
1038
935
  source,
1039
936
  broadcastPostProcessor,
1040
937
  priority
1041
- ),
1042
- new MessageRegistrationMetadata(
1043
- source,
1044
- typeof(T),
1045
- MessageRegistrationType.BroadcastPostProcessor,
1046
- priority
1047
938
  )
1048
939
  );
1049
940
  }
@@ -1166,12 +1057,6 @@ namespace DxMessaging.Core
1166
1057
  default,
1167
1058
  broadcastHandler,
1168
1059
  priority
1169
- ),
1170
- new MessageRegistrationMetadata(
1171
- null,
1172
- typeof(T),
1173
- MessageRegistrationType.BroadcastWithoutSource,
1174
- priority
1175
1060
  )
1176
1061
  );
1177
1062
  }
@@ -1204,12 +1089,6 @@ namespace DxMessaging.Core
1204
1089
  default,
1205
1090
  broadcastHandler,
1206
1091
  priority
1207
- ),
1208
- new MessageRegistrationMetadata(
1209
- null,
1210
- typeof(T),
1211
- MessageRegistrationType.BroadcastWithoutSource,
1212
- priority
1213
1092
  )
1214
1093
  );
1215
1094
  }
@@ -1247,12 +1126,6 @@ namespace DxMessaging.Core
1247
1126
  default,
1248
1127
  broadcastHandler,
1249
1128
  priority
1250
- ),
1251
- new MessageRegistrationMetadata(
1252
- null,
1253
- typeof(T),
1254
- MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1255
- priority
1256
1129
  )
1257
1130
  );
1258
1131
  }
@@ -1285,12 +1158,6 @@ namespace DxMessaging.Core
1285
1158
  default,
1286
1159
  broadcastHandler,
1287
1160
  priority
1288
- ),
1289
- new MessageRegistrationMetadata(
1290
- null,
1291
- typeof(T),
1292
- MessageRegistrationType.BroadcastWithoutSourcePostProcessor,
1293
- priority
1294
1161
  )
1295
1162
  );
1296
1163
  }
@@ -1321,12 +1188,6 @@ namespace DxMessaging.Core
1321
1188
  acceptAllUntargeted,
1322
1189
  acceptAllTargeted,
1323
1190
  acceptAllBroadcast
1324
- ),
1325
- new MessageRegistrationMetadata(
1326
- null,
1327
- typeof(IMessage),
1328
- MessageRegistrationType.GlobalAcceptAll,
1329
- 0
1330
1191
  )
1331
1192
  );
1332
1193
  }
@@ -1366,12 +1227,6 @@ namespace DxMessaging.Core
1366
1227
  acceptAllUntargeted,
1367
1228
  acceptAllTargeted,
1368
1229
  acceptAllBroadcast
1369
- ),
1370
- new MessageRegistrationMetadata(
1371
- null,
1372
- typeof(IMessage),
1373
- MessageRegistrationType.GlobalAcceptAll,
1374
- 0
1375
1230
  )
1376
1231
  );
1377
1232
  }
@@ -1395,18 +1250,7 @@ namespace DxMessaging.Core
1395
1250
  }
1396
1251
 
1397
1252
  return InternalRegister(
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
- )
1253
+ new UntargetedInterceptorRegistration<T>(this, interceptor, priority)
1410
1254
  );
1411
1255
  }
1412
1256
 
@@ -1429,19 +1273,7 @@ namespace DxMessaging.Core
1429
1273
  }
1430
1274
 
1431
1275
  return InternalRegister(
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
- )
1276
+ new BroadcastInterceptorRegistration<T>(this, interceptor, priority)
1445
1277
  );
1446
1278
  }
1447
1279
 
@@ -1464,19 +1296,7 @@ namespace DxMessaging.Core
1464
1296
  }
1465
1297
 
1466
1298
  return InternalRegister(
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
- )
1299
+ new TargetedInterceptorRegistration<T>(this, interceptor, priority)
1480
1300
  );
1481
1301
  }
1482
1302
 
@@ -1484,29 +1304,29 @@ namespace DxMessaging.Core
1484
1304
  /// Handles the actual [de]registration wrapping and (potential) lazy execution.
1485
1305
  /// </summary>
1486
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>
1487
- /// <param name="metadata">Registration metadata recorded for the diagnostics inspector overlay and the registration-count warning.</param>
1488
1307
  /// <returns>A handle that allows for registration and de-registration.</returns>
1489
- private MessageRegistrationHandle InternalRegister(
1490
- Registration registration,
1491
- MessageRegistrationMetadata metadata
1492
- )
1308
+ private MessageRegistrationHandle InternalRegister(Registration registration)
1493
1309
  {
1310
+ int slotIndex = AllocateSlot();
1494
1311
  MessageRegistrationHandle handle =
1495
- MessageRegistrationHandle.CreateMessageRegistrationHandle();
1496
-
1312
+ MessageRegistrationHandle.CreateMessageRegistrationHandle(slotIndex);
1497
1313
  registration.Handle = handle;
1498
- _registrations[handle] = registration;
1499
- _registrationOrder.Add(handle);
1500
- // Metadata is passed by value (a readonly struct) instead of through a
1501
- // Func<MessageRegistrationMetadata> factory: the factory was invoked
1502
- // immediately and unconditionally here, so it never deferred any work --
1503
- // it only cost one delegate (plus its display class) allocation per
1504
- // registration. Constructing the struct at the call site is identical
1505
- // work without that closure. _metadata stays populated for every
1506
- // registration because the inspector overlay and the registration-count
1507
- // warning read it regardless of diagnostics mode.
1508
- _metadata[handle] = metadata;
1509
-
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;
1510
1330
  // Generally, registrations should take place before all calls to enable.
1511
1331
  // Just in case, though, register immediately if already enabled. We do not
1512
1332
  // register at staging time when disabled (the owner might not be awake), so
@@ -1515,7 +1335,7 @@ namespace DxMessaging.Core
1515
1335
  if (_enabled)
1516
1336
  {
1517
1337
  MessageHandler.HandlerDeregistration actualDeregistration = registration.Register();
1518
- AddDeregistration(handle, actualDeregistration);
1338
+ AddDeregistration(slotIndex, handle.Id, actualDeregistration);
1519
1339
  }
1520
1340
 
1521
1341
  return handle;
@@ -1540,7 +1360,7 @@ namespace DxMessaging.Core
1540
1360
  return;
1541
1361
  }
1542
1362
 
1543
- if (_registrations is { Count: > 0 })
1363
+ if (_registrationCount > 0)
1544
1364
  {
1545
1365
  // Replay staged registrations in original registration order
1546
1366
  // (via _registrationOrder) rather than in
@@ -1569,13 +1389,13 @@ namespace DxMessaging.Core
1569
1389
  /// </example>
1570
1390
  public void Disable()
1571
1391
  {
1572
- if (!_enabled && _deregistrations.Count == 0)
1392
+ if (!_enabled && _deregistrationCount == 0)
1573
1393
  {
1574
1394
  return;
1575
1395
  }
1576
1396
 
1577
1397
  Exception deregistrationException = InvokeDeregistrationQueue();
1578
- _enabled = _deregistrations.Count > 0;
1398
+ _enabled = _deregistrationCount > 0;
1579
1399
  if (deregistrationException != null)
1580
1400
  {
1581
1401
  ExceptionDispatchInfo.Capture(deregistrationException).Throw();
@@ -1598,13 +1418,12 @@ namespace DxMessaging.Core
1598
1418
  if (deregistrationException == null)
1599
1419
  {
1600
1420
  _enabled = false;
1601
- _registrations?.Clear();
1602
- _registrationOrder?.Clear();
1421
+ ClearRegistrationArena();
1603
1422
  ClearDiagnosticState();
1604
1423
  return;
1605
1424
  }
1606
1425
 
1607
- _enabled = _deregistrations.Count > 0;
1426
+ _enabled = _deregistrationCount > 0;
1608
1427
  PruneRegistrationStateToFailedDeregistrations();
1609
1428
  ExceptionDispatchInfo.Capture(deregistrationException).Throw();
1610
1429
  }
@@ -1627,16 +1446,21 @@ namespace DxMessaging.Core
1627
1446
  bool rebindActiveRegistrations =
1628
1447
  effectiveMode == MessageBusRebindMode.RebindActive
1629
1448
  && _enabled
1630
- && _deregistrations is { Count: > 0 };
1449
+ && _deregistrationCount > 0;
1631
1450
  if (sameBus && !rebindActiveRegistrations)
1632
1451
  {
1633
1452
  return;
1634
1453
  }
1635
1454
 
1636
1455
  IMessageBus previousMessageBus = _messageBus;
1637
- List<MessageRegistrationHandle> activeRetargetHandles = rebindActiveRegistrations
1638
- ? new List<MessageRegistrationHandle>(_deregistrations.Keys)
1639
- : null;
1456
+ List<MessageRegistrationHandle> activeRetargetHandles = null;
1457
+ if (rebindActiveRegistrations)
1458
+ {
1459
+ activeRetargetHandles = _activeRetargetHandles ??=
1460
+ new List<MessageRegistrationHandle>();
1461
+ activeRetargetHandles.Clear();
1462
+ QueueActiveHandles(activeRetargetHandles);
1463
+ }
1640
1464
  if (rebindActiveRegistrations)
1641
1465
  {
1642
1466
  Exception deregistrationException = InvokeDeregistrationQueue();
@@ -1646,7 +1470,7 @@ namespace DxMessaging.Core
1646
1470
  previousMessageBus,
1647
1471
  activeRetargetHandles
1648
1472
  );
1649
- _enabled = _deregistrations.Count > 0;
1473
+ _enabled = _deregistrationCount > 0;
1650
1474
  ExceptionDispatchInfo.Capture(deregistrationException).Throw();
1651
1475
  }
1652
1476
 
@@ -1655,7 +1479,7 @@ namespace DxMessaging.Core
1655
1479
 
1656
1480
  _messageBus = messageBus;
1657
1481
 
1658
- if (rebindActiveRegistrations && _registrations is { Count: > 0 })
1482
+ if (rebindActiveRegistrations && _registrationCount > 0)
1659
1483
  {
1660
1484
  // Mirror Enable(): rebind in original registration order so the
1661
1485
  // equal-priority dispatch order survives a bus retarget.
@@ -1675,20 +1499,32 @@ namespace DxMessaging.Core
1675
1499
  }
1676
1500
 
1677
1501
  private void AddDeregistration(
1678
- MessageRegistrationHandle handle,
1502
+ int slotIndex,
1503
+ long id,
1679
1504
  MessageHandler.HandlerDeregistration deregistration
1680
1505
  )
1681
1506
  {
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))
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)
1687
1522
  {
1523
+ _slots[slotIndex].Deregistration = deregistration;
1524
+ ++_deregistrationCount;
1688
1525
  return;
1689
1526
  }
1690
1527
 
1691
- object existing = _deregistrations[handle];
1692
1528
  if (existing is PendingDeregistration pending)
1693
1529
  {
1694
1530
  pending.Add(deregistration);
@@ -1701,7 +1537,33 @@ namespace DxMessaging.Core
1701
1537
  PendingDeregistration promoted = new();
1702
1538
  promoted.Add((MessageHandler.HandlerDeregistration)existing);
1703
1539
  promoted.Add(deregistration);
1704
- _deregistrations[handle] = promoted;
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;
1705
1567
  }
1706
1568
 
1707
1569
  // Logical de-registration count for a _deregistrations value (inline object == 1).
@@ -1750,10 +1612,31 @@ namespace DxMessaging.Core
1750
1612
 
1751
1613
  private Dictionary<MessageRegistrationHandle, int> SnapshotDeregistrationCounts()
1752
1614
  {
1753
- Dictionary<MessageRegistrationHandle, int> snapshot = new(_deregistrations.Count);
1754
- foreach (KeyValuePair<MessageRegistrationHandle, object> 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
1622
+ )
1623
+ {
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)
1755
1633
  {
1756
- snapshot[entry.Key] = DeregistrationCount(entry.Value);
1634
+ foreach (
1635
+ KeyValuePair<MessageRegistrationHandle, object> entry in _orphanDeregistrations
1636
+ )
1637
+ {
1638
+ snapshot[entry.Key] = DeregistrationCount(entry.Value);
1639
+ }
1757
1640
  }
1758
1641
 
1759
1642
  return snapshot;
@@ -1791,9 +1674,9 @@ namespace DxMessaging.Core
1791
1674
  private void RestoreRegistrationsAfterRetargetFailure(IMessageBus previousMessageBus)
1792
1675
  {
1793
1676
  _messageBus = previousMessageBus;
1794
- if (_registrations.Count == 0)
1677
+ if (_registrationCount == 0)
1795
1678
  {
1796
- _enabled = false;
1679
+ _enabled = _deregistrationCount > 0;
1797
1680
  return;
1798
1681
  }
1799
1682
 
@@ -1805,7 +1688,7 @@ namespace DxMessaging.Core
1805
1688
  }
1806
1689
  catch (Exception restoreException)
1807
1690
  {
1808
- _enabled = _deregistrations.Count > 0;
1691
+ _enabled = _deregistrationCount > 0;
1809
1692
  if (MessagingDebug.enabled)
1810
1693
  {
1811
1694
  MessagingDebug.Log(
@@ -1819,15 +1702,16 @@ namespace DxMessaging.Core
1819
1702
 
1820
1703
  private void QueueRegistrationsInOrder()
1821
1704
  {
1822
- _registrationReplayQueue.Clear();
1823
- int registrationCount = _registrationOrder.Count;
1824
- 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
+ )
1825
1712
  {
1826
- MessageRegistrationHandle handle = _registrationOrder[i];
1827
- if (_registrations.TryGetValue(handle, out Registration registration))
1828
- {
1829
- _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
1830
- }
1713
+ RegistrationSlot slot = _slots[slotIndex];
1714
+ queue.Add(new StagedRegistration(slotIndex, slot.Id, slot.Registration));
1831
1715
  }
1832
1716
  }
1833
1717
 
@@ -1835,40 +1719,42 @@ namespace DxMessaging.Core
1835
1719
  List<MessageRegistrationHandle> activeRetargetHandles
1836
1720
  )
1837
1721
  {
1838
- _registrationReplayQueue.Clear();
1839
- int registrationCount = _registrationOrder.Count;
1840
- 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
+ )
1841
1729
  {
1842
- MessageRegistrationHandle handle = _registrationOrder[i];
1843
- if (
1844
- !activeRetargetHandles.Contains(handle)
1845
- || _deregistrations.ContainsKey(handle)
1846
- || !_registrations.TryGetValue(handle, out Registration registration)
1847
- )
1730
+ RegistrationSlot slot = _slots[slotIndex];
1731
+ MessageRegistrationHandle handle = slot.Registration.Handle;
1732
+ if (!activeRetargetHandles.Contains(handle) || slot.Deregistration != null)
1848
1733
  {
1849
1734
  continue;
1850
1735
  }
1851
1736
 
1852
- _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
1737
+ queue.Add(new StagedRegistration(slotIndex, slot.Id, slot.Registration));
1853
1738
  }
1854
1739
  }
1855
1740
 
1856
1741
  private void QueueRegistrationsWithoutRetryableDeregistrationsInOrder()
1857
1742
  {
1858
- _registrationReplayQueue.Clear();
1859
- int registrationCount = _registrationOrder.Count;
1860
- 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
+ )
1861
1750
  {
1862
- MessageRegistrationHandle handle = _registrationOrder[i];
1863
- if (
1864
- _deregistrations.ContainsKey(handle)
1865
- || !_registrations.TryGetValue(handle, out Registration registration)
1866
- )
1751
+ RegistrationSlot slot = _slots[slotIndex];
1752
+ if (slot.Deregistration != null)
1867
1753
  {
1868
1754
  continue;
1869
1755
  }
1870
1756
 
1871
- _registrationReplayQueue.Add(new StagedRegistration(handle, registration));
1757
+ queue.Add(new StagedRegistration(slotIndex, slot.Id, slot.Registration));
1872
1758
  }
1873
1759
  }
1874
1760
 
@@ -1876,7 +1762,7 @@ namespace DxMessaging.Core
1876
1762
  {
1877
1763
  try
1878
1764
  {
1879
- foreach (StagedRegistration staged in _registrationReplayQueue)
1765
+ foreach (StagedRegistration staged in GetRegistrationReplayQueue())
1880
1766
  {
1881
1767
  Registration registration = staged.Registration;
1882
1768
  if (registration == null)
@@ -1886,12 +1772,12 @@ namespace DxMessaging.Core
1886
1772
 
1887
1773
  MessageHandler.HandlerDeregistration actualDeregistration =
1888
1774
  registration.Register();
1889
- AddDeregistration(staged.Handle, actualDeregistration);
1775
+ AddDeregistration(staged.Slot, staged.Id, actualDeregistration);
1890
1776
  }
1891
1777
  }
1892
1778
  finally
1893
1779
  {
1894
- _registrationReplayQueue.Clear();
1780
+ _registrationReplayQueue?.Clear();
1895
1781
  }
1896
1782
  }
1897
1783
 
@@ -1905,12 +1791,14 @@ namespace DxMessaging.Core
1905
1791
  /// </summary>
1906
1792
  private readonly struct StagedRegistration
1907
1793
  {
1908
- public readonly MessageRegistrationHandle Handle;
1794
+ public readonly int Slot;
1795
+ public readonly long Id;
1909
1796
  public readonly Registration Registration;
1910
1797
 
1911
- public StagedRegistration(MessageRegistrationHandle handle, Registration registration)
1798
+ public StagedRegistration(int slot, long id, Registration registration)
1912
1799
  {
1913
- Handle = handle;
1800
+ Slot = slot;
1801
+ Id = id;
1914
1802
  Registration = registration;
1915
1803
  }
1916
1804
  }
@@ -1919,22 +1807,76 @@ namespace DxMessaging.Core
1919
1807
  Dictionary<MessageRegistrationHandle, int> baselineCounts = null
1920
1808
  )
1921
1809
  {
1922
- if (_deregistrations.Count == 0)
1810
+ if (_deregistrationCount == 0)
1923
1811
  {
1924
1812
  return null;
1925
1813
  }
1926
1814
 
1927
1815
  bool scopedToAddedDeregistrations = baselineCounts != null;
1928
- _handleQueue.Clear();
1929
- _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
+ }
1930
1848
  Exception firstException = null;
1931
1849
  try
1932
1850
  {
1933
- foreach (MessageRegistrationHandle handle in _handleQueue)
1851
+ foreach (TeardownIdentity identity in teardownQueue)
1934
1852
  {
1935
- if (!_deregistrations.TryGetValue(handle, out object value))
1853
+ MessageRegistrationHandle handle = MessageRegistrationHandle.FromIdentity(
1854
+ identity.Id,
1855
+ identity.Slot
1856
+ );
1857
+ object value;
1858
+ if (identity.IsOrphan)
1936
1859
  {
1937
- 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
+ }
1938
1880
  }
1939
1881
 
1940
1882
  int startIndex = 0;
@@ -1963,13 +1905,24 @@ namespace DxMessaging.Core
1963
1905
 
1964
1906
  if (shouldRemove)
1965
1907
  {
1966
- _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
+ }
1967
1920
  }
1968
1921
  }
1969
1922
  }
1970
1923
  finally
1971
1924
  {
1972
- _handleQueue.Clear();
1925
+ teardownQueue.Clear();
1973
1926
  }
1974
1927
 
1975
1928
  return firstException;
@@ -1986,7 +1939,7 @@ namespace DxMessaging.Core
1986
1939
  catch (Exception exception)
1987
1940
  {
1988
1941
  RollBackDeregistrationsAfterRegistrationFailure(rollbackBaseline);
1989
- _enabled = _deregistrations.Count > 0;
1942
+ _enabled = _deregistrationCount > 0;
1990
1943
  ExceptionDispatchInfo.Capture(exception).Throw();
1991
1944
  throw;
1992
1945
  }
@@ -1996,7 +1949,7 @@ namespace DxMessaging.Core
1996
1949
  Dictionary<MessageRegistrationHandle, int> rollbackBaseline
1997
1950
  )
1998
1951
  {
1999
- if (_deregistrations.Count == 0)
1952
+ if (_deregistrationCount == 0)
2000
1953
  {
2001
1954
  return;
2002
1955
  }
@@ -2014,7 +1967,6 @@ namespace DxMessaging.Core
2014
1967
 
2015
1968
  private void ClearDiagnosticState()
2016
1969
  {
2017
- _metadata.Clear();
2018
1970
  // Clear through the backing fields so an inactive (never-materialized)
2019
1971
  // diagnostics collection is not allocated merely to be emptied.
2020
1972
  _callCountsBacking?.Clear();
@@ -2023,19 +1975,20 @@ namespace DxMessaging.Core
2023
1975
 
2024
1976
  private void PruneRegistrationStateToFailedDeregistrations()
2025
1977
  {
2026
- for (int i = _registrationOrder.Count - 1; i >= 0; --i)
1978
+ int slotIndex = _registrationTail;
1979
+ while (slotIndex >= 0)
2027
1980
  {
2028
- MessageRegistrationHandle handle = _registrationOrder[i];
2029
- if (_deregistrations.ContainsKey(handle))
1981
+ int previous = _slots[slotIndex].Previous;
1982
+ if (_slots[slotIndex].Deregistration == null)
2030
1983
  {
2031
- continue;
1984
+ RemoveRegistrationState(slotIndex, _slots[slotIndex].Id);
2032
1985
  }
2033
1986
 
2034
- RemoveRegistrationState(handle);
1987
+ slotIndex = previous;
2035
1988
  }
2036
1989
 
2037
1990
  _emissionBufferBacking?.Clear();
2038
- if (_registrations.Count == 0)
1991
+ if (_registrationCount == 0)
2039
1992
  {
2040
1993
  ClearDiagnosticState();
2041
1994
  }
@@ -2043,16 +1996,51 @@ namespace DxMessaging.Core
2043
1996
 
2044
1997
  private bool RemoveRegistrationState(MessageRegistrationHandle handle)
2045
1998
  {
2046
- bool removedRegistration = _registrations.Remove(handle);
2047
- _ = _registrationOrder.Remove(handle);
2048
- _ = _metadata.Remove(handle);
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
+ );
2049
2031
  _callCountsBacking?.Remove(handle);
2050
- if (removedRegistration && _registrations.Count == 0)
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)
2051
2039
  {
2052
2040
  ClearDiagnosticState();
2053
2041
  }
2054
2042
 
2055
- return removedRegistration;
2043
+ return true;
2056
2044
  }
2057
2045
 
2058
2046
  /// <summary>
@@ -2067,7 +2055,14 @@ namespace DxMessaging.Core
2067
2055
  /// </example>
2068
2056
  public void RemoveRegistration(MessageRegistrationHandle handle)
2069
2057
  {
2070
- if (_deregistrations.TryGetValue(handle, out object value))
2058
+ if (!TryGetSlot(handle.Slot, handle.Id, out RegistrationSlot slot))
2059
+ {
2060
+ RemoveOrphanDeregistration(handle);
2061
+ return;
2062
+ }
2063
+
2064
+ object value = slot.Deregistration;
2065
+ if (value != null)
2071
2066
  {
2072
2067
  Exception deregistrationException = InvokeDeregistration(
2073
2068
  value,
@@ -2076,7 +2071,11 @@ namespace DxMessaging.Core
2076
2071
  );
2077
2072
  if (shouldRemove)
2078
2073
  {
2079
- _deregistrations.Remove(handle);
2074
+ if (TryGetSlot(handle.Slot, handle.Id, out RegistrationSlot current))
2075
+ {
2076
+ _slots[handle.Slot].Deregistration = null;
2077
+ --_deregistrationCount;
2078
+ }
2080
2079
  }
2081
2080
 
2082
2081
  if (deregistrationException != null)
@@ -2091,23 +2090,262 @@ namespace DxMessaging.Core
2091
2090
  RemoveRegistrationState(handle);
2092
2091
  }
2093
2092
 
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
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
2111
2349
  // was consumed -- preserving the exact ordering, partial-failure, and
2112
2350
  // rollback-baseline (startIndex) semantics the List<Action> form had.
2113
2351
  private sealed class PendingDeregistration
@@ -2259,10 +2497,11 @@ namespace DxMessaging.Core
2259
2497
  // user-accepted "unified object, accept the kind-switch" design -- NOT ~14
2260
2498
  // subclasses). GlobalAcceptAllRegistration is non-generic (its sub-handlers
2261
2499
  // are the fixed IMessage facades).
2262
- private abstract class Registration
2500
+ private abstract class Registration : MessageHandler.HandlerDeregistration
2263
2501
  {
2264
2502
  protected readonly MessageRegistrationToken Token;
2265
2503
  public MessageRegistrationHandle Handle;
2504
+ private IMessageBus _registeredMessageBus;
2266
2505
 
2267
2506
  protected Registration(MessageRegistrationToken token)
2268
2507
  {
@@ -2276,9 +2515,109 @@ namespace DxMessaging.Core
2276
2515
  // the staging closure at call time -- the staging closure also read the
2277
2516
  // field, not a snapshot).
2278
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
+ }
2279
2618
  }
2280
2619
 
2281
- private sealed class TargetedRegistration<T> : Registration
2620
+ private sealed class TargetedRegistration<T> : Registration<T>
2282
2621
  where T : ITargetedMessage
2283
2622
  {
2284
2623
  private readonly RegistrationKind _kind;
@@ -2310,85 +2649,108 @@ namespace DxMessaging.Core
2310
2649
  _priority = priority;
2311
2650
  }
2312
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
+
2313
2665
  public override MessageHandler.HandlerDeregistration Register()
2314
2666
  {
2315
2667
  MessageHandler messageHandler = Token._messageHandler;
2316
- IMessageBus messageBus = Token._messageBus;
2668
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
2317
2669
  switch (_kind)
2318
2670
  {
2319
2671
  case RegistrationKind.TargetedHandlerAction:
2320
2672
  _scalarAction = (Action<T>)_userHandler;
2321
- return messageHandler.RegisterTargetedMessageHandler(
2322
- _context,
2323
- _scalarAction,
2324
- AugmentedScalarAction,
2325
- priority: _priority,
2326
- messageBus: messageBus
2673
+ return StoreDeregistration(
2674
+ messageHandler.RegisterTargetedMessageHandler(
2675
+ _context,
2676
+ _scalarAction,
2677
+ AugmentedScalarAction,
2678
+ priority: _priority,
2679
+ messageBus: messageBus
2680
+ )
2327
2681
  );
2328
2682
  case RegistrationKind.TargetedHandlerFast:
2329
2683
  _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2330
- return messageHandler.RegisterTargetedMessageHandler(
2331
- _context,
2332
- _scalarFast,
2333
- AugmentedScalarFast,
2334
- priority: _priority,
2335
- messageBus: messageBus
2684
+ return StoreDeregistration(
2685
+ messageHandler.RegisterTargetedMessageHandler(
2686
+ _context,
2687
+ _scalarFast,
2688
+ AugmentedScalarFast,
2689
+ priority: _priority,
2690
+ messageBus: messageBus
2691
+ )
2336
2692
  );
2337
2693
  case RegistrationKind.TargetedPostProcessorAction:
2338
2694
  _scalarAction = (Action<T>)_userHandler;
2339
- return messageHandler.RegisterTargetedPostProcessor(
2340
- _context,
2341
- _scalarAction,
2342
- AugmentedScalarAction,
2343
- _priority,
2344
- messageBus
2695
+ return StoreDeregistration(
2696
+ messageHandler.RegisterTargetedPostProcessor(
2697
+ _context,
2698
+ _scalarAction,
2699
+ AugmentedScalarAction,
2700
+ _priority,
2701
+ messageBus
2702
+ )
2345
2703
  );
2346
2704
  case RegistrationKind.TargetedPostProcessorFast:
2347
2705
  _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2348
- return messageHandler.RegisterTargetedPostProcessor(
2349
- _context,
2350
- _scalarFast,
2351
- AugmentedScalarFast,
2352
- _priority,
2353
- messageBus
2706
+ return StoreDeregistration(
2707
+ messageHandler.RegisterTargetedPostProcessor(
2708
+ _context,
2709
+ _scalarFast,
2710
+ AugmentedScalarFast,
2711
+ _priority,
2712
+ messageBus
2713
+ )
2354
2714
  );
2355
2715
  case RegistrationKind.TargetedWithoutTargetingAction:
2356
2716
  _contextAction = (Action<InstanceId, T>)_userHandler;
2357
- return messageHandler.RegisterTargetedWithoutTargeting(
2358
- _contextAction,
2359
- AugmentedContextAction,
2360
- priority: _priority,
2361
- messageBus: messageBus
2717
+ return StoreDeregistration(
2718
+ messageHandler.RegisterTargetedWithoutTargeting(
2719
+ _contextAction,
2720
+ AugmentedContextAction,
2721
+ priority: _priority,
2722
+ messageBus: messageBus
2723
+ )
2362
2724
  );
2363
2725
  case RegistrationKind.TargetedWithoutTargetingFast:
2364
2726
  _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2365
- return messageHandler.RegisterTargetedWithoutTargeting(
2366
- _contextFast,
2367
- AugmentedContextFast,
2368
- priority: _priority,
2369
- messageBus: messageBus
2727
+ return StoreDeregistration(
2728
+ messageHandler.RegisterTargetedWithoutTargeting(
2729
+ _contextFast,
2730
+ AugmentedContextFast,
2731
+ priority: _priority,
2732
+ messageBus: messageBus
2733
+ )
2370
2734
  );
2371
2735
  case RegistrationKind.TargetedWithoutTargetingPostProcessorAction:
2372
2736
  _contextAction = (Action<InstanceId, T>)_userHandler;
2373
- return messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2374
- _contextAction,
2375
- AugmentedContextAction,
2376
- _priority,
2377
- messageBus
2737
+ return StoreDeregistration(
2738
+ messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2739
+ _contextAction,
2740
+ AugmentedContextAction,
2741
+ _priority,
2742
+ messageBus
2743
+ )
2378
2744
  );
2379
2745
  case RegistrationKind.TargetedWithoutTargetingPostProcessorFast:
2380
2746
  _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
2747
+ return StoreDeregistration(
2748
+ messageHandler.RegisterTargetedWithoutTargetingPostProcessor(
2749
+ _contextFast,
2750
+ AugmentedContextFast,
2751
+ _priority,
2752
+ messageBus
2753
+ )
2392
2754
  );
2393
2755
  default:
2394
2756
  throw new InvalidOperationException(
@@ -2406,8 +2768,7 @@ namespace DxMessaging.Core
2406
2768
  _scalarAction(message);
2407
2769
  if (Token._diagnosticMode)
2408
2770
  {
2409
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2410
- Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2771
+ RecordDiagnosticEmission(message, _context);
2411
2772
  }
2412
2773
  }
2413
2774
 
@@ -2416,8 +2777,7 @@ namespace DxMessaging.Core
2416
2777
  _scalarFast(ref message);
2417
2778
  if (Token._diagnosticMode)
2418
2779
  {
2419
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2420
- Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
2780
+ RecordDiagnosticEmission(message, _context);
2421
2781
  }
2422
2782
  }
2423
2783
 
@@ -2429,8 +2789,7 @@ namespace DxMessaging.Core
2429
2789
  _contextAction(target, message);
2430
2790
  if (Token._diagnosticMode)
2431
2791
  {
2432
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2433
- Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2792
+ RecordDiagnosticEmission(message, target);
2434
2793
  }
2435
2794
  }
2436
2795
 
@@ -2439,13 +2798,12 @@ namespace DxMessaging.Core
2439
2798
  _contextFast(ref target, ref message);
2440
2799
  if (Token._diagnosticMode)
2441
2800
  {
2442
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2443
- Token._emissionBuffer.Add(new MessageEmissionData(message, target));
2801
+ RecordDiagnosticEmission(message, target);
2444
2802
  }
2445
2803
  }
2446
2804
  }
2447
2805
 
2448
- private sealed class UntargetedRegistration<T> : Registration
2806
+ private sealed class UntargetedRegistration<T> : Registration<T>
2449
2807
  where T : IUntargetedMessage
2450
2808
  {
2451
2809
  private readonly RegistrationKind _kind;
@@ -2471,41 +2829,49 @@ namespace DxMessaging.Core
2471
2829
  _priority = priority;
2472
2830
  }
2473
2831
 
2832
+ public override MessageRegistrationMetadata Metadata =>
2833
+ new MessageRegistrationMetadata(
2834
+ null,
2835
+ typeof(T),
2836
+ GetRegistrationType(_kind),
2837
+ _priority
2838
+ );
2839
+
2474
2840
  public override MessageHandler.HandlerDeregistration Register()
2475
2841
  {
2476
2842
  MessageHandler messageHandler = Token._messageHandler;
2477
- IMessageBus messageBus = Token._messageBus;
2843
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
2478
2844
  switch (_kind)
2479
2845
  {
2480
2846
  case RegistrationKind.UntargetedHandlerAction:
2481
2847
  _scalarAction = (Action<T>)_userHandler;
2482
- return messageHandler.RegisterUntargetedMessageHandler(
2483
- _scalarAction,
2484
- AugmentedScalarAction,
2485
- priority: _priority,
2486
- messageBus: messageBus
2848
+ return StoreDeregistration(
2849
+ messageHandler.RegisterUntargetedMessageHandler(
2850
+ _scalarAction,
2851
+ AugmentedScalarAction,
2852
+ priority: _priority,
2853
+ messageBus: messageBus
2854
+ )
2487
2855
  );
2488
2856
  case RegistrationKind.UntargetedHandlerFast:
2489
2857
  _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2490
- return messageHandler.RegisterUntargetedMessageHandler(
2491
- _scalarFast,
2492
- AugmentedScalarFast,
2493
- priority: _priority,
2494
- messageBus: messageBus
2858
+ return StoreDeregistration(
2859
+ messageHandler.RegisterUntargetedMessageHandler(
2860
+ _scalarFast,
2861
+ AugmentedScalarFast,
2862
+ priority: _priority,
2863
+ messageBus: messageBus
2864
+ )
2495
2865
  );
2496
2866
  case RegistrationKind.UntargetedPostProcessorFast:
2497
2867
  _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
2868
+ return StoreDeregistration(
2869
+ messageHandler.RegisterUntargetedPostProcessor(
2870
+ _scalarFast,
2871
+ AugmentedScalarFast,
2872
+ _priority,
2873
+ messageBus
2874
+ )
2509
2875
  );
2510
2876
  default:
2511
2877
  throw new InvalidOperationException(
@@ -2521,8 +2887,7 @@ namespace DxMessaging.Core
2521
2887
  _scalarAction(message);
2522
2888
  if (Token._diagnosticMode)
2523
2889
  {
2524
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2525
- Token._emissionBuffer.Add(new MessageEmissionData(message));
2890
+ RecordDiagnosticEmission(message);
2526
2891
  }
2527
2892
  }
2528
2893
 
@@ -2531,13 +2896,12 @@ namespace DxMessaging.Core
2531
2896
  _scalarFast(ref message);
2532
2897
  if (Token._diagnosticMode)
2533
2898
  {
2534
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2535
- Token._emissionBuffer.Add(new MessageEmissionData(message));
2899
+ RecordDiagnosticEmission(message);
2536
2900
  }
2537
2901
  }
2538
2902
  }
2539
2903
 
2540
- private sealed class BroadcastRegistration<T> : Registration
2904
+ private sealed class BroadcastRegistration<T> : Registration<T>
2541
2905
  where T : IBroadcastMessage
2542
2906
  {
2543
2907
  private readonly RegistrationKind _kind;
@@ -2568,85 +2932,108 @@ namespace DxMessaging.Core
2568
2932
  _priority = priority;
2569
2933
  }
2570
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
+
2571
2948
  public override MessageHandler.HandlerDeregistration Register()
2572
2949
  {
2573
2950
  MessageHandler messageHandler = Token._messageHandler;
2574
- IMessageBus messageBus = Token._messageBus;
2951
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
2575
2952
  switch (_kind)
2576
2953
  {
2577
2954
  case RegistrationKind.BroadcastHandlerAction:
2578
2955
  _scalarAction = (Action<T>)_userHandler;
2579
- return messageHandler.RegisterSourcedBroadcastMessageHandler(
2580
- _context,
2581
- _scalarAction,
2582
- AugmentedScalarAction,
2583
- priority: _priority,
2584
- messageBus: messageBus
2956
+ return StoreDeregistration(
2957
+ messageHandler.RegisterSourcedBroadcastMessageHandler(
2958
+ _context,
2959
+ _scalarAction,
2960
+ AugmentedScalarAction,
2961
+ priority: _priority,
2962
+ messageBus: messageBus
2963
+ )
2585
2964
  );
2586
2965
  case RegistrationKind.BroadcastHandlerFast:
2587
2966
  _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2588
- return messageHandler.RegisterSourcedBroadcastMessageHandler(
2589
- _context,
2590
- _scalarFast,
2591
- AugmentedScalarFast,
2592
- priority: _priority,
2593
- messageBus: messageBus
2967
+ return StoreDeregistration(
2968
+ messageHandler.RegisterSourcedBroadcastMessageHandler(
2969
+ _context,
2970
+ _scalarFast,
2971
+ AugmentedScalarFast,
2972
+ priority: _priority,
2973
+ messageBus: messageBus
2974
+ )
2594
2975
  );
2595
2976
  case RegistrationKind.BroadcastPostProcessorAction:
2596
2977
  _scalarAction = (Action<T>)_userHandler;
2597
- return messageHandler.RegisterSourcedBroadcastPostProcessor(
2598
- _context,
2599
- _scalarAction,
2600
- AugmentedScalarAction,
2601
- _priority,
2602
- messageBus
2978
+ return StoreDeregistration(
2979
+ messageHandler.RegisterSourcedBroadcastPostProcessor(
2980
+ _context,
2981
+ _scalarAction,
2982
+ AugmentedScalarAction,
2983
+ _priority,
2984
+ messageBus
2985
+ )
2603
2986
  );
2604
2987
  case RegistrationKind.BroadcastPostProcessorFast:
2605
2988
  _scalarFast = (MessageHandler.FastHandler<T>)_userHandler;
2606
- return messageHandler.RegisterSourcedBroadcastPostProcessor(
2607
- _context,
2608
- _scalarFast,
2609
- AugmentedScalarFast,
2610
- _priority,
2611
- messageBus
2989
+ return StoreDeregistration(
2990
+ messageHandler.RegisterSourcedBroadcastPostProcessor(
2991
+ _context,
2992
+ _scalarFast,
2993
+ AugmentedScalarFast,
2994
+ _priority,
2995
+ messageBus
2996
+ )
2612
2997
  );
2613
2998
  case RegistrationKind.BroadcastWithoutSourceAction:
2614
2999
  _contextAction = (Action<InstanceId, T>)_userHandler;
2615
- return messageHandler.RegisterSourcedBroadcastWithoutSource(
2616
- _contextAction,
2617
- AugmentedContextAction,
2618
- priority: _priority,
2619
- messageBus: messageBus
3000
+ return StoreDeregistration(
3001
+ messageHandler.RegisterSourcedBroadcastWithoutSource(
3002
+ _contextAction,
3003
+ AugmentedContextAction,
3004
+ priority: _priority,
3005
+ messageBus: messageBus
3006
+ )
2620
3007
  );
2621
3008
  case RegistrationKind.BroadcastWithoutSourceFast:
2622
3009
  _contextFast = (MessageHandler.FastHandlerWithContext<T>)_userHandler;
2623
- return messageHandler.RegisterSourcedBroadcastWithoutSource(
2624
- _contextFast,
2625
- AugmentedContextFast,
2626
- priority: _priority,
2627
- messageBus: messageBus
3010
+ return StoreDeregistration(
3011
+ messageHandler.RegisterSourcedBroadcastWithoutSource(
3012
+ _contextFast,
3013
+ AugmentedContextFast,
3014
+ priority: _priority,
3015
+ messageBus: messageBus
3016
+ )
2628
3017
  );
2629
3018
  case RegistrationKind.BroadcastWithoutSourcePostProcessorAction:
2630
3019
  _contextAction = (Action<InstanceId, T>)_userHandler;
2631
- return messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
2632
- _contextAction,
2633
- AugmentedContextAction,
2634
- priority: _priority,
2635
- messageBus
3020
+ return StoreDeregistration(
3021
+ messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
3022
+ _contextAction,
3023
+ AugmentedContextAction,
3024
+ priority: _priority,
3025
+ messageBus
3026
+ )
2636
3027
  );
2637
3028
  case RegistrationKind.BroadcastWithoutSourcePostProcessorFast:
2638
3029
  _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
3030
+ return StoreDeregistration(
3031
+ messageHandler.RegisterSourcedBroadcastWithoutSourcePostProcessor(
3032
+ _contextFast,
3033
+ AugmentedContextFast,
3034
+ priority: _priority,
3035
+ messageBus
3036
+ )
2650
3037
  );
2651
3038
  default:
2652
3039
  throw new InvalidOperationException(
@@ -2662,8 +3049,7 @@ namespace DxMessaging.Core
2662
3049
  _scalarAction(message);
2663
3050
  if (Token._diagnosticMode)
2664
3051
  {
2665
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2666
- Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
3052
+ RecordDiagnosticEmission(message, _context);
2667
3053
  }
2668
3054
  }
2669
3055
 
@@ -2672,8 +3058,7 @@ namespace DxMessaging.Core
2672
3058
  _scalarFast(ref message);
2673
3059
  if (Token._diagnosticMode)
2674
3060
  {
2675
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2676
- Token._emissionBuffer.Add(new MessageEmissionData(message, _context));
3061
+ RecordDiagnosticEmission(message, _context);
2677
3062
  }
2678
3063
  }
2679
3064
 
@@ -2685,8 +3070,7 @@ namespace DxMessaging.Core
2685
3070
  _contextAction(source, message);
2686
3071
  if (Token._diagnosticMode)
2687
3072
  {
2688
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2689
- Token._emissionBuffer.Add(new MessageEmissionData(message, source));
3073
+ RecordDiagnosticEmission(message, source);
2690
3074
  }
2691
3075
  }
2692
3076
 
@@ -2695,9 +3079,152 @@ namespace DxMessaging.Core
2695
3079
  _contextFast(ref source, ref message);
2696
3080
  if (Token._diagnosticMode)
2697
3081
  {
2698
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2699
- Token._emissionBuffer.Add(new MessageEmissionData(message, source));
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;
2700
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
+ );
2701
3228
  }
2702
3229
  }
2703
3230
 
@@ -2720,6 +3247,16 @@ namespace DxMessaging.Core
2720
3247
  private readonly MessageHandler.FastHandler<IUntargetedMessage> _untargetedFast;
2721
3248
  private readonly MessageHandler.FastHandlerWithContext<ITargetedMessage> _targetedFast;
2722
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
+ );
2723
3260
 
2724
3261
  internal GlobalAcceptAllRegistration(
2725
3262
  MessageRegistrationToken token,
@@ -2752,38 +3289,66 @@ namespace DxMessaging.Core
2752
3289
  public override MessageHandler.HandlerDeregistration Register()
2753
3290
  {
2754
3291
  MessageHandler messageHandler = Token._messageHandler;
2755
- IMessageBus messageBus = Token._messageBus;
3292
+ IMessageBus messageBus = ResolveRegistrationMessageBus();
2756
3293
  if (_kind == RegistrationKind.GlobalAcceptAllAction)
2757
3294
  {
2758
- return messageHandler.RegisterGlobalAcceptAll(
2759
- _untargetedAction,
2760
- AugmentedUntargetedAction,
2761
- _targetedAction,
2762
- AugmentedTargetedAction,
2763
- _broadcastAction,
2764
- AugmentedBroadcastAction,
2765
- messageBus
3295
+ return StoreDeregistration(
3296
+ messageHandler.RegisterGlobalAcceptAll(
3297
+ _untargetedAction,
3298
+ AugmentedUntargetedAction,
3299
+ _targetedAction,
3300
+ AugmentedTargetedAction,
3301
+ _broadcastAction,
3302
+ AugmentedBroadcastAction,
3303
+ messageBus
3304
+ )
2766
3305
  );
2767
3306
  }
2768
3307
 
2769
- return messageHandler.RegisterGlobalAcceptAll(
2770
- _untargetedFast,
2771
- AugmentedUntargetedFast,
2772
- _targetedFast,
2773
- AugmentedTargetedFast,
2774
- _broadcastFast,
2775
- AugmentedBroadcastFast,
2776
- messageBus
3308
+ return StoreDeregistration(
3309
+ messageHandler.RegisterGlobalAcceptAll(
3310
+ _untargetedFast,
3311
+ AugmentedUntargetedFast,
3312
+ _targetedFast,
3313
+ AugmentedTargetedFast,
3314
+ _broadcastFast,
3315
+ AugmentedBroadcastFast,
3316
+ messageBus
3317
+ )
2777
3318
  );
2778
3319
  }
2779
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
+
2780
3346
  private void AugmentedUntargetedAction(IUntargetedMessage message)
2781
3347
  {
2782
3348
  _untargetedAction(message);
2783
3349
  if (Token._diagnosticMode)
2784
3350
  {
2785
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2786
- Token._emissionBuffer.Add(new MessageEmissionData(message));
3351
+ RecordDiagnosticEmission(message);
2787
3352
  }
2788
3353
  }
2789
3354
 
@@ -2792,8 +3357,7 @@ namespace DxMessaging.Core
2792
3357
  _targetedAction(target, message);
2793
3358
  if (Token._diagnosticMode)
2794
3359
  {
2795
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2796
- Token._emissionBuffer.Add(new MessageEmissionData(message, target));
3360
+ RecordDiagnosticEmission(message, target);
2797
3361
  }
2798
3362
  }
2799
3363
 
@@ -2802,8 +3366,7 @@ namespace DxMessaging.Core
2802
3366
  _broadcastAction(source, message);
2803
3367
  if (Token._diagnosticMode)
2804
3368
  {
2805
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2806
- Token._emissionBuffer.Add(new MessageEmissionData(message, source));
3369
+ RecordDiagnosticEmission(message, source);
2807
3370
  }
2808
3371
  }
2809
3372
 
@@ -2812,8 +3375,7 @@ namespace DxMessaging.Core
2812
3375
  _untargetedFast(ref message);
2813
3376
  if (Token._diagnosticMode)
2814
3377
  {
2815
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2816
- Token._emissionBuffer.Add(new MessageEmissionData(message));
3378
+ RecordDiagnosticEmission(message);
2817
3379
  }
2818
3380
  }
2819
3381
 
@@ -2822,8 +3384,7 @@ namespace DxMessaging.Core
2822
3384
  _targetedFast(ref target, ref message);
2823
3385
  if (Token._diagnosticMode)
2824
3386
  {
2825
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2826
- Token._emissionBuffer.Add(new MessageEmissionData(message, target));
3387
+ RecordDiagnosticEmission(message, target);
2827
3388
  }
2828
3389
  }
2829
3390
 
@@ -2835,8 +3396,7 @@ namespace DxMessaging.Core
2835
3396
  _broadcastFast(ref source, ref message);
2836
3397
  if (Token._diagnosticMode)
2837
3398
  {
2838
- Token._callCounts[Handle] = Token._callCounts.GetValueOrDefault(Handle) + 1;
2839
- Token._emissionBuffer.Add(new MessageEmissionData(message, source));
3399
+ RecordDiagnosticEmission(message, source);
2840
3400
  }
2841
3401
  }
2842
3402
  }