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
@@ -294,333 +294,12 @@ namespace DxMessaging.Core
294
294
  return messageBus ?? _defaultMessageBus ?? MessageBus;
295
295
  }
296
296
 
297
- /// <summary>
298
- /// Callback from the MessageBus for handling UntargetedMessages - user code should generally never use this.
299
- /// </summary>
300
- /// <note>
301
- /// In this case, "UntargetedMessage" refers to Targeted without targeting, and UntargetedMessages, hence T : IMessage.
302
- /// </note>
303
- /// <param name="message">Message to handle.</param>
304
- /// <param name="messageBus">The specific MessageBus to use.</param>
305
- /// <param name="priority">Priority at which to run the handlers.</param>
306
- public void HandleUntargetedMessage<TMessage>(
307
- ref TMessage message,
308
- IMessageBus messageBus,
309
- int priority
310
- )
311
- where TMessage : IMessage
312
- {
313
- if (!active)
314
- {
315
- return;
316
- }
317
-
318
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
319
- {
320
- long emissionId = messageBus.EmissionId;
321
- handler.HandleUntargeted(ref message, priority, emissionId);
322
- }
323
- }
324
-
325
- /// <summary>
326
- /// Callback from the MessageBus for handling UntargetedMessages - user code should generally never use this.
327
- /// </summary>
328
- /// <note>
329
- /// In this case, "UntargetedMessage" refers to Targeted without targeting, and UntargetedMessages, hence T : IUntargetedMessage.
330
- /// </note>
331
- /// <param name="message">Message to handle.</param>
332
- /// <param name="messageBus">The specific MessageBus to use.</param>
333
- /// <param name="priority">Priority at which to run the handlers.</param>
334
- public void HandleUntargetedPostProcessing<TMessage>(
335
- ref TMessage message,
336
- IMessageBus messageBus,
337
- int priority
338
- )
339
- where TMessage : IUntargetedMessage
340
- {
341
- if (!active)
342
- {
343
- return;
344
- }
345
-
346
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
347
- {
348
- long emissionId = messageBus.EmissionId;
349
- handler.HandleUntargetedPostProcessing(ref message, priority, emissionId);
350
- }
351
- }
352
-
353
- /// <summary>
354
- /// Callback from the MessageBus for handling TargetedMessages when this MessageHandler has subscribed - user code should generally never use this.
355
- /// </summary>
356
- /// <note>
357
- /// TargetedMessage refers to those that are intended for the GameObject that owns this MessageHandler.
358
- /// </note>
359
- /// <param name="target">Target Id the message is for.</param>
360
- /// <param name="message">Message to handle.</param>
361
- /// <param name="messageBus">The specific MessageBus to use.</param>
362
- /// <param name="priority">Priority at which to run the handlers.</param>
363
- public void HandleTargeted<TMessage>(
364
- ref InstanceId target,
365
- ref TMessage message,
366
- IMessageBus messageBus,
367
- int priority
368
- )
369
- where TMessage : ITargetedMessage
370
- {
371
- if (!active)
372
- {
373
- return;
374
- }
375
-
376
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
377
- {
378
- long emissionId = messageBus.EmissionId;
379
- handler.HandleTargeted(ref target, ref message, priority, emissionId);
380
- }
381
- }
382
-
383
- /// <summary>
384
- /// Callback from the MessageBus for handling TargetedMessages without targeting when this MessageHandler has subscribed - user code should generally never use this.
385
- /// </summary>
386
- /// <note>
387
- /// Any TargetedMessage.
388
- /// </note>
389
- /// <param name="target">Target Id the message is for.</param>
390
- /// <param name="message">Message to handle.</param>
391
- /// <param name="messageBus">The specific MessageBus to use.</param>
392
- /// <param name="priority">Priority at which to run the handlers.</param>
393
- public void HandleTargetedWithoutTargeting<TMessage>(
394
- ref InstanceId target,
395
- ref TMessage message,
396
- IMessageBus messageBus,
397
- int priority
398
- )
399
- where TMessage : ITargetedMessage
400
- {
401
- if (!active)
402
- {
403
- return;
404
- }
405
-
406
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
407
- {
408
- long emissionId = messageBus.EmissionId;
409
- handler.HandleTargetedWithoutTargeting(
410
- ref target,
411
- ref message,
412
- priority,
413
- emissionId
414
- );
415
- }
416
- }
417
-
418
- /// <summary>
419
- /// Callback from the MessageBus for post-processing TargetedMessages when this MessageHandler has subscribed - user code should generally never use this.
420
- /// </summary>
421
- /// <note>
422
- /// TargetedMessage refers to those that are intended for the GameObject that owns this MessageHandler.
423
- /// </note>
424
- /// <param name="target">Target Id the message is for.</param>
425
- /// <param name="message">Message to handle.</param>
426
- /// <param name="messageBus">The specific MessageBus to use.</param>
427
- /// <param name="priority">Priority at which to run the handlers.</param>
428
- public void HandleTargetedPostProcessing<TMessage>(
429
- ref InstanceId target,
430
- ref TMessage message,
431
- IMessageBus messageBus,
432
- int priority
433
- )
434
- where TMessage : ITargetedMessage
435
- {
436
- if (!active)
437
- {
438
- return;
439
- }
440
-
441
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
442
- {
443
- long emissionId = messageBus.EmissionId;
444
- handler.HandleTargetedPostProcessing(ref target, ref message, priority, emissionId);
445
- }
446
- }
447
-
448
- /// <summary>
449
- /// Callback from the MessageBus for post-processing TargetedMessages when this MessageHandler has subscribed - user code should generally never use this.
450
- /// </summary>
451
- /// <note>
452
- /// TargetedMessage refers to those that are intended for the GameObject that owns this MessageHandler.
453
- /// </note>
454
- /// <param name="target">Target Id the message is for.</param>
455
- /// <param name="message">Message to handle.</param>
456
- /// <param name="messageBus">The specific MessageBus to use.</param>
457
- /// <param name="priority">Priority at which to run the handlers.</param>
458
- public void HandleTargetedWithoutTargetingPostProcessing<TMessage>(
459
- ref InstanceId target,
460
- ref TMessage message,
461
- IMessageBus messageBus,
462
- int priority
463
- )
464
- where TMessage : ITargetedMessage
465
- {
466
- if (!active)
467
- {
468
- return;
469
- }
470
-
471
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
472
- {
473
- long emissionId = messageBus.EmissionId;
474
- handler.HandleTargetedWithoutTargetingPostProcessing(
475
- ref target,
476
- ref message,
477
- priority,
478
- emissionId
479
- );
480
- }
481
- }
482
-
483
- /// <summary>
484
- /// Callback from the MessageBus for handling SourcedBroadcastMessages - user code should generally never use this.
485
- /// </summary>
486
- /// <note>
487
- /// SourcedBroadcastMessages generally refer to those that are sourced from the GameObject that owns this MessageHandler.
488
- /// </note>
489
- /// <param name="source">Source Id the broadcast message is from.</param>
490
- /// <param name="message">Message to handle</param>
491
- /// <param name="messageBus">The specific MessageBus to use.</param>
492
- /// <param name="priority">Priority at which to run the handlers.</param>
493
- public void HandleSourcedBroadcast<TMessage>(
494
- ref InstanceId source,
495
- ref TMessage message,
496
- IMessageBus messageBus,
497
- int priority
498
- )
499
- where TMessage : IBroadcastMessage
500
- {
501
- if (!active)
502
- {
503
- return;
504
- }
505
-
506
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
507
- {
508
- long emissionId = messageBus.EmissionId;
509
- handler.HandleSourcedBroadcast(ref source, ref message, priority, emissionId);
510
- }
511
- }
512
-
513
- /// <summary>
514
- /// Callback from the MessageBus for handling SourcedBroadcastMessages without source - user code should generally never use this.
515
- /// </summary>
516
- /// <note>
517
- /// Any SourcedBroadcastMessages.
518
- /// </note>
519
- /// <param name="source">Source Id the broadcast message is from.</param>
520
- /// <param name="message">Message to handle</param>
521
- /// <param name="messageBus">The specific MessageBus to use.</param>
522
- /// <param name="priority">Priority at which to run the handlers.</param>
523
- public void HandleSourcedBroadcastWithoutSource<TMessage>(
524
- ref InstanceId source,
525
- ref TMessage message,
526
- IMessageBus messageBus,
527
- int priority
528
- )
529
- where TMessage : IBroadcastMessage
530
- {
531
- if (!active)
532
- {
533
- return;
534
- }
535
-
536
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
537
- {
538
- long emissionId = messageBus.EmissionId;
539
- handler.HandleSourcedBroadcastWithoutSource(
540
- ref source,
541
- ref message,
542
- priority,
543
- emissionId
544
- );
545
- }
546
- }
547
-
548
- /// <summary>
549
- /// Callback from the MessageBus for handling SourcedBroadcastPostProcessing - user code should generally never use this.
550
- /// </summary>
551
- /// <note>
552
- /// SourcedBroadcastMessages generally refer to those that are sourced from the GameObject that owns this MessageHandler.
553
- /// </note>
554
- /// <param name="source">Source Id the broadcast message is from.</param>
555
- /// <param name="message">Message to handle</param>
556
- /// <param name="messageBus">The specific MessageBus to use.</param>
557
- /// <param name="priority">Priority at which to run the handlers.</param>
558
- public void HandleSourcedBroadcastPostProcessing<TMessage>(
559
- ref InstanceId source,
560
- ref TMessage message,
561
- IMessageBus messageBus,
562
- int priority
563
- )
564
- where TMessage : IBroadcastMessage
565
- {
566
- if (!active)
567
- {
568
- return;
569
- }
570
-
571
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
572
- {
573
- long emissionId = messageBus.EmissionId;
574
- handler.HandleSourcedBroadcastPostProcessing(
575
- ref source,
576
- ref message,
577
- priority,
578
- emissionId
579
- );
580
- }
581
- }
582
-
583
- /// <summary>
584
- /// Callback from the MessageBus for handling SourcedBroadcastPostProcessing - user code should generally never use this.
585
- /// </summary>
586
- /// <note>
587
- /// SourcedBroadcastMessages generally refer to those that are sourced from the GameObject that owns this MessageHandler.
588
- /// </note>
589
- /// <param name="source">Source Id the broadcast message is from.</param>
590
- /// <param name="message">Message to handle</param>
591
- /// <param name="messageBus">The specific MessageBus to use.</param>
592
- /// <param name="priority">Priority at which to run the handlers.</param>
593
- public void HandleSourcedBroadcastWithoutSourcePostProcessing<TMessage>(
594
- ref InstanceId source,
595
- ref TMessage message,
596
- IMessageBus messageBus,
597
- int priority
598
- )
599
- where TMessage : IBroadcastMessage
600
- {
601
- if (!active)
602
- {
603
- return;
604
- }
605
-
606
- if (GetHandlerForType(messageBus, out TypedHandler<TMessage> handler))
607
- {
608
- long emissionId = messageBus.EmissionId;
609
- handler.HandleBroadcastWithoutSourcePostProcessing(
610
- ref source,
611
- ref message,
612
- priority,
613
- emissionId
614
- );
615
- }
616
- }
617
-
618
297
  /// <summary>
619
298
  /// Callback from the MessageBus for handling Messages when this MessageHandler has subscribed to GlobalAcceptAll - user code should generally never use this.
620
299
  /// </summary>
621
300
  /// <param name="message">Message to handle.</param>
622
301
  /// <param name="messageBus">The specific MessageBus to use.</param>
623
- public void HandleGlobalUntargetedMessage(
302
+ internal void HandleGlobalUntargetedMessage(
624
303
  ref IUntargetedMessage message,
625
304
  IMessageBus messageBus
626
305
  )
@@ -725,7 +404,7 @@ namespace DxMessaging.Core
725
404
  /// <param name="target">Target of the message.</param>
726
405
  /// <param name="message">Message to handle.</param>
727
406
  /// <param name="messageBus">The specific MessageBus to use.</param>
728
- public void HandleGlobalTargetedMessage(
407
+ internal void HandleGlobalTargetedMessage(
729
408
  ref InstanceId target,
730
409
  ref ITargetedMessage message,
731
410
  IMessageBus messageBus
@@ -750,7 +429,7 @@ namespace DxMessaging.Core
750
429
  /// <param name="source">Source that this message is from.</param>
751
430
  /// <param name="message">Message to handle.</param>
752
431
  /// <param name="messageBus">The specific MessageBus to use.</param>
753
- public void HandleGlobalSourcedBroadcastMessage(
432
+ internal void HandleGlobalSourcedBroadcastMessage(
754
433
  ref InstanceId source,
755
434
  ref IBroadcastMessage message,
756
435
  IMessageBus messageBus
@@ -770,14 +449,10 @@ namespace DxMessaging.Core
770
449
  }
771
450
 
772
451
  /// <summary>
773
- /// Per-handle teardown object returned by the
774
- /// <see cref="MessageHandler"/> registration entry points, replacing the
775
- /// per-registration deregistration <see cref="Action"/> closure (the
776
- /// closure's display class + delegate are folded into this object's
777
- /// fields, saving one managed allocation per registration). The
778
- /// concrete subclasses re-express the exact body of the closure they
779
- /// replace; <see cref="Deregister"/> is idempotent-friendly under the
780
- /// same generation / slot-version guards.
452
+ /// Reference-identity teardown base used by token lifecycle queues. The common
453
+ /// token path is implemented by the token's existing Registration object with
454
+ /// value-type state returned from MessageHandler; concrete wrappers below are
455
+ /// reserved for direct handler compatibility and overlapping retry spills.
781
456
  /// </summary>
782
457
  /// <remarks>
783
458
  /// The implicit conversion to <see cref="Action"/> exists so the
@@ -785,8 +460,7 @@ namespace DxMessaging.Core
785
460
  /// bus&lt;-&gt;handler boundary) that stored the old <see cref="Action"/>
786
461
  /// continue to compile unchanged; it allocates a single delegate ONLY
787
462
  /// when a caller explicitly requests the <see cref="Action"/> form, and
788
- /// is never used on the token's common storage path (which holds this
789
- /// object directly).
463
+ /// is never used on the token's common storage path.
790
464
  /// </remarks>
791
465
  internal abstract class HandlerDeregistration
792
466
  {
@@ -803,13 +477,13 @@ namespace DxMessaging.Core
803
477
  /// the only thing to undo (there is no typed-handler cache entry).
804
478
  /// Mirrors the old <c>() =&gt; messageBus.Deregister&lt;T&gt;(in registration)</c>.
805
479
  /// </summary>
806
- internal sealed class InterceptorDeregistration<T> : HandlerDeregistration
480
+ internal readonly struct InterceptorDeregistrationState<T>
807
481
  where T : IMessage
808
482
  {
809
483
  private readonly IMessageBus _messageBus;
810
484
  private readonly MessageBusRegistration _registration;
811
485
 
812
- internal InterceptorDeregistration(
486
+ internal InterceptorDeregistrationState(
813
487
  IMessageBus messageBus,
814
488
  in MessageBusRegistration registration
815
489
  )
@@ -818,10 +492,94 @@ namespace DxMessaging.Core
818
492
  _registration = registration;
819
493
  }
820
494
 
821
- internal override void Deregister()
495
+ internal void Deregister()
822
496
  {
823
497
  _messageBus.Deregister<T>(in _registration);
824
498
  }
499
+
500
+ public static implicit operator HandlerDeregistration(
501
+ InterceptorDeregistrationState<T> state
502
+ )
503
+ {
504
+ return new InterceptorDeregistration<T>(state);
505
+ }
506
+
507
+ public static implicit operator Action(InterceptorDeregistrationState<T> state)
508
+ {
509
+ InterceptorDeregistration<T> deregistration = new(state);
510
+ return deregistration.Deregister;
511
+ }
512
+ }
513
+
514
+ internal sealed class InterceptorDeregistration<T> : HandlerDeregistration
515
+ where T : IMessage
516
+ {
517
+ private readonly InterceptorDeregistrationState<T> _state;
518
+
519
+ internal InterceptorDeregistration(InterceptorDeregistrationState<T> state)
520
+ {
521
+ _state = state;
522
+ }
523
+
524
+ internal override void Deregister()
525
+ {
526
+ _state.Deregister();
527
+ }
528
+ }
529
+
530
+ /// <summary>
531
+ /// Value teardown for one typed global handler slot. Captures the old closure's
532
+ /// reset/slot guards, erased cache identity, refcount key, and touch/live-count state.
533
+ /// </summary>
534
+ internal readonly struct GlobalHandlerDeregistrationState
535
+ {
536
+ private readonly IMessageBus _messageBus;
537
+ private readonly long _resetGeneration;
538
+ private readonly TypedGlobalSlot _slot;
539
+ private readonly long _slotVersion;
540
+ private readonly IHandlerActionCache _cache;
541
+ private readonly object _originalHandler;
542
+
543
+ internal GlobalHandlerDeregistrationState(
544
+ IMessageBus messageBus,
545
+ long resetGeneration,
546
+ TypedGlobalSlot slot,
547
+ long slotVersion,
548
+ IHandlerActionCache cache,
549
+ object originalHandler
550
+ )
551
+ {
552
+ _messageBus = messageBus;
553
+ _resetGeneration = resetGeneration;
554
+ _slot = slot;
555
+ _slotVersion = slotVersion;
556
+ _cache = cache;
557
+ _originalHandler = originalHandler;
558
+ }
559
+
560
+ internal void Deregister()
561
+ {
562
+ if (
563
+ !global::DxMessaging.Core.MessageBus.MessageBus.IsResetGenerationCurrent(
564
+ _messageBus,
565
+ _resetGeneration
566
+ )
567
+ || _slot.version != _slotVersion
568
+ || !_cache.ContainsEntry(_originalHandler)
569
+ )
570
+ {
571
+ return;
572
+ }
573
+
574
+ _cache.BumpVersion();
575
+ _slot.lastTouchTicks =
576
+ global::DxMessaging.Core.MessageBus.MessageBus.GetCurrentTouchTick(_messageBus);
577
+ _ = _cache.DeregisterEntry(_originalHandler, out bool wasLastForEntry);
578
+ if (wasLastForEntry)
579
+ {
580
+ _slot.liveCount--;
581
+ }
582
+ }
825
583
  }
826
584
 
827
585
  /// <summary>
@@ -830,20 +588,20 @@ namespace DxMessaging.Core
830
588
  /// deregistrations, preserving the exact order of the old closure
831
589
  /// (<c>messageBus.Deregister&lt;IMessage&gt;(...); untargeted(); targeted(); broadcast();</c>).
832
590
  /// </summary>
833
- internal sealed class GlobalAcceptAllDeregistration : HandlerDeregistration
591
+ internal readonly struct GlobalAcceptAllDeregistrationState
834
592
  {
835
593
  private readonly IMessageBus _messageBus;
836
594
  private readonly MessageBusRegistration _messageBusRegistration;
837
- private readonly Action _untargetedDeregistration;
838
- private readonly Action _targetedDeregistration;
839
- private readonly Action _broadcastDeregistration;
595
+ private readonly GlobalHandlerDeregistrationState _untargetedDeregistration;
596
+ private readonly GlobalHandlerDeregistrationState _targetedDeregistration;
597
+ private readonly GlobalHandlerDeregistrationState _broadcastDeregistration;
840
598
 
841
- internal GlobalAcceptAllDeregistration(
599
+ internal GlobalAcceptAllDeregistrationState(
842
600
  IMessageBus messageBus,
843
601
  in MessageBusRegistration messageBusRegistration,
844
- Action untargetedDeregistration,
845
- Action targetedDeregistration,
846
- Action broadcastDeregistration
602
+ GlobalHandlerDeregistrationState untargetedDeregistration,
603
+ GlobalHandlerDeregistrationState targetedDeregistration,
604
+ GlobalHandlerDeregistrationState broadcastDeregistration
847
605
  )
848
606
  {
849
607
  _messageBus = messageBus;
@@ -853,12 +611,40 @@ namespace DxMessaging.Core
853
611
  _broadcastDeregistration = broadcastDeregistration;
854
612
  }
855
613
 
614
+ internal void Deregister()
615
+ {
616
+ _messageBus.Deregister<IMessage>(in _messageBusRegistration);
617
+ _untargetedDeregistration.Deregister();
618
+ _targetedDeregistration.Deregister();
619
+ _broadcastDeregistration.Deregister();
620
+ }
621
+
622
+ public static implicit operator HandlerDeregistration(
623
+ GlobalAcceptAllDeregistrationState state
624
+ )
625
+ {
626
+ return new GlobalAcceptAllDeregistration(state);
627
+ }
628
+
629
+ public static implicit operator Action(GlobalAcceptAllDeregistrationState state)
630
+ {
631
+ GlobalAcceptAllDeregistration deregistration = new(state);
632
+ return deregistration.Deregister;
633
+ }
634
+ }
635
+
636
+ internal sealed class GlobalAcceptAllDeregistration : HandlerDeregistration
637
+ {
638
+ private readonly GlobalAcceptAllDeregistrationState _state;
639
+
640
+ internal GlobalAcceptAllDeregistration(GlobalAcceptAllDeregistrationState state)
641
+ {
642
+ _state = state;
643
+ }
644
+
856
645
  internal override void Deregister()
857
646
  {
858
- _messageBus.Deregister<IMessage>(in _messageBusRegistration);
859
- _untargetedDeregistration();
860
- _targetedDeregistration();
861
- _broadcastDeregistration();
647
+ _state.Deregister();
862
648
  }
863
649
  }
864
650
 
@@ -870,7 +656,7 @@ namespace DxMessaging.Core
870
656
  /// <param name="targetedMessageHandler">MessageHandler to accept all BroadcastMessages for all entities.</param>
871
657
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
872
658
  /// <returns>The de-registration action.</returns>
873
- internal HandlerDeregistration RegisterGlobalAcceptAll(
659
+ internal GlobalAcceptAllDeregistrationState RegisterGlobalAcceptAll(
874
660
  Action<IUntargetedMessage> originalUntargetedMessageHandler,
875
661
  Action<IUntargetedMessage> untargetedMessageHandler,
876
662
  Action<InstanceId, ITargetedMessage> originalTargetedMessageHandler,
@@ -886,37 +672,32 @@ namespace DxMessaging.Core
886
672
  );
887
673
  TypedHandler<IMessage> typedHandler = GetOrCreateHandlerForType<IMessage>(messageBus);
888
674
 
889
- Action untargetedDeregistration = typedHandler.AddGlobalUntargetedHandler(
890
- originalUntargetedMessageHandler,
891
- untargetedMessageHandler,
892
- NullDeregistration,
893
- messageBus
894
- );
895
- Action targetedDeregistration = typedHandler.AddGlobalTargetedHandler(
896
- originalTargetedMessageHandler,
897
- targetedMessageHandler,
898
- NullDeregistration,
899
- messageBus
900
- );
901
- Action broadcastDeregistration = typedHandler.AddGlobalBroadcastHandler(
902
- originalBroadcastMessageHandler,
903
- broadcastMessageHandler,
904
- NullDeregistration,
905
- messageBus
906
- );
675
+ GlobalHandlerDeregistrationState untargetedDeregistration =
676
+ typedHandler.AddGlobalUntargetedHandler(
677
+ originalUntargetedMessageHandler,
678
+ untargetedMessageHandler,
679
+ messageBus
680
+ );
681
+ GlobalHandlerDeregistrationState targetedDeregistration =
682
+ typedHandler.AddGlobalTargetedHandler(
683
+ originalTargetedMessageHandler,
684
+ targetedMessageHandler,
685
+ messageBus
686
+ );
687
+ GlobalHandlerDeregistrationState broadcastDeregistration =
688
+ typedHandler.AddGlobalBroadcastHandler(
689
+ originalBroadcastMessageHandler,
690
+ broadcastMessageHandler,
691
+ messageBus
692
+ );
907
693
 
908
- return new GlobalAcceptAllDeregistration(
694
+ return new GlobalAcceptAllDeregistrationState(
909
695
  messageBus,
910
696
  in messageBusDeregistration,
911
697
  untargetedDeregistration,
912
698
  targetedDeregistration,
913
699
  broadcastDeregistration
914
700
  );
915
-
916
- void NullDeregistration()
917
- {
918
- // No-op
919
- }
920
701
  }
921
702
 
922
703
  /// <summary>
@@ -927,7 +708,7 @@ namespace DxMessaging.Core
927
708
  /// <param name="targetedMessageHandler">MessageHandler to accept all BroadcastMessages for all entities.</param>
928
709
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
929
710
  /// <returns>The de-registration action.</returns>
930
- internal HandlerDeregistration RegisterGlobalAcceptAll(
711
+ internal GlobalAcceptAllDeregistrationState RegisterGlobalAcceptAll(
931
712
  FastHandler<IUntargetedMessage> originalUntargetedMessageHandler,
932
713
  FastHandler<IUntargetedMessage> untargetedMessageHandler,
933
714
  FastHandlerWithContext<ITargetedMessage> originalTargetedMessageHandler,
@@ -943,37 +724,32 @@ namespace DxMessaging.Core
943
724
  );
944
725
  TypedHandler<IMessage> typedHandler = GetOrCreateHandlerForType<IMessage>(messageBus);
945
726
 
946
- Action untargetedDeregistration = typedHandler.AddGlobalUntargetedHandler(
947
- originalUntargetedMessageHandler,
948
- untargetedMessageHandler,
949
- NullDeregistration,
950
- messageBus
951
- );
952
- Action targetedDeregistration = typedHandler.AddGlobalTargetedHandler(
953
- originalTargetedMessageHandler,
954
- targetedMessageHandler,
955
- NullDeregistration,
956
- messageBus
957
- );
958
- Action broadcastDeregistration = typedHandler.AddGlobalBroadcastHandler(
959
- originalBroadcastMessageHandler,
960
- broadcastMessageHandler,
961
- NullDeregistration,
962
- messageBus
963
- );
727
+ GlobalHandlerDeregistrationState untargetedDeregistration =
728
+ typedHandler.AddGlobalUntargetedHandler(
729
+ originalUntargetedMessageHandler,
730
+ untargetedMessageHandler,
731
+ messageBus
732
+ );
733
+ GlobalHandlerDeregistrationState targetedDeregistration =
734
+ typedHandler.AddGlobalTargetedHandler(
735
+ originalTargetedMessageHandler,
736
+ targetedMessageHandler,
737
+ messageBus
738
+ );
739
+ GlobalHandlerDeregistrationState broadcastDeregistration =
740
+ typedHandler.AddGlobalBroadcastHandler(
741
+ originalBroadcastMessageHandler,
742
+ broadcastMessageHandler,
743
+ messageBus
744
+ );
964
745
 
965
- return new GlobalAcceptAllDeregistration(
746
+ return new GlobalAcceptAllDeregistrationState(
966
747
  messageBus,
967
748
  in messageBusDeregistration,
968
749
  untargetedDeregistration,
969
750
  targetedDeregistration,
970
751
  broadcastDeregistration
971
752
  );
972
-
973
- void NullDeregistration()
974
- {
975
- // No-op
976
- }
977
753
  }
978
754
 
979
755
  /// <summary>
@@ -985,7 +761,7 @@ namespace DxMessaging.Core
985
761
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
986
762
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
987
763
  /// <returns>The de-registration action.</returns>
988
- internal HandlerDeregistration RegisterTargetedMessageHandler<T>(
764
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedMessageHandler<T>(
989
765
  InstanceId target,
990
766
  Action<T> originalHandler,
991
767
  Action<T> messageHandler,
@@ -1018,7 +794,7 @@ namespace DxMessaging.Core
1018
794
  /// wrapper plus a separately allocated FastHandler adapter.
1019
795
  /// <paramref name="originalHandler"/> stays the dedup/identity key.
1020
796
  /// </summary>
1021
- internal HandlerDeregistration RegisterTargetedMessageHandler<T>(
797
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedMessageHandler<T>(
1022
798
  InstanceId target,
1023
799
  Action<T> originalHandler,
1024
800
  FastHandler<T> flatInvoker,
@@ -1053,7 +829,7 @@ namespace DxMessaging.Core
1053
829
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1054
830
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1055
831
  /// <returns>The de-registration action.</returns>
1056
- internal HandlerDeregistration RegisterTargetedMessageHandler<T>(
832
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedMessageHandler<T>(
1057
833
  InstanceId target,
1058
834
  FastHandler<T> originalHandler,
1059
835
  FastHandler<T> messageHandler,
@@ -1088,7 +864,7 @@ namespace DxMessaging.Core
1088
864
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1089
865
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1090
866
  /// <returns>The de-registration action.</returns>
1091
- internal HandlerDeregistration RegisterTargetedPostProcessor<T>(
867
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedPostProcessor<T>(
1092
868
  InstanceId target,
1093
869
  Action<T> originalHandler,
1094
870
  Action<T> messageHandler,
@@ -1120,7 +896,7 @@ namespace DxMessaging.Core
1120
896
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1121
897
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1122
898
  /// <returns>The de-registration action.</returns>
1123
- internal HandlerDeregistration RegisterTargetedPostProcessor<T>(
899
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedPostProcessor<T>(
1124
900
  InstanceId target,
1125
901
  FastHandler<T> originalHandler,
1126
902
  FastHandler<T> messageHandler,
@@ -1150,7 +926,7 @@ namespace DxMessaging.Core
1150
926
  /// wrapper plus a separately allocated FastHandler adapter.
1151
927
  /// <paramref name="originalHandler"/> stays the dedup/identity key.
1152
928
  /// </summary>
1153
- internal HandlerDeregistration RegisterTargetedPostProcessor<T>(
929
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedPostProcessor<T>(
1154
930
  InstanceId target,
1155
931
  Action<T> originalHandler,
1156
932
  FastHandler<T> flatInvoker,
@@ -1181,7 +957,7 @@ namespace DxMessaging.Core
1181
957
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1182
958
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1183
959
  /// <returns>The de-registration action.</returns>
1184
- internal HandlerDeregistration RegisterTargetedWithoutTargetingPostProcessor<T>(
960
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedWithoutTargetingPostProcessor<T>(
1185
961
  Action<InstanceId, T> originalHandler,
1186
962
  Action<InstanceId, T> messageHandler,
1187
963
  int priority = 0,
@@ -1213,7 +989,7 @@ namespace DxMessaging.Core
1213
989
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1214
990
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1215
991
  /// <returns>The de-registration action.</returns>
1216
- internal HandlerDeregistration RegisterTargetedWithoutTargetingPostProcessor<T>(
992
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedWithoutTargetingPostProcessor<T>(
1217
993
  FastHandlerWithContext<T> originalHandler,
1218
994
  FastHandlerWithContext<T> messageHandler,
1219
995
  int priority = 0,
@@ -1245,7 +1021,7 @@ namespace DxMessaging.Core
1245
1021
  /// allocated FastHandlerWithContext adapter. <paramref name="originalHandler"/>
1246
1022
  /// stays the dedup/identity key.
1247
1023
  /// </summary>
1248
- internal HandlerDeregistration RegisterTargetedWithoutTargetingPostProcessor<T>(
1024
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedWithoutTargetingPostProcessor<T>(
1249
1025
  Action<InstanceId, T> originalHandler,
1250
1026
  FastHandlerWithContext<T> flatInvoker,
1251
1027
  int priority = 0,
@@ -1277,7 +1053,7 @@ namespace DxMessaging.Core
1277
1053
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1278
1054
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1279
1055
  /// <returns>The de-registration action.</returns>
1280
- internal HandlerDeregistration RegisterTargetedWithoutTargeting<T>(
1056
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedWithoutTargeting<T>(
1281
1057
  Action<InstanceId, T> originalHandler,
1282
1058
  Action<InstanceId, T> messageHandler,
1283
1059
  int priority = 0,
@@ -1306,7 +1082,7 @@ namespace DxMessaging.Core
1306
1082
  /// FastHandlerWithContext adapter. <paramref name="originalHandler"/> stays
1307
1083
  /// the dedup/identity key.
1308
1084
  /// </summary>
1309
- internal HandlerDeregistration RegisterTargetedWithoutTargeting<T>(
1085
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedWithoutTargeting<T>(
1310
1086
  Action<InstanceId, T> originalHandler,
1311
1087
  FastHandlerWithContext<T> flatInvoker,
1312
1088
  int priority = 0,
@@ -1335,7 +1111,7 @@ namespace DxMessaging.Core
1335
1111
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1336
1112
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1337
1113
  /// <returns>The de-registration action.</returns>
1338
- internal HandlerDeregistration RegisterTargetedWithoutTargeting<T>(
1114
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterTargetedWithoutTargeting<T>(
1339
1115
  FastHandlerWithContext<T> originalHandler,
1340
1116
  FastHandlerWithContext<T> messageHandler,
1341
1117
  int priority = 0,
@@ -1364,7 +1140,7 @@ namespace DxMessaging.Core
1364
1140
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1365
1141
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1366
1142
  /// <returns>The de-registration action.</returns>
1367
- internal HandlerDeregistration RegisterUntargetedMessageHandler<T>(
1143
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterUntargetedMessageHandler<T>(
1368
1144
  Action<T> originalHandler,
1369
1145
  Action<T> messageHandler,
1370
1146
  int priority = 0,
@@ -1394,7 +1170,7 @@ namespace DxMessaging.Core
1394
1170
  /// wrapper plus a separately allocated FastHandler adapter.
1395
1171
  /// <paramref name="originalHandler"/> stays the dedup/identity key.
1396
1172
  /// </summary>
1397
- internal HandlerDeregistration RegisterUntargetedMessageHandler<T>(
1173
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterUntargetedMessageHandler<T>(
1398
1174
  Action<T> originalHandler,
1399
1175
  FastHandler<T> flatInvoker,
1400
1176
  int priority = 0,
@@ -1425,7 +1201,7 @@ namespace DxMessaging.Core
1425
1201
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1426
1202
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1427
1203
  /// <returns>The de-registration action.</returns>
1428
- internal HandlerDeregistration RegisterUntargetedMessageHandler<T>(
1204
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterUntargetedMessageHandler<T>(
1429
1205
  FastHandler<T> originalHandler,
1430
1206
  FastHandler<T> messageHandler,
1431
1207
  int priority = 0,
@@ -1456,7 +1232,7 @@ namespace DxMessaging.Core
1456
1232
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1457
1233
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1458
1234
  /// <returns>The de-registration action.</returns>
1459
- internal HandlerDeregistration RegisterUntargetedPostProcessor<T>(
1235
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterUntargetedPostProcessor<T>(
1460
1236
  Action<T> originalHandler,
1461
1237
  Action<T> messageHandler,
1462
1238
  int priority = 0,
@@ -1488,7 +1264,7 @@ namespace DxMessaging.Core
1488
1264
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1489
1265
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1490
1266
  /// <returns>The de-registration action.</returns>
1491
- internal HandlerDeregistration RegisterUntargetedPostProcessor<T>(
1267
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterUntargetedPostProcessor<T>(
1492
1268
  FastHandler<T> originalHandler,
1493
1269
  FastHandler<T> messageHandler,
1494
1270
  int priority = 0,
@@ -1521,7 +1297,7 @@ namespace DxMessaging.Core
1521
1297
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1522
1298
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1523
1299
  /// <returns>The de-registration action.</returns>
1524
- internal HandlerDeregistration RegisterSourcedBroadcastMessageHandler<T>(
1300
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastMessageHandler<T>(
1525
1301
  InstanceId source,
1526
1302
  Action<T> originalHandler,
1527
1303
  Action<T> messageHandler,
@@ -1552,7 +1328,7 @@ namespace DxMessaging.Core
1552
1328
  /// separately allocated FastHandler adapter.
1553
1329
  /// <paramref name="originalHandler"/> stays the dedup/identity key.
1554
1330
  /// </summary>
1555
- internal HandlerDeregistration RegisterSourcedBroadcastMessageHandler<T>(
1331
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastMessageHandler<T>(
1556
1332
  InstanceId source,
1557
1333
  Action<T> originalHandler,
1558
1334
  FastHandler<T> flatInvoker,
@@ -1584,7 +1360,7 @@ namespace DxMessaging.Core
1584
1360
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1585
1361
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1586
1362
  /// <returns>The de-registration action.</returns>
1587
- internal HandlerDeregistration RegisterSourcedBroadcastMessageHandler<T>(
1363
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastMessageHandler<T>(
1588
1364
  InstanceId source,
1589
1365
  FastHandler<T> originalHandler,
1590
1366
  FastHandler<T> messageHandler,
@@ -1615,7 +1391,7 @@ namespace DxMessaging.Core
1615
1391
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1616
1392
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1617
1393
  /// <returns>The de-registration action.</returns>
1618
- internal HandlerDeregistration RegisterSourcedBroadcastWithoutSource<T>(
1394
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastWithoutSource<T>(
1619
1395
  Action<InstanceId, T> originalHandler,
1620
1396
  Action<InstanceId, T> messageHandler,
1621
1397
  int priority = 0,
@@ -1644,7 +1420,7 @@ namespace DxMessaging.Core
1644
1420
  /// FastHandlerWithContext adapter. <paramref name="originalHandler"/> stays
1645
1421
  /// the dedup/identity key.
1646
1422
  /// </summary>
1647
- internal HandlerDeregistration RegisterSourcedBroadcastWithoutSource<T>(
1423
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastWithoutSource<T>(
1648
1424
  Action<InstanceId, T> originalHandler,
1649
1425
  FastHandlerWithContext<T> flatInvoker,
1650
1426
  int priority = 0,
@@ -1673,7 +1449,7 @@ namespace DxMessaging.Core
1673
1449
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1674
1450
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1675
1451
  /// <returns>The de-registration action.</returns>
1676
- internal HandlerDeregistration RegisterSourcedBroadcastWithoutSource<T>(
1452
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastWithoutSource<T>(
1677
1453
  FastHandlerWithContext<T> originalHandler,
1678
1454
  FastHandlerWithContext<T> messageHandler,
1679
1455
  int priority = 0,
@@ -1703,7 +1479,7 @@ namespace DxMessaging.Core
1703
1479
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1704
1480
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1705
1481
  /// <returns>The de-registration action.</returns>
1706
- internal HandlerDeregistration RegisterSourcedBroadcastPostProcessor<T>(
1482
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastPostProcessor<T>(
1707
1483
  InstanceId source,
1708
1484
  Action<T> originalHandler,
1709
1485
  Action<T> messageHandler,
@@ -1739,7 +1515,7 @@ namespace DxMessaging.Core
1739
1515
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1740
1516
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1741
1517
  /// <returns>The de-registration action.</returns>
1742
- internal HandlerDeregistration RegisterSourcedBroadcastPostProcessor<T>(
1518
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastPostProcessor<T>(
1743
1519
  InstanceId source,
1744
1520
  FastHandler<T> originalHandler,
1745
1521
  FastHandler<T> messageHandler,
@@ -1773,7 +1549,7 @@ namespace DxMessaging.Core
1773
1549
  /// <see cref="Action{T}"/> wrapper plus a separately allocated FastHandler
1774
1550
  /// adapter. <paramref name="originalHandler"/> stays the dedup/identity key.
1775
1551
  /// </summary>
1776
- internal HandlerDeregistration RegisterSourcedBroadcastPostProcessor<T>(
1552
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastPostProcessor<T>(
1777
1553
  InstanceId source,
1778
1554
  Action<T> originalHandler,
1779
1555
  FastHandler<T> flatInvoker,
@@ -1808,7 +1584,7 @@ namespace DxMessaging.Core
1808
1584
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1809
1585
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1810
1586
  /// <returns>The de-registration action.</returns>
1811
- internal HandlerDeregistration RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1587
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1812
1588
  Action<InstanceId, T> originalHandler,
1813
1589
  Action<InstanceId, T> messageHandler,
1814
1590
  int priority = 0,
@@ -1840,7 +1616,7 @@ namespace DxMessaging.Core
1840
1616
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1841
1617
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1842
1618
  /// <returns>The de-registration action.</returns>
1843
- internal HandlerDeregistration RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1619
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1844
1620
  FastHandlerWithContext<T> originalHandler,
1845
1621
  FastHandlerWithContext<T> messageHandler,
1846
1622
  int priority = 0,
@@ -1872,7 +1648,7 @@ namespace DxMessaging.Core
1872
1648
  /// allocated FastHandlerWithContext adapter. <paramref name="originalHandler"/>
1873
1649
  /// stays the dedup/identity key.
1874
1650
  /// </summary>
1875
- internal HandlerDeregistration RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1651
+ internal TypedHandler<T>.TypedHandlerDeregistrationState RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1876
1652
  Action<InstanceId, T> originalHandler,
1877
1653
  FastHandlerWithContext<T> flatInvoker,
1878
1654
  int priority = 0,
@@ -1904,7 +1680,7 @@ namespace DxMessaging.Core
1904
1680
  /// <param name="priority">Priority to register the interceptor at (interceptors are run from low -> high priority)</param>
1905
1681
  /// <param name="messageBus">Message bus to register the interceptor on.</param>
1906
1682
  /// <returns>The de-registration action.</returns>
1907
- internal HandlerDeregistration RegisterUntargetedInterceptor<T>(
1683
+ internal InterceptorDeregistrationState<T> RegisterUntargetedInterceptor<T>(
1908
1684
  IMessageBus.UntargetedInterceptor<T> interceptor,
1909
1685
  int priority = 0,
1910
1686
  IMessageBus messageBus = null
@@ -1916,7 +1692,7 @@ namespace DxMessaging.Core
1916
1692
  interceptor,
1917
1693
  priority
1918
1694
  );
1919
- return new InterceptorDeregistration<T>(messageBus, in registration);
1695
+ return new InterceptorDeregistrationState<T>(messageBus, in registration);
1920
1696
  }
1921
1697
 
1922
1698
  /// <summary>
@@ -1927,7 +1703,7 @@ namespace DxMessaging.Core
1927
1703
  /// <param name="priority">Priority to register the interceptor at (interceptors are run from low -> high priority)</param>
1928
1704
  /// <param name="messageBus">Message bus to register the interceptor on.</param>
1929
1705
  /// <returns>The de-registration action.</returns>
1930
- internal HandlerDeregistration RegisterBroadcastInterceptor<T>(
1706
+ internal InterceptorDeregistrationState<T> RegisterBroadcastInterceptor<T>(
1931
1707
  IMessageBus.BroadcastInterceptor<T> interceptor,
1932
1708
  int priority = 0,
1933
1709
  IMessageBus messageBus = null
@@ -1939,7 +1715,7 @@ namespace DxMessaging.Core
1939
1715
  interceptor,
1940
1716
  priority
1941
1717
  );
1942
- return new InterceptorDeregistration<T>(messageBus, in registration);
1718
+ return new InterceptorDeregistrationState<T>(messageBus, in registration);
1943
1719
  }
1944
1720
 
1945
1721
  /// <summary>
@@ -1950,7 +1726,7 @@ namespace DxMessaging.Core
1950
1726
  /// <param name="priority">Priority to register the interceptor at (interceptors are run from low -> high priority)</param>
1951
1727
  /// <param name="messageBus">Message bus to register the interceptor on.</param>
1952
1728
  /// <returns>The de-registration action.</returns>
1953
- internal HandlerDeregistration RegisterTargetedInterceptor<T>(
1729
+ internal InterceptorDeregistrationState<T> RegisterTargetedInterceptor<T>(
1954
1730
  IMessageBus.TargetedInterceptor<T> interceptor,
1955
1731
  int priority = 0,
1956
1732
  IMessageBus messageBus = null
@@ -1962,7 +1738,7 @@ namespace DxMessaging.Core
1962
1738
  interceptor,
1963
1739
  priority
1964
1740
  );
1965
- return new InterceptorDeregistration<T>(messageBus, in registration);
1741
+ return new InterceptorDeregistrationState<T>(messageBus, in registration);
1966
1742
  }
1967
1743
 
1968
1744
  /// <summary>
@@ -2097,6 +1873,94 @@ namespace DxMessaging.Core
2097
1873
  return false;
2098
1874
  }
2099
1875
 
1876
+ internal bool TryObserveFastPriorityHandlerStorage<T>(
1877
+ IMessageBus messageBus,
1878
+ int slotIndex,
1879
+ int priority,
1880
+ out HandlerCacheStorageObservation observation
1881
+ )
1882
+ where T : IMessage
1883
+ {
1884
+ if (!GetHandlerForType(messageBus, out TypedHandler<T> typedHandler))
1885
+ {
1886
+ observation = default;
1887
+ return false;
1888
+ }
1889
+
1890
+ TypedSlot<T> slot = typedHandler._slots[slotIndex];
1891
+ if (
1892
+ slot == null
1893
+ || !slot.byPriority.TryGetValue(priority, out IHandlerActionCache erased)
1894
+ || erased is not HandlerActionCache<FastHandler<T>> cache
1895
+ )
1896
+ {
1897
+ observation = default;
1898
+ return false;
1899
+ }
1900
+
1901
+ observation = new HandlerCacheStorageObservation(
1902
+ slot.byPriority.Count,
1903
+ 0,
1904
+ slot.byPriority.EnsureCapacity(0),
1905
+ slot.orderedPriorities.Capacity,
1906
+ true,
1907
+ cache.entries.Count,
1908
+ cache.entries.InlineCapacity,
1909
+ cache.entries.MapCapacity,
1910
+ cache.entries.OrderCapacity,
1911
+ cache.entries.UsesSpillStorage
1912
+ );
1913
+ return true;
1914
+ }
1915
+
1916
+ internal readonly struct HandlerCacheStorageObservation
1917
+ {
1918
+ internal HandlerCacheStorageObservation(
1919
+ int priorityEntries,
1920
+ int priorityInlineCapacity,
1921
+ int priorityMapCapacity,
1922
+ int priorityOrderCapacity,
1923
+ bool priorityUsesSpillStorage,
1924
+ int handlerEntries,
1925
+ int handlerInlineCapacity,
1926
+ int handlerMapCapacity,
1927
+ int handlerOrderCapacity,
1928
+ bool handlerUsesSpillStorage
1929
+ )
1930
+ {
1931
+ PriorityEntries = priorityEntries;
1932
+ PriorityInlineCapacity = priorityInlineCapacity;
1933
+ PriorityMapCapacity = priorityMapCapacity;
1934
+ PriorityOrderCapacity = priorityOrderCapacity;
1935
+ PriorityUsesSpillStorage = priorityUsesSpillStorage;
1936
+ HandlerEntries = handlerEntries;
1937
+ HandlerInlineCapacity = handlerInlineCapacity;
1938
+ HandlerMapCapacity = handlerMapCapacity;
1939
+ HandlerOrderCapacity = handlerOrderCapacity;
1940
+ HandlerUsesSpillStorage = handlerUsesSpillStorage;
1941
+ }
1942
+
1943
+ internal int PriorityEntries { get; }
1944
+
1945
+ internal int PriorityInlineCapacity { get; }
1946
+
1947
+ internal int PriorityMapCapacity { get; }
1948
+
1949
+ internal int PriorityOrderCapacity { get; }
1950
+
1951
+ internal bool PriorityUsesSpillStorage { get; }
1952
+
1953
+ internal int HandlerEntries { get; }
1954
+
1955
+ internal int HandlerInlineCapacity { get; }
1956
+
1957
+ internal int HandlerMapCapacity { get; }
1958
+
1959
+ internal int HandlerOrderCapacity { get; }
1960
+
1961
+ internal bool HandlerUsesSpillStorage { get; }
1962
+ }
1963
+
2100
1964
  /// <summary>
2101
1965
  /// Resets empty typed-handler slots associated with
2102
1966
  /// <paramref name="messageBus"/>. The eviction layer calls through
@@ -2430,7 +2294,7 @@ namespace DxMessaging.Core
2430
2294
  && erased is HandlerActionCache<TDelegate> cache
2431
2295
  )
2432
2296
  {
2433
- return cache.insertionOrder.Count;
2297
+ return cache.entries.Count;
2434
2298
  }
2435
2299
 
2436
2300
  return 0;
@@ -2453,19 +2317,11 @@ namespace DxMessaging.Core
2453
2317
  return writeIndex;
2454
2318
  }
2455
2319
 
2456
- List<FastHandler<T>> ordered = cache.insertionOrder;
2457
- int orderedCount = ordered.Count;
2320
+ int orderedCount = cache.entries.Count;
2458
2321
  for (int i = 0; i < orderedCount; ++i)
2459
2322
  {
2460
- if (
2461
- cache.entries.TryGetValue(
2462
- ordered[i],
2463
- out HandlerActionCache<FastHandler<T>>.Entry entry
2464
- )
2465
- )
2466
- {
2467
- target[writeIndex++] = new FlatDispatchEntry<T>(this, entry.handler);
2468
- }
2323
+ HandlerActionCache<FastHandler<T>>.Entry entry = cache.entries.ValueAt(i);
2324
+ target[writeIndex++] = new FlatDispatchEntry<T>(this, entry.handler);
2469
2325
  }
2470
2326
 
2471
2327
  return writeIndex;
@@ -2488,141 +2344,394 @@ namespace DxMessaging.Core
2488
2344
  return writeIndex;
2489
2345
  }
2490
2346
 
2491
- List<Action<T>> ordered = cache.insertionOrder;
2492
- int orderedCount = ordered.Count;
2347
+ int orderedCount = cache.entries.Count;
2493
2348
  for (int i = 0; i < orderedCount; ++i)
2494
2349
  {
2495
- if (
2496
- !cache.entries.TryGetValue(
2497
- ordered[i],
2498
- out HandlerActionCache<Action<T>>.Entry entry
2499
- )
2500
- )
2350
+ HandlerActionCache<Action<T>>.Entry entry = cache.entries.ValueAt(i);
2351
+
2352
+ // Every default registration path for the flattened slots
2353
+ // supplies the adapter at registration time (AddUntargetedHandler,
2354
+ // AddTargetedHandler, AddSourcedBroadcastHandler, and their
2355
+ // post-processor siblings). The type test doubles as a null
2356
+ // guard; a missing adapter would indicate a new registration
2357
+ // path that forgot to provide one.
2358
+ //
2359
+ // INVARIANT: default-slot dispatch consumes entry.flatInvoker.
2360
+ // entry.handler is not a safe dispatch target because
2361
+ // diagnostics-folding Add* overloads may store the raw user
2362
+ // Action there as the identity key. The legacy Handle*/RunHandlers
2363
+ // path uses GetOrAddNewFlatInvokerStack for the same reason.
2364
+ if (entry.flatInvoker is FastHandler<T> invoker)
2365
+ {
2366
+ target[writeIndex++] = new FlatDispatchEntry<T>(this, invoker);
2367
+ }
2368
+ else
2369
+ {
2370
+ System.Diagnostics.Debug.Assert(
2371
+ false,
2372
+ "Default registration is missing its FastHandler flat invoker; "
2373
+ + "every Add*Handler/Add*PostProcessor default path must adapt "
2374
+ + "the augmented handler at registration time."
2375
+ );
2376
+ }
2377
+ }
2378
+
2379
+ return writeIndex;
2380
+ }
2381
+
2382
+ private int FillFastWithContextFlatEntries<T>(
2383
+ Dictionary<int, IHandlerActionCache> byPriority,
2384
+ int priority,
2385
+ ContextFlatDispatchEntry<T>[] target,
2386
+ int writeIndex
2387
+ )
2388
+ where T : IMessage
2389
+ {
2390
+ if (
2391
+ byPriority == null
2392
+ || !byPriority.TryGetValue(priority, out IHandlerActionCache erased)
2393
+ || erased is not HandlerActionCache<FastHandlerWithContext<T>> cache
2394
+ )
2395
+ {
2396
+ return writeIndex;
2397
+ }
2398
+
2399
+ int orderedCount = cache.entries.Count;
2400
+ for (int i = 0; i < orderedCount; ++i)
2401
+ {
2402
+ HandlerActionCache<FastHandlerWithContext<T>>.Entry entry = cache.entries.ValueAt(
2403
+ i
2404
+ );
2405
+ target[writeIndex++] = new ContextFlatDispatchEntry<T>(this, entry.handler);
2406
+ }
2407
+
2408
+ return writeIndex;
2409
+ }
2410
+
2411
+ private int FillDefaultWithContextFlatEntries<T>(
2412
+ Dictionary<int, IHandlerActionCache> byPriority,
2413
+ int priority,
2414
+ ContextFlatDispatchEntry<T>[] target,
2415
+ int writeIndex
2416
+ )
2417
+ where T : IMessage
2418
+ {
2419
+ if (
2420
+ byPriority == null
2421
+ || !byPriority.TryGetValue(priority, out IHandlerActionCache erased)
2422
+ || erased is not HandlerActionCache<Action<InstanceId, T>> cache
2423
+ )
2424
+ {
2425
+ return writeIndex;
2426
+ }
2427
+
2428
+ int orderedCount = cache.entries.Count;
2429
+ for (int i = 0; i < orderedCount; ++i)
2430
+ {
2431
+ HandlerActionCache<Action<InstanceId, T>>.Entry entry = cache.entries.ValueAt(i);
2432
+
2433
+ // See FillDefaultFlatEntries: the adapter is created once at
2434
+ // registration time (AddTargetedWithoutTargetingHandler,
2435
+ // AddSourcedBroadcastWithoutSourceHandler, and their
2436
+ // post-processor siblings).
2437
+ if (entry.flatInvoker is FastHandlerWithContext<T> invoker)
2438
+ {
2439
+ target[writeIndex++] = new ContextFlatDispatchEntry<T>(this, invoker);
2440
+ }
2441
+ else
2442
+ {
2443
+ System.Diagnostics.Debug.Assert(
2444
+ false,
2445
+ "Default with-context registration is missing its "
2446
+ + "FastHandlerWithContext flat invoker; every without-context "
2447
+ + "Add* default path must adapt the augmented handler at "
2448
+ + "registration time."
2449
+ );
2450
+ }
2451
+ }
2452
+
2453
+ return writeIndex;
2454
+ }
2455
+
2456
+ internal sealed class HandlerActionCache<T> : DxMessaging.Core.Internal.IHandlerActionCache
2457
+ {
2458
+ internal struct OrderedEntries
2459
+ {
2460
+ private const int PhysicalInlineCapacity = 2;
2461
+ private static readonly EqualityComparer<T> s_comparer =
2462
+ EqualityComparer<T>.Default;
2463
+
2464
+ private int _inlineCount;
2465
+ private T _key0;
2466
+ private T _key1;
2467
+ private Entry _value0;
2468
+ private Entry _value1;
2469
+ private Dictionary<T, Entry> _spillMap;
2470
+ private List<T> _spillOrder;
2471
+
2472
+ public int Count
2473
+ {
2474
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2475
+ get => _spillMap?.Count ?? _inlineCount;
2476
+ }
2477
+
2478
+ public int InlineCapacity
2479
+ {
2480
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2481
+ get => PhysicalInlineCapacity;
2482
+ }
2483
+
2484
+ public int MapCapacity
2485
+ {
2486
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2487
+ get => _spillMap?.EnsureCapacity(0) ?? 0;
2488
+ }
2489
+
2490
+ public int OrderCapacity
2491
+ {
2492
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2493
+ get => _spillOrder?.Capacity ?? 0;
2494
+ }
2495
+
2496
+ public bool UsesSpillStorage
2497
+ {
2498
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2499
+ get => _spillMap != null;
2500
+ }
2501
+
2502
+ public Entry this[T key]
2503
+ {
2504
+ set
2505
+ {
2506
+ ThrowIfNull(key);
2507
+ if (_spillMap != null)
2508
+ {
2509
+ if (!_spillMap.ContainsKey(key))
2510
+ {
2511
+ _spillMap.Add(key, value);
2512
+ try
2513
+ {
2514
+ _spillOrder.Add(key);
2515
+ }
2516
+ catch
2517
+ {
2518
+ _ = _spillMap.Remove(key);
2519
+ throw;
2520
+ }
2521
+ return;
2522
+ }
2523
+ _spillMap[key] = value;
2524
+ return;
2525
+ }
2526
+
2527
+ int index = FindInlineIndex(key);
2528
+ if (index >= 0)
2529
+ {
2530
+ SetInlineValue(index, value);
2531
+ return;
2532
+ }
2533
+ if (_inlineCount < PhysicalInlineCapacity)
2534
+ {
2535
+ SetInline(_inlineCount, key, value);
2536
+ ++_inlineCount;
2537
+ return;
2538
+ }
2539
+
2540
+ Spill();
2541
+ _spillMap.Add(key, value);
2542
+ try
2543
+ {
2544
+ _spillOrder.Add(key);
2545
+ }
2546
+ catch
2547
+ {
2548
+ _ = _spillMap.Remove(key);
2549
+ throw;
2550
+ }
2551
+ }
2552
+ }
2553
+
2554
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2555
+ public bool ContainsKey(T key)
2556
+ {
2557
+ ThrowIfNull(key);
2558
+ return _spillMap?.ContainsKey(key) ?? FindInlineIndex(key) >= 0;
2559
+ }
2560
+
2561
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2562
+ public bool TryGetValue(T key, out Entry value)
2563
+ {
2564
+ ThrowIfNull(key);
2565
+ if (_spillMap != null)
2566
+ {
2567
+ return _spillMap.TryGetValue(key, out value);
2568
+ }
2569
+ int index = FindInlineIndex(key);
2570
+ if (index >= 0)
2571
+ {
2572
+ value = GetInlineValue(index);
2573
+ return true;
2574
+ }
2575
+ value = default;
2576
+ return false;
2577
+ }
2578
+
2579
+ public bool Remove(T key)
2580
+ {
2581
+ ThrowIfNull(key);
2582
+ if (_spillMap != null)
2583
+ {
2584
+ if (!_spillMap.Remove(key))
2585
+ {
2586
+ return false;
2587
+ }
2588
+ _ = _spillOrder.Remove(key);
2589
+ return true;
2590
+ }
2591
+ int index = FindInlineIndex(key);
2592
+ if (index < 0)
2593
+ {
2594
+ return false;
2595
+ }
2596
+ int lastIndex = _inlineCount - 1;
2597
+ for (int moveIndex = index; moveIndex < lastIndex; ++moveIndex)
2598
+ {
2599
+ SetInline(
2600
+ moveIndex,
2601
+ GetInlineKey(moveIndex + 1),
2602
+ GetInlineValue(moveIndex + 1)
2603
+ );
2604
+ }
2605
+ ClearInline(lastIndex);
2606
+ --_inlineCount;
2607
+ return true;
2608
+ }
2609
+
2610
+ public void Clear()
2611
+ {
2612
+ if (_spillMap != null)
2613
+ {
2614
+ _spillMap.Clear();
2615
+ _spillOrder.Clear();
2616
+ return;
2617
+ }
2618
+ for (int index = 0; index < _inlineCount; ++index)
2619
+ {
2620
+ ClearInline(index);
2621
+ }
2622
+ _inlineCount = 0;
2623
+ }
2624
+
2625
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2626
+ public T KeyAt(int index)
2501
2627
  {
2502
- continue;
2628
+ if (_spillOrder != null)
2629
+ {
2630
+ return _spillOrder[index];
2631
+ }
2632
+ if ((uint)index >= (uint)_inlineCount)
2633
+ {
2634
+ throw new ArgumentOutOfRangeException(nameof(index));
2635
+ }
2636
+ return GetInlineKey(index);
2503
2637
  }
2504
2638
 
2505
- // Every default registration path for the flattened slots
2506
- // supplies the adapter at registration time (AddUntargetedHandler,
2507
- // AddTargetedHandler, AddSourcedBroadcastHandler, and their
2508
- // post-processor siblings). The type test doubles as a null
2509
- // guard; a missing adapter would indicate a new registration
2510
- // path that forgot to provide one.
2511
- //
2512
- // INVARIANT: default-slot dispatch consumes entry.flatInvoker.
2513
- // entry.handler is not a safe dispatch target because
2514
- // diagnostics-folding Add* overloads may store the raw user
2515
- // Action there as the identity key. The legacy Handle*/RunHandlers
2516
- // path uses GetOrAddNewFlatInvokerStack for the same reason.
2517
- if (entry.flatInvoker is FastHandler<T> invoker)
2639
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2640
+ public Entry ValueAt(int index)
2518
2641
  {
2519
- target[writeIndex++] = new FlatDispatchEntry<T>(this, invoker);
2642
+ if (_spillOrder != null)
2643
+ {
2644
+ return _spillMap[_spillOrder[index]];
2645
+ }
2646
+ if ((uint)index >= (uint)_inlineCount)
2647
+ {
2648
+ throw new ArgumentOutOfRangeException(nameof(index));
2649
+ }
2650
+ return GetInlineValue(index);
2520
2651
  }
2521
- else
2652
+
2653
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2654
+ private int FindInlineIndex(T key)
2522
2655
  {
2523
- System.Diagnostics.Debug.Assert(
2524
- false,
2525
- "Default registration is missing its FastHandler flat invoker; "
2526
- + "every Add*Handler/Add*PostProcessor default path must adapt "
2527
- + "the augmented handler at registration time."
2528
- );
2656
+ if (_inlineCount > 0 && s_comparer.Equals(_key0, key))
2657
+ {
2658
+ return 0;
2659
+ }
2660
+ if (_inlineCount > 1 && s_comparer.Equals(_key1, key))
2661
+ {
2662
+ return 1;
2663
+ }
2664
+ return -1;
2529
2665
  }
2530
- }
2531
2666
 
2532
- return writeIndex;
2533
- }
2534
-
2535
- private int FillFastWithContextFlatEntries<T>(
2536
- Dictionary<int, IHandlerActionCache> byPriority,
2537
- int priority,
2538
- ContextFlatDispatchEntry<T>[] target,
2539
- int writeIndex
2540
- )
2541
- where T : IMessage
2542
- {
2543
- if (
2544
- byPriority == null
2545
- || !byPriority.TryGetValue(priority, out IHandlerActionCache erased)
2546
- || erased is not HandlerActionCache<FastHandlerWithContext<T>> cache
2547
- )
2548
- {
2549
- return writeIndex;
2550
- }
2667
+ private void Spill()
2668
+ {
2669
+ Dictionary<T, Entry> map = new(PhysicalInlineCapacity * 2);
2670
+ List<T> order = new(PhysicalInlineCapacity * 2);
2671
+ for (int index = 0; index < _inlineCount; ++index)
2672
+ {
2673
+ T key = GetInlineKey(index);
2674
+ map.Add(key, GetInlineValue(index));
2675
+ order.Add(key);
2676
+ }
2677
+ for (int index = 0; index < _inlineCount; ++index)
2678
+ {
2679
+ ClearInline(index);
2680
+ }
2681
+ _inlineCount = 0;
2682
+ _spillMap = map;
2683
+ _spillOrder = order;
2684
+ }
2551
2685
 
2552
- List<FastHandlerWithContext<T>> ordered = cache.insertionOrder;
2553
- int orderedCount = ordered.Count;
2554
- for (int i = 0; i < orderedCount; ++i)
2555
- {
2556
- if (
2557
- cache.entries.TryGetValue(
2558
- ordered[i],
2559
- out HandlerActionCache<FastHandlerWithContext<T>>.Entry entry
2560
- )
2561
- )
2686
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2687
+ private T GetInlineKey(int index)
2562
2688
  {
2563
- target[writeIndex++] = new ContextFlatDispatchEntry<T>(this, entry.handler);
2689
+ return index == 0 ? _key0 : _key1;
2564
2690
  }
2565
- }
2566
2691
 
2567
- return writeIndex;
2568
- }
2692
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2693
+ private Entry GetInlineValue(int index)
2694
+ {
2695
+ return index == 0 ? _value0 : _value1;
2696
+ }
2569
2697
 
2570
- private int FillDefaultWithContextFlatEntries<T>(
2571
- Dictionary<int, IHandlerActionCache> byPriority,
2572
- int priority,
2573
- ContextFlatDispatchEntry<T>[] target,
2574
- int writeIndex
2575
- )
2576
- where T : IMessage
2577
- {
2578
- if (
2579
- byPriority == null
2580
- || !byPriority.TryGetValue(priority, out IHandlerActionCache erased)
2581
- || erased is not HandlerActionCache<Action<InstanceId, T>> cache
2582
- )
2583
- {
2584
- return writeIndex;
2585
- }
2698
+ private void SetInline(int index, T key, Entry value)
2699
+ {
2700
+ if (index == 0)
2701
+ {
2702
+ _key0 = key;
2703
+ _value0 = value;
2704
+ return;
2705
+ }
2706
+ _key1 = key;
2707
+ _value1 = value;
2708
+ }
2586
2709
 
2587
- List<Action<InstanceId, T>> ordered = cache.insertionOrder;
2588
- int orderedCount = ordered.Count;
2589
- for (int i = 0; i < orderedCount; ++i)
2590
- {
2591
- if (
2592
- !cache.entries.TryGetValue(
2593
- ordered[i],
2594
- out HandlerActionCache<Action<InstanceId, T>>.Entry entry
2595
- )
2596
- )
2710
+ private void SetInlineValue(int index, Entry value)
2597
2711
  {
2598
- continue;
2712
+ if (index == 0)
2713
+ {
2714
+ _value0 = value;
2715
+ return;
2716
+ }
2717
+ _value1 = value;
2599
2718
  }
2600
2719
 
2601
- // See FillDefaultFlatEntries: the adapter is created once at
2602
- // registration time (AddTargetedWithoutTargetingHandler,
2603
- // AddSourcedBroadcastWithoutSourceHandler, and their
2604
- // post-processor siblings).
2605
- if (entry.flatInvoker is FastHandlerWithContext<T> invoker)
2720
+ private void ClearInline(int index)
2606
2721
  {
2607
- target[writeIndex++] = new ContextFlatDispatchEntry<T>(this, invoker);
2722
+ SetInline(index, default, default);
2608
2723
  }
2609
- else
2724
+
2725
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2726
+ private static void ThrowIfNull(T key)
2610
2727
  {
2611
- System.Diagnostics.Debug.Assert(
2612
- false,
2613
- "Default with-context registration is missing its "
2614
- + "FastHandlerWithContext flat invoker; every without-context "
2615
- + "Add* default path must adapt the augmented handler at "
2616
- + "registration time."
2617
- );
2728
+ if (key is null)
2729
+ {
2730
+ throw new ArgumentNullException(nameof(key));
2731
+ }
2618
2732
  }
2619
2733
  }
2620
2734
 
2621
- return writeIndex;
2622
- }
2623
-
2624
- internal sealed class HandlerActionCache<T> : DxMessaging.Core.Internal.IHandlerActionCache
2625
- {
2626
2735
  // Uses outer T as a field type -- reflection callers must close
2627
2736
  // via MakeGenericType(outer.GetGenericArguments()) before passing
2628
2737
  // this type to Activator.CreateInstance. See
@@ -2680,19 +2789,12 @@ namespace DxMessaging.Core
2680
2789
  public readonly object flatInvoker;
2681
2790
  }
2682
2791
 
2683
- public readonly Dictionary<T, Entry> entries = new();
2684
-
2685
- // Original-handler keys in first-registration order. Dictionary
2686
- // enumeration order is NOT stable across Remove/Add churn (.NET
2687
- // reuses freed slots LIFO), so dispatch snapshots are rebuilt from
2688
- // this list instead of from <see cref="entries"/> to honor the
2689
- // documented "same priority uses registration order" contract.
2690
- // Invariants: contains exactly the keys of <see cref="entries"/>;
2691
- // a key is appended on its FIRST registration only (refcount
2692
- // increments do not move it) and removed when its refcount drops
2693
- // to zero. Maintained exclusively by the AddHandler* family and
2694
- // <see cref="DxMessaging.Core.Internal.IHandlerActionCache.Reset"/>.
2695
- public readonly List<T> insertionOrder = new();
2792
+ // The first two distinct handlers live directly in this object.
2793
+ // The third materializes a dictionary plus an ordered key list;
2794
+ // that spill remains allocated for reuse after removal or reset.
2795
+ // Both representations preserve first-registration order and make
2796
+ // entry/order drift structurally impossible.
2797
+ public OrderedEntries entries;
2696
2798
  public readonly List<T> cache = new();
2697
2799
  private System.Collections.IList _flatInvokerCache;
2698
2800
  public long version;
@@ -2726,7 +2828,7 @@ namespace DxMessaging.Core
2726
2828
  set => lastSeenEmissionId = value;
2727
2829
  }
2728
2830
 
2729
- /// <summary>True iff the entries dictionary holds zero handlers.</summary>
2831
+ /// <summary>True iff the ordered entry storage holds zero handlers.</summary>
2730
2832
  bool DxMessaging.Core.Internal.IHandlerActionCache.IsEmpty
2731
2833
  {
2732
2834
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -2740,7 +2842,6 @@ namespace DxMessaging.Core
2740
2842
  void DxMessaging.Core.Internal.IHandlerActionCache.Reset()
2741
2843
  {
2742
2844
  entries.Clear();
2743
- insertionOrder.Clear();
2744
2845
  cache.Clear();
2745
2846
  _flatInvokerCache?.Clear();
2746
2847
  lastSeenVersion = -1;
@@ -2788,9 +2889,6 @@ namespace DxMessaging.Core
2788
2889
  if (entry.count <= 1)
2789
2890
  {
2790
2891
  _ = entries.Remove(key);
2791
- // List.Remove is O(n) over the same-priority bucket;
2792
- // accepted (see AddHandlerPreservingPriorityKey).
2793
- _ = insertionOrder.Remove(key);
2794
2892
  version++;
2795
2893
  wasLastForEntry = true;
2796
2894
  return true;
@@ -2844,8 +2942,8 @@ namespace DxMessaging.Core
2844
2942
  internal bool _markedForOuterRemoval;
2845
2943
 
2846
2944
  /// <summary>
2847
- /// Per-handle teardown object for the typed-handler registration
2848
- /// path, replacing the deregistration <see cref="Action"/> closure
2945
+ /// Reusable teardown state for the typed-handler registration path,
2946
+ /// replacing the deregistration <see cref="Action"/> closure
2849
2947
  /// the two instance <c>AddHandlerPreservingPriorityKey</c> overloads
2850
2948
  /// used to build and return. All of the closure's captured locals
2851
2949
  /// are stored as fields; <see cref="Deregister"/> re-expresses the
@@ -2853,9 +2951,11 @@ namespace DxMessaging.Core
2853
2951
  /// resolution by <see cref="_keyed"/> and using the non-generic
2854
2952
  /// <see cref="IHandlerActionCache"/> teardown ops
2855
2953
  /// (<c>ContainsEntry</c> / <c>BumpVersion</c> / <c>DeregisterEntry</c>)
2856
- /// so it needs no delegate-shape type argument.
2954
+ /// so it needs no delegate-shape type argument. Tokens embed this value
2955
+ /// in their existing Registration object; compatibility callers can
2956
+ /// convert it to an allocation-backed wrapper or <see cref="Action"/>.
2857
2957
  /// </summary>
2858
- internal sealed class TypedHandlerDeregistration : HandlerDeregistration
2958
+ internal readonly struct TypedHandlerDeregistrationState
2859
2959
  {
2860
2960
  // Captured state (was the closure's captures).
2861
2961
  private readonly IMessageBus _messageBus;
@@ -2879,7 +2979,7 @@ namespace DxMessaging.Core
2879
2979
  private readonly MessageBusRegistration _busRegistration;
2880
2980
 
2881
2981
  // Scalar constructor.
2882
- internal TypedHandlerDeregistration(
2982
+ internal TypedHandlerDeregistrationState(
2883
2983
  IMessageBus messageBus,
2884
2984
  long resetGeneration,
2885
2985
  TypedSlot<T> slot,
@@ -2904,7 +3004,7 @@ namespace DxMessaging.Core
2904
3004
  }
2905
3005
 
2906
3006
  // Context constructor.
2907
- internal TypedHandlerDeregistration(
3007
+ internal TypedHandlerDeregistrationState(
2908
3008
  IMessageBus messageBus,
2909
3009
  long resetGeneration,
2910
3010
  TypedSlot<T> slot,
@@ -2929,7 +3029,7 @@ namespace DxMessaging.Core
2929
3029
  _busRegistration = busRegistration;
2930
3030
  }
2931
3031
 
2932
- internal override void Deregister()
3032
+ internal void Deregister()
2933
3033
  {
2934
3034
  if (
2935
3035
  !global::DxMessaging.Core.MessageBus.MessageBus.IsResetGenerationCurrent(
@@ -2996,6 +3096,40 @@ namespace DxMessaging.Core
2996
3096
  // Deliberately keep the priority and context mappings to
2997
3097
  // preserve frozen snapshots for the current emission.
2998
3098
  }
3099
+
3100
+ public static implicit operator HandlerDeregistration(
3101
+ TypedHandlerDeregistrationState state
3102
+ )
3103
+ {
3104
+ return new TypedHandlerDeregistration(state);
3105
+ }
3106
+
3107
+ public static implicit operator Action(TypedHandlerDeregistrationState state)
3108
+ {
3109
+ TypedHandlerDeregistration deregistration = new(state);
3110
+ return deregistration.Deregister;
3111
+ }
3112
+ }
3113
+
3114
+ /// <summary>
3115
+ /// Allocation-backed compatibility/spill wrapper. Token registrations keep
3116
+ /// <see cref="TypedHandlerDeregistrationState"/> inline on their existing
3117
+ /// registration object; direct handler callers and overlapping retry state
3118
+ /// use this wrapper only when independent object identity is required.
3119
+ /// </summary>
3120
+ internal sealed class TypedHandlerDeregistration : HandlerDeregistration
3121
+ {
3122
+ private readonly TypedHandlerDeregistrationState _state;
3123
+
3124
+ internal TypedHandlerDeregistration(TypedHandlerDeregistrationState state)
3125
+ {
3126
+ _state = state;
3127
+ }
3128
+
3129
+ internal override void Deregister()
3130
+ {
3131
+ _state.Deregister();
3132
+ }
2999
3133
  }
3000
3134
 
3001
3135
  int ITypedHandlerSlotSweeper.MessageTypeIndex
@@ -3749,7 +3883,7 @@ namespace DxMessaging.Core
3749
3883
  /// <param name="deregistration">Deregistration action for the handler.</param>
3750
3884
  /// <param name="priority">Priority at which to add the handler.</param>
3751
3885
  /// <returns>De-registration action to unregister the handler.</returns>
3752
- public HandlerDeregistration AddTargetedHandler(
3886
+ public TypedHandlerDeregistrationState AddTargetedHandler(
3753
3887
  InstanceId target,
3754
3888
  Action<T> originalHandler,
3755
3889
  Action<T> handler,
@@ -3782,7 +3916,7 @@ namespace DxMessaging.Core
3782
3916
  /// one <see cref="FastHandler{T}"/>). <paramref name="originalHandler"/>
3783
3917
  /// is the dedup/identity key and is never invoked for the default slot.
3784
3918
  /// </summary>
3785
- internal HandlerDeregistration AddTargetedHandler(
3919
+ internal TypedHandlerDeregistrationState AddTargetedHandler(
3786
3920
  InstanceId target,
3787
3921
  Action<T> originalHandler,
3788
3922
  FastHandler<T> flatInvoker,
@@ -3811,7 +3945,7 @@ namespace DxMessaging.Core
3811
3945
  /// <param name="deregistration">Deregistration action for the handler.</param>
3812
3946
  /// <param name="priority">Priority at which to add the handler.</param>
3813
3947
  /// <returns>De-registration action to unregister the handler.</returns>
3814
- public HandlerDeregistration AddTargetedHandler(
3948
+ public TypedHandlerDeregistrationState AddTargetedHandler(
3815
3949
  InstanceId target,
3816
3950
  FastHandler<T> originalHandler,
3817
3951
  FastHandler<T> handler,
@@ -3838,7 +3972,7 @@ namespace DxMessaging.Core
3838
3972
  /// <param name="deregistration">Deregistration action for the handler.</param>
3839
3973
  /// <param name="priority">Priority at which to add the handler.</param>
3840
3974
  /// <returns>De-registration action to unregister the handler.</returns>
3841
- public HandlerDeregistration AddTargetedWithoutTargetingHandler(
3975
+ public TypedHandlerDeregistrationState AddTargetedWithoutTargetingHandler(
3842
3976
  Action<InstanceId, T> originalHandler,
3843
3977
  Action<InstanceId, T> handler,
3844
3978
  MessageBusRegistration deregistration,
@@ -3874,7 +4008,7 @@ namespace DxMessaging.Core
3874
4008
  /// <paramref name="originalHandler"/> is the dedup/identity key and is
3875
4009
  /// never invoked for the default slot.
3876
4010
  /// </summary>
3877
- internal HandlerDeregistration AddTargetedWithoutTargetingHandler(
4011
+ internal TypedHandlerDeregistrationState AddTargetedWithoutTargetingHandler(
3878
4012
  Action<InstanceId, T> originalHandler,
3879
4013
  FastHandlerWithContext<T> flatInvoker,
3880
4014
  MessageBusRegistration deregistration,
@@ -3903,7 +4037,7 @@ namespace DxMessaging.Core
3903
4037
  /// <param name="deregistration">Deregistration action for the handler.</param>
3904
4038
  /// <param name="priority">Priority at which to add the handler.</param>
3905
4039
  /// <returns>De-registration action to unregister the handler.</returns>
3906
- public HandlerDeregistration AddTargetedWithoutTargetingHandler(
4040
+ public TypedHandlerDeregistrationState AddTargetedWithoutTargetingHandler(
3907
4041
  FastHandlerWithContext<T> originalHandler,
3908
4042
  FastHandlerWithContext<T> handler,
3909
4043
  MessageBusRegistration deregistration,
@@ -3931,7 +4065,7 @@ namespace DxMessaging.Core
3931
4065
  /// <param name="deregistration">Deregistration action for the handler.</param>
3932
4066
  /// <param name="priority">Priority at which to add the handler.</param>
3933
4067
  /// <returns>De-registration action to unregister the handler.</returns>
3934
- public HandlerDeregistration AddUntargetedHandler(
4068
+ public TypedHandlerDeregistrationState AddUntargetedHandler(
3935
4069
  Action<T> originalHandler,
3936
4070
  Action<T> handler,
3937
4071
  MessageBusRegistration deregistration,
@@ -3967,7 +4101,7 @@ namespace DxMessaging.Core
3967
4101
  /// stored as the entry handler but is never invoked for the default
3968
4102
  /// slot, which dispatches through <paramref name="flatInvoker"/>.
3969
4103
  /// </summary>
3970
- internal HandlerDeregistration AddUntargetedHandler(
4104
+ internal TypedHandlerDeregistrationState AddUntargetedHandler(
3971
4105
  Action<T> originalHandler,
3972
4106
  FastHandler<T> flatInvoker,
3973
4107
  MessageBusRegistration deregistration,
@@ -3996,7 +4130,7 @@ namespace DxMessaging.Core
3996
4130
  /// <param name="deregistration">Deregistration action for the handler.</param>
3997
4131
  /// <param name="priority">Priority at which to add the handler.</param>
3998
4132
  /// <returns>De-registration action to unregister the handler.</returns>
3999
- public HandlerDeregistration AddUntargetedHandler(
4133
+ public TypedHandlerDeregistrationState AddUntargetedHandler(
4000
4134
  FastHandler<T> originalHandler,
4001
4135
  FastHandler<T> handler,
4002
4136
  MessageBusRegistration deregistration,
@@ -4025,7 +4159,7 @@ namespace DxMessaging.Core
4025
4159
  /// <param name="deregistration">Deregistration action for the handler.</param>
4026
4160
  /// <param name="priority">Priority at which to add the handler.</param>
4027
4161
  /// <returns>De-registration action to unregister the handler.</returns>
4028
- public HandlerDeregistration AddSourcedBroadcastHandler(
4162
+ public TypedHandlerDeregistrationState AddSourcedBroadcastHandler(
4029
4163
  InstanceId source,
4030
4164
  Action<T> originalHandler,
4031
4165
  Action<T> handler,
@@ -4057,7 +4191,7 @@ namespace DxMessaging.Core
4057
4191
  /// allocated FastHandler adapter. <paramref name="originalHandler"/> is
4058
4192
  /// the dedup/identity key and is never invoked for the default slot.
4059
4193
  /// </summary>
4060
- internal HandlerDeregistration AddSourcedBroadcastHandler(
4194
+ internal TypedHandlerDeregistrationState AddSourcedBroadcastHandler(
4061
4195
  InstanceId source,
4062
4196
  Action<T> originalHandler,
4063
4197
  FastHandler<T> flatInvoker,
@@ -4086,7 +4220,7 @@ namespace DxMessaging.Core
4086
4220
  /// <param name="deregistration">Deregistration action for the handler.</param>
4087
4221
  /// <param name="priority">Priority at which to add the handler.</param>
4088
4222
  /// <returns>De-registration action to unregister the handler.</returns>
4089
- public HandlerDeregistration AddSourcedBroadcastHandler(
4223
+ public TypedHandlerDeregistrationState AddSourcedBroadcastHandler(
4090
4224
  InstanceId source,
4091
4225
  FastHandler<T> originalHandler,
4092
4226
  FastHandler<T> handler,
@@ -4113,7 +4247,7 @@ namespace DxMessaging.Core
4113
4247
  /// <param name="deregistration">Deregistration action for the handler.</param>
4114
4248
  /// <param name="priority">Priority at which to add the handler.</param>
4115
4249
  /// <returns>De-registration action to unregister the handler.</returns>
4116
- public HandlerDeregistration AddSourcedBroadcastWithoutSourceHandler(
4250
+ public TypedHandlerDeregistrationState AddSourcedBroadcastWithoutSourceHandler(
4117
4251
  Action<InstanceId, T> originalHandler,
4118
4252
  Action<InstanceId, T> handler,
4119
4253
  MessageBusRegistration deregistration,
@@ -4151,7 +4285,7 @@ namespace DxMessaging.Core
4151
4285
  /// <paramref name="originalHandler"/> is the dedup/identity key and is
4152
4286
  /// never invoked for the default slot.
4153
4287
  /// </summary>
4154
- internal HandlerDeregistration AddSourcedBroadcastWithoutSourceHandler(
4288
+ internal TypedHandlerDeregistrationState AddSourcedBroadcastWithoutSourceHandler(
4155
4289
  Action<InstanceId, T> originalHandler,
4156
4290
  FastHandlerWithContext<T> flatInvoker,
4157
4291
  MessageBusRegistration deregistration,
@@ -4180,7 +4314,7 @@ namespace DxMessaging.Core
4180
4314
  /// <param name="deregistration">Deregistration action for the handler.</param>
4181
4315
  /// <param name="priority">Priority at which to add the handler.</param>
4182
4316
  /// <returns>De-registration action to unregister the handler.</returns>
4183
- public HandlerDeregistration AddSourcedBroadcastWithoutSourceHandler(
4317
+ public TypedHandlerDeregistrationState AddSourcedBroadcastWithoutSourceHandler(
4184
4318
  FastHandlerWithContext<T> originalHandler,
4185
4319
  FastHandlerWithContext<T> handler,
4186
4320
  MessageBusRegistration deregistration,
@@ -4206,12 +4340,10 @@ namespace DxMessaging.Core
4206
4340
  /// Adds a Global UntargetedHandler to listen to all Untargeted Messages of all types, returning the deregistration action.
4207
4341
  /// </summary>
4208
4342
  /// <param name="handler">Relevant MessageHandler.</param>
4209
- /// <param name="deregistration">Deregistration action for the handler.</param>
4210
4343
  /// <returns>De-registration action to unregister the handler.</returns>
4211
- public Action AddGlobalUntargetedHandler(
4344
+ public GlobalHandlerDeregistrationState AddGlobalUntargetedHandler(
4212
4345
  Action<IUntargetedMessage> originalHandler,
4213
4346
  Action<IUntargetedMessage> handler,
4214
- Action deregistration,
4215
4347
  IMessageBus messageBus
4216
4348
  )
4217
4349
  {
@@ -4219,7 +4351,6 @@ namespace DxMessaging.Core
4219
4351
  GetOrCreateGlobalSlot(TypedGlobalSlotIndex.UntargetedDefault),
4220
4352
  originalHandler,
4221
4353
  handler,
4222
- deregistration,
4223
4354
  messageBus
4224
4355
  );
4225
4356
  }
@@ -4228,12 +4359,10 @@ namespace DxMessaging.Core
4228
4359
  /// Adds a Global fast UntargetedHandler to listen to all Untargeted Messages of all types, returning the deregistration action.
4229
4360
  /// </summary>
4230
4361
  /// <param name="handler">Relevant MessageHandler.</param>
4231
- /// <param name="deregistration">Deregistration action for the handler.</param>
4232
4362
  /// <returns>De-registration action to unregister the handler.</returns>
4233
- public Action AddGlobalUntargetedHandler(
4363
+ public GlobalHandlerDeregistrationState AddGlobalUntargetedHandler(
4234
4364
  FastHandler<IUntargetedMessage> originalHandler,
4235
4365
  FastHandler<IUntargetedMessage> handler,
4236
- Action deregistration,
4237
4366
  IMessageBus messageBus
4238
4367
  )
4239
4368
  {
@@ -4241,7 +4370,6 @@ namespace DxMessaging.Core
4241
4370
  GetOrCreateGlobalSlot(TypedGlobalSlotIndex.UntargetedFast),
4242
4371
  originalHandler,
4243
4372
  handler,
4244
- deregistration,
4245
4373
  messageBus
4246
4374
  );
4247
4375
  }
@@ -4250,12 +4378,10 @@ namespace DxMessaging.Core
4250
4378
  /// Adds a Global TargetedHandler to listen to all Targeted Messages of all types for all entities, returning the deregistration action.
4251
4379
  /// </summary>
4252
4380
  /// <param name="handler">Relevant MessageHandler.</param>
4253
- /// <param name="deregistration">Deregistration action for the handler.</param>
4254
4381
  /// <returns>De-registration action to unregister the handler.</returns>
4255
- public Action AddGlobalTargetedHandler(
4382
+ public GlobalHandlerDeregistrationState AddGlobalTargetedHandler(
4256
4383
  Action<InstanceId, ITargetedMessage> originalHandler,
4257
4384
  Action<InstanceId, ITargetedMessage> handler,
4258
- Action deregistration,
4259
4385
  IMessageBus messageBus
4260
4386
  )
4261
4387
  {
@@ -4263,7 +4389,6 @@ namespace DxMessaging.Core
4263
4389
  GetOrCreateGlobalSlot(TypedGlobalSlotIndex.TargetedDefault),
4264
4390
  originalHandler,
4265
4391
  handler,
4266
- deregistration,
4267
4392
  messageBus
4268
4393
  );
4269
4394
  }
@@ -4272,12 +4397,10 @@ namespace DxMessaging.Core
4272
4397
  /// Adds a Global fast TargetedHandler to listen to all Targeted Messages of all types for all entities (along with the target instance id), returning the deregistration action.
4273
4398
  /// </summary>
4274
4399
  /// <param name="handler">Relevant MessageHandler.</param>
4275
- /// <param name="deregistration">Deregistration action for the handler.</param>
4276
4400
  /// <returns>De-registration action to unregister the handler.</returns>
4277
- public Action AddGlobalTargetedHandler(
4401
+ public GlobalHandlerDeregistrationState AddGlobalTargetedHandler(
4278
4402
  FastHandlerWithContext<ITargetedMessage> originalHandler,
4279
4403
  FastHandlerWithContext<ITargetedMessage> handler,
4280
- Action deregistration,
4281
4404
  IMessageBus messageBus
4282
4405
  )
4283
4406
  {
@@ -4285,7 +4408,6 @@ namespace DxMessaging.Core
4285
4408
  GetOrCreateGlobalSlot(TypedGlobalSlotIndex.TargetedFast),
4286
4409
  originalHandler,
4287
4410
  handler,
4288
- deregistration,
4289
4411
  messageBus
4290
4412
  );
4291
4413
  }
@@ -4294,12 +4416,10 @@ namespace DxMessaging.Core
4294
4416
  /// Adds a Global BroadcastHandler to listen to all Targeted Messages of all types for all entities, returning the deregistration action.
4295
4417
  /// </summary>
4296
4418
  /// <param name="handler">Relevant MessageHandler.</param>
4297
- /// <param name="deregistration">Deregistration action for the handler.</param>
4298
4419
  /// <returns>De-registration action to unregister the handler.</returns>
4299
- public Action AddGlobalBroadcastHandler(
4420
+ public GlobalHandlerDeregistrationState AddGlobalBroadcastHandler(
4300
4421
  Action<InstanceId, IBroadcastMessage> originalHandler,
4301
4422
  Action<InstanceId, IBroadcastMessage> handler,
4302
- Action deregistration,
4303
4423
  IMessageBus messageBus
4304
4424
  )
4305
4425
  {
@@ -4307,7 +4427,6 @@ namespace DxMessaging.Core
4307
4427
  GetOrCreateGlobalSlot(TypedGlobalSlotIndex.BroadcastDefault),
4308
4428
  originalHandler,
4309
4429
  handler,
4310
- deregistration,
4311
4430
  messageBus
4312
4431
  );
4313
4432
  }
@@ -4316,12 +4435,10 @@ namespace DxMessaging.Core
4316
4435
  /// Adds a Global fast BroadcastHandler to listen to all Targeted Messages of all types for all entities (along with the source instance id), returning the deregistration action.
4317
4436
  /// </summary>
4318
4437
  /// <param name="handler">Relevant MessageHandler.</param>
4319
- /// <param name="deregistration">Deregistration action for the handler.</param>
4320
4438
  /// <returns>De-registration action to unregister the handler.</returns>
4321
- public Action AddGlobalBroadcastHandler(
4439
+ public GlobalHandlerDeregistrationState AddGlobalBroadcastHandler(
4322
4440
  FastHandlerWithContext<IBroadcastMessage> originalHandler,
4323
4441
  FastHandlerWithContext<IBroadcastMessage> handler,
4324
- Action deregistration,
4325
4442
  IMessageBus messageBus
4326
4443
  )
4327
4444
  {
@@ -4329,7 +4446,6 @@ namespace DxMessaging.Core
4329
4446
  GetOrCreateGlobalSlot(TypedGlobalSlotIndex.BroadcastFast),
4330
4447
  originalHandler,
4331
4448
  handler,
4332
- deregistration,
4333
4449
  messageBus
4334
4450
  );
4335
4451
  }
@@ -4341,7 +4457,7 @@ namespace DxMessaging.Core
4341
4457
  /// <param name="deregistration">Deregistration action for the handler.</param>
4342
4458
  /// <param name="priority">Priority at which to add the handler.</param>
4343
4459
  /// <returns>De-registration action to unregister the handler.</returns>
4344
- public HandlerDeregistration AddUntargetedPostProcessor(
4460
+ public TypedHandlerDeregistrationState AddUntargetedPostProcessor(
4345
4461
  Action<T> originalHandler,
4346
4462
  Action<T> handler,
4347
4463
  MessageBusRegistration deregistration,
@@ -4374,7 +4490,7 @@ namespace DxMessaging.Core
4374
4490
  /// <param name="deregistration">Deregistration action for the handler.</param>
4375
4491
  /// <param name="priority">Priority at which to add the handler.</param>
4376
4492
  /// <returns>De-registration action to unregister the handler.</returns>
4377
- public HandlerDeregistration AddUntargetedPostProcessor(
4493
+ public TypedHandlerDeregistrationState AddUntargetedPostProcessor(
4378
4494
  FastHandler<T> originalHandler,
4379
4495
  FastHandler<T> handler,
4380
4496
  MessageBusRegistration deregistration,
@@ -4403,7 +4519,7 @@ namespace DxMessaging.Core
4403
4519
  /// <param name="deregistration">Deregistration action for the handler.</param>
4404
4520
  /// <param name="priority">Priority at which to add the handler.</param>
4405
4521
  /// <returns>De-registration action to unregister the handler.</returns>
4406
- public HandlerDeregistration AddTargetedPostProcessor(
4522
+ public TypedHandlerDeregistrationState AddTargetedPostProcessor(
4407
4523
  InstanceId target,
4408
4524
  Action<T> originalHandler,
4409
4525
  Action<T> handler,
@@ -4436,7 +4552,7 @@ namespace DxMessaging.Core
4436
4552
  /// allocated FastHandler adapter. <paramref name="originalHandler"/> is the
4437
4553
  /// dedup/identity key and is never invoked for the default slot.
4438
4554
  /// </summary>
4439
- internal HandlerDeregistration AddTargetedPostProcessor(
4555
+ internal TypedHandlerDeregistrationState AddTargetedPostProcessor(
4440
4556
  InstanceId target,
4441
4557
  Action<T> originalHandler,
4442
4558
  FastHandler<T> flatInvoker,
@@ -4465,7 +4581,7 @@ namespace DxMessaging.Core
4465
4581
  /// <param name="deregistration">Deregistration action for the handler.</param>
4466
4582
  /// <param name="priority">Priority at which to add the handler.</param>
4467
4583
  /// <returns>De-registration action to unregister the handler.</returns>
4468
- public HandlerDeregistration AddTargetedPostProcessor(
4584
+ public TypedHandlerDeregistrationState AddTargetedPostProcessor(
4469
4585
  InstanceId target,
4470
4586
  FastHandler<T> originalHandler,
4471
4587
  FastHandler<T> handler,
@@ -4492,7 +4608,7 @@ namespace DxMessaging.Core
4492
4608
  /// <param name="deregistration">Deregistration action for the handler.</param>
4493
4609
  /// <param name="priority">Priority at which to add the handler.</param>
4494
4610
  /// <returns>De-registration action to unregister the handler.</returns>
4495
- public HandlerDeregistration AddTargetedWithoutTargetingPostProcessor(
4611
+ public TypedHandlerDeregistrationState AddTargetedWithoutTargetingPostProcessor(
4496
4612
  Action<InstanceId, T> originalHandler,
4497
4613
  Action<InstanceId, T> handler,
4498
4614
  MessageBusRegistration deregistration,
@@ -4530,7 +4646,7 @@ namespace DxMessaging.Core
4530
4646
  /// <paramref name="originalHandler"/> is the dedup/identity key and is never
4531
4647
  /// invoked for the default slot.
4532
4648
  /// </summary>
4533
- internal HandlerDeregistration AddTargetedWithoutTargetingPostProcessor(
4649
+ internal TypedHandlerDeregistrationState AddTargetedWithoutTargetingPostProcessor(
4534
4650
  Action<InstanceId, T> originalHandler,
4535
4651
  FastHandlerWithContext<T> flatInvoker,
4536
4652
  MessageBusRegistration deregistration,
@@ -4559,7 +4675,7 @@ namespace DxMessaging.Core
4559
4675
  /// <param name="deregistration">Deregistration action for the handler.</param>
4560
4676
  /// <param name="priority">Priority at which to add the handler.</param>
4561
4677
  /// <returns>De-registration action to unregister the handler.</returns>
4562
- public HandlerDeregistration AddTargetedWithoutTargetingPostProcessor(
4678
+ public TypedHandlerDeregistrationState AddTargetedWithoutTargetingPostProcessor(
4563
4679
  FastHandlerWithContext<T> originalHandler,
4564
4680
  FastHandlerWithContext<T> handler,
4565
4681
  MessageBusRegistration deregistration,
@@ -4588,7 +4704,7 @@ namespace DxMessaging.Core
4588
4704
  /// <param name="deregistration">Deregistration action for the handler.</param>
4589
4705
  /// <param name="priority">Priority at which to add the handler.</param>
4590
4706
  /// <returns>De-registration action to unregister the handler.</returns>
4591
- public HandlerDeregistration AddBroadcastPostProcessor(
4707
+ public TypedHandlerDeregistrationState AddBroadcastPostProcessor(
4592
4708
  InstanceId source,
4593
4709
  Action<T> originalHandler,
4594
4710
  Action<T> handler,
@@ -4621,7 +4737,7 @@ namespace DxMessaging.Core
4621
4737
  /// allocated FastHandler adapter. <paramref name="originalHandler"/> is the
4622
4738
  /// dedup/identity key and is never invoked for the default slot.
4623
4739
  /// </summary>
4624
- internal HandlerDeregistration AddBroadcastPostProcessor(
4740
+ internal TypedHandlerDeregistrationState AddBroadcastPostProcessor(
4625
4741
  InstanceId source,
4626
4742
  Action<T> originalHandler,
4627
4743
  FastHandler<T> flatInvoker,
@@ -4650,7 +4766,7 @@ namespace DxMessaging.Core
4650
4766
  /// <param name="deregistration">Deregistration action for the handler.</param>
4651
4767
  /// <param name="priority">Priority at which to add the handler.</param>
4652
4768
  /// <returns>De-registration action to unregister the handler.</returns>
4653
- public HandlerDeregistration AddBroadcastPostProcessor(
4769
+ public TypedHandlerDeregistrationState AddBroadcastPostProcessor(
4654
4770
  InstanceId source,
4655
4771
  FastHandler<T> originalHandler,
4656
4772
  FastHandler<T> handler,
@@ -4677,7 +4793,7 @@ namespace DxMessaging.Core
4677
4793
  /// <param name="deregistration">Deregistration action for the handler.</param>
4678
4794
  /// <param name="priority">Priority at which to add the handler.</param>
4679
4795
  /// <returns>De-registration action to unregister the handler.</returns>
4680
- public HandlerDeregistration AddBroadcastWithoutSourcePostProcessor(
4796
+ public TypedHandlerDeregistrationState AddBroadcastWithoutSourcePostProcessor(
4681
4797
  Action<InstanceId, T> originalHandler,
4682
4798
  Action<InstanceId, T> handler,
4683
4799
  MessageBusRegistration deregistration,
@@ -4714,7 +4830,7 @@ namespace DxMessaging.Core
4714
4830
  /// FastHandlerWithContext adapter. <paramref name="originalHandler"/> is the
4715
4831
  /// dedup/identity key and is never invoked for the default slot.
4716
4832
  /// </summary>
4717
- internal HandlerDeregistration AddBroadcastWithoutSourcePostProcessor(
4833
+ internal TypedHandlerDeregistrationState AddBroadcastWithoutSourcePostProcessor(
4718
4834
  Action<InstanceId, T> originalHandler,
4719
4835
  FastHandlerWithContext<T> flatInvoker,
4720
4836
  MessageBusRegistration deregistration,
@@ -4743,7 +4859,7 @@ namespace DxMessaging.Core
4743
4859
  /// <param name="deregistration">Deregistration action for the handler.</param>
4744
4860
  /// <param name="priority">Priority at which to add the handler.</param>
4745
4861
  /// <returns>De-registration action to unregister the handler.</returns>
4746
- public HandlerDeregistration AddBroadcastWithoutSourcePostProcessor(
4862
+ public TypedHandlerDeregistrationState AddBroadcastWithoutSourcePostProcessor(
4747
4863
  FastHandlerWithContext<T> originalHandler,
4748
4864
  FastHandlerWithContext<T> handler,
4749
4865
  MessageBusRegistration deregistration,
@@ -4783,7 +4899,7 @@ namespace DxMessaging.Core
4783
4899
  // default-shape registrations the bus-side flat snapshot consumes
4784
4900
  // (FastHandler adapter wrapping the augmented handler); see
4785
4901
  // HandlerActionCache.Entry.flatInvoker.
4786
- private HandlerDeregistration AddHandlerPreservingPriorityKey<TU>(
4902
+ private TypedHandlerDeregistrationState AddHandlerPreservingPriorityKey<TU>(
4787
4903
  InstanceId context,
4788
4904
  Dictionary<InstanceId, Dictionary<int, IHandlerActionCache>> handlersByContext,
4789
4905
  TU originalHandler,
@@ -4834,10 +4950,6 @@ namespace DxMessaging.Core
4834
4950
  );
4835
4951
 
4836
4952
  cache.entries[originalHandler] = entry;
4837
- if (firstRegistration)
4838
- {
4839
- cache.insertionOrder.Add(originalHandler);
4840
- }
4841
4953
  cache.version++;
4842
4954
  TypedSlot<T> slot = FindContextSlot(handlersByContext);
4843
4955
  if (slot != null)
@@ -4863,7 +4975,7 @@ namespace DxMessaging.Core
4863
4975
  // using the non-generic IHandlerActionCache teardown ops, so it
4864
4976
  // carries no TU type argument and replaces the closure's display
4865
4977
  // class + delegate with this single object.
4866
- return new TypedHandlerDeregistration(
4978
+ return new TypedHandlerDeregistrationState(
4867
4979
  messageBus,
4868
4980
  localResetGeneration,
4869
4981
  slot,
@@ -4925,10 +5037,6 @@ namespace DxMessaging.Core
4925
5037
  : new HandlerActionCache<TU>.Entry(entry.handler, entry.count + 1);
4926
5038
 
4927
5039
  cache.entries[originalHandler] = entry;
4928
- if (firstRegistration)
4929
- {
4930
- cache.insertionOrder.Add(originalHandler);
4931
- }
4932
5040
  cache.version++;
4933
5041
 
4934
5042
  Dictionary<
@@ -4967,7 +5075,6 @@ namespace DxMessaging.Core
4967
5075
  if (localEntry.count <= 1)
4968
5076
  {
4969
5077
  _ = localCache.entries.Remove(originalHandler);
4970
- _ = localCache.insertionOrder.Remove(originalHandler);
4971
5078
  localCache.version++;
4972
5079
  // Deliberately keep the priority and context mappings to preserve
4973
5080
  // frozen snapshots for the current emission.
@@ -5298,7 +5405,7 @@ namespace DxMessaging.Core
5298
5405
  where TMessage : IMessage
5299
5406
  where TU : IMessage
5300
5407
  {
5301
- // Snapshot semantics: do not bail on the live entries dictionary
5408
+ // Snapshot semantics: do not bail on the live entry storage
5302
5409
  // count. A mid-emit removal can drain entries while the pinned
5303
5410
  // emission snapshot in cache.cache still holds the handlers we
5304
5411
  // must invoke. Read the snapshot first and bail only if the
@@ -5306,13 +5413,13 @@ namespace DxMessaging.Core
5306
5413
  //
5307
5414
  // Perf note: GetOrAddNewHandlerStack is now invoked on every
5308
5415
  // call (including for empty caches that the previous fast-path
5309
- // would have skipped). The cost is one dictionary
5416
+ // would have skipped). The cost is one
5310
5417
  // emission-id/version compare and -- only when the per-emission
5311
5418
  // snapshot has not been pinned yet -- a single pass over
5312
5419
  // cache.entries to materialise an empty list. The win is
5313
5420
  // correctness across cross-handler mid-emit removals where the
5314
5421
  // pinned snapshot in cache.cache still holds handlers the live
5315
- // entries dictionary no longer reaches.
5422
+ // entry storage no longer reaches.
5316
5423
  if (cache == null)
5317
5424
  {
5318
5425
  return;
@@ -5421,7 +5528,7 @@ namespace DxMessaging.Core
5421
5528
  {
5422
5529
  // Snapshot semantics: see comment on the FastHandler<TU> overload.
5423
5530
  // The pinned emission snapshot may still hold handlers even when
5424
- // the live entries dictionary has been drained mid-emit.
5531
+ // the live entry storage has been drained mid-emit.
5425
5532
  if (cache == null)
5426
5533
  {
5427
5534
  return;
@@ -6202,34 +6309,19 @@ namespace DxMessaging.Core
6202
6309
  long emissionId
6203
6310
  )
6204
6311
  {
6205
- DebugAssertInsertionOrderInSync(actionCache);
6206
6312
  if (actionCache.lastSeenEmissionId != emissionId)
6207
6313
  {
6208
6314
  if (actionCache.version != actionCache.lastSeenVersion)
6209
6315
  {
6210
- // Rebuild the dispatch snapshot from insertionOrder, NOT from
6211
- // the entries dictionary: dictionary enumeration order permutes
6212
- // after Remove/Add churn (freed slots are reused LIFO), while
6213
- // insertionOrder preserves the documented first-registration
6214
- // order for equal-priority handlers. This branch only runs on
6215
- // registration churn (version bump), never on steady-state
6216
- // dispatch, and allocates nothing (the pooled cache list is
6217
- // cleared and refilled in place).
6316
+ // Rebuild in the ordered map's first-registration order.
6317
+ // This branch only runs on registration churn, never on
6318
+ // steady-state dispatch, and reuses the snapshot list.
6218
6319
  List<TU> list = actionCache.cache;
6219
6320
  list.Clear();
6220
- List<TU> orderedHandlers = actionCache.insertionOrder;
6221
- int orderedCount = orderedHandlers.Count;
6321
+ int orderedCount = actionCache.entries.Count;
6222
6322
  for (int i = 0; i < orderedCount; ++i)
6223
6323
  {
6224
- if (
6225
- actionCache.entries.TryGetValue(
6226
- orderedHandlers[i],
6227
- out HandlerActionCache<TU>.Entry entry
6228
- )
6229
- )
6230
- {
6231
- list.Add(entry.handler);
6232
- }
6324
+ list.Add(actionCache.entries.ValueAt(i).handler);
6233
6325
  }
6234
6326
  actionCache.lastSeenVersion = actionCache.version;
6235
6327
  }
@@ -6249,26 +6341,16 @@ namespace DxMessaging.Core
6249
6341
  )
6250
6342
  where TInvoker : class
6251
6343
  {
6252
- DebugAssertInsertionOrderInSync(actionCache);
6253
6344
  if (actionCache.flatInvokerLastSeenEmissionId != emissionId)
6254
6345
  {
6255
6346
  if (actionCache.version != actionCache.flatInvokerLastSeenVersion)
6256
6347
  {
6257
6348
  List<TInvoker> list = actionCache.GetOrCreateFlatInvokerCache<TInvoker>();
6258
6349
  list.Clear();
6259
- List<TU> orderedHandlers = actionCache.insertionOrder;
6260
- int orderedCount = orderedHandlers.Count;
6350
+ int orderedCount = actionCache.entries.Count;
6261
6351
  for (int i = 0; i < orderedCount; ++i)
6262
6352
  {
6263
- if (
6264
- !actionCache.entries.TryGetValue(
6265
- orderedHandlers[i],
6266
- out HandlerActionCache<TU>.Entry entry
6267
- )
6268
- )
6269
- {
6270
- continue;
6271
- }
6353
+ HandlerActionCache<TU>.Entry entry = actionCache.entries.ValueAt(i);
6272
6354
 
6273
6355
  if (entry.flatInvoker is TInvoker invoker)
6274
6356
  {
@@ -6295,32 +6377,10 @@ namespace DxMessaging.Core
6295
6377
  return actionCache.GetOrCreateFlatInvokerCache<TInvoker>();
6296
6378
  }
6297
6379
 
6298
- // Asserts insertionOrder stays in lockstep with the entries
6299
- // dictionary at every dispatch-snapshot read. Drift indicates a
6300
- // mutation site of HandlerActionCache.entries that forgot to
6301
- // mirror the change into insertionOrder (AddHandler* family,
6302
- // deregistration closures, IHandlerActionCache.Reset). Stripped
6303
- // in Release builds via [Conditional("DEBUG")] -- zero hot-path
6304
- // cost.
6305
- [Conditional("DEBUG")]
6306
- private static void DebugAssertInsertionOrderInSync<TU>(
6307
- HandlerActionCache<TU> actionCache
6308
- )
6309
- {
6310
- System.Diagnostics.Debug.Assert(
6311
- actionCache.insertionOrder.Count == actionCache.entries.Count,
6312
- "HandlerActionCache.insertionOrder must mirror entries: every first "
6313
- + "registration appends and every final deregistration removes. A "
6314
- + "count mismatch means a mutation site skipped the insertionOrder "
6315
- + "update and same-priority dispatch order is no longer trustworthy."
6316
- );
6317
- }
6318
-
6319
- private static Action AddHandler<TU>(
6380
+ private static GlobalHandlerDeregistrationState AddHandler<TU>(
6320
6381
  TypedGlobalSlot slot,
6321
6382
  TU originalHandler,
6322
6383
  TU augmentedHandler,
6323
- Action deregistration,
6324
6384
  IMessageBus messageBus
6325
6385
  )
6326
6386
  {
@@ -6349,72 +6409,24 @@ namespace DxMessaging.Core
6349
6409
  : new HandlerActionCache<TU>.Entry(entry.handler, entry.count + 1);
6350
6410
 
6351
6411
  cache.entries[originalHandler] = entry;
6352
- if (firstRegistration)
6353
- {
6354
- cache.insertionOrder.Add(originalHandler);
6355
- }
6356
6412
  cache.version++;
6357
6413
  if (firstRegistration)
6358
6414
  {
6359
6415
  slot.liveCount++;
6360
6416
  }
6361
6417
 
6362
- HandlerActionCache<TU> localCache = cache;
6363
- TypedGlobalSlot localSlot = slot;
6364
6418
  long localSlotVersion = slot.version;
6365
6419
  long localResetGeneration =
6366
6420
  global::DxMessaging.Core.MessageBus.MessageBus.GetResetGeneration(messageBus);
6367
6421
 
6368
- return () =>
6369
- {
6370
- if (
6371
- !global::DxMessaging.Core.MessageBus.MessageBus.IsResetGenerationCurrent(
6372
- messageBus,
6373
- localResetGeneration
6374
- )
6375
- )
6376
- {
6377
- return;
6378
- }
6379
-
6380
- if (localSlot.version != localSlotVersion)
6381
- {
6382
- return;
6383
- }
6384
-
6385
- if (
6386
- !localCache.entries.TryGetValue(
6387
- originalHandler,
6388
- out HandlerActionCache<TU>.Entry localEntry
6389
- )
6390
- )
6391
- {
6392
- return;
6393
- }
6394
-
6395
- localCache.version++;
6396
-
6397
- deregistration?.Invoke();
6398
- localSlot.lastTouchTicks =
6399
- global::DxMessaging.Core.MessageBus.MessageBus.GetCurrentTouchTick(
6400
- messageBus
6401
- );
6402
-
6403
- if (localEntry.count <= 1)
6404
- {
6405
- _ = localCache.entries.Remove(originalHandler);
6406
- _ = localCache.insertionOrder.Remove(originalHandler);
6407
- localCache.version++;
6408
- localSlot.liveCount--;
6409
- return;
6410
- }
6411
-
6412
- localEntry = new HandlerActionCache<TU>.Entry(
6413
- localEntry.handler,
6414
- localEntry.count - 1
6415
- );
6416
- localCache.entries[originalHandler] = localEntry;
6417
- };
6422
+ return new GlobalHandlerDeregistrationState(
6423
+ messageBus,
6424
+ localResetGeneration,
6425
+ slot,
6426
+ localSlotVersion,
6427
+ cache,
6428
+ originalHandler
6429
+ );
6418
6430
  }
6419
6431
 
6420
6432
  private static Action AddHandler<TU>(
@@ -6466,10 +6478,6 @@ namespace DxMessaging.Core
6466
6478
  : new HandlerActionCache<TU>.Entry(entry.handler, entry.count + 1);
6467
6479
 
6468
6480
  cache.entries[originalHandler] = entry;
6469
- if (firstRegistration)
6470
- {
6471
- cache.insertionOrder.Add(originalHandler);
6472
- }
6473
6481
  cache.version++;
6474
6482
 
6475
6483
  Dictionary<
@@ -6508,7 +6516,6 @@ namespace DxMessaging.Core
6508
6516
  if (localEntry.count <= 1)
6509
6517
  {
6510
6518
  _ = localCache.entries.Remove(originalHandler);
6511
- _ = localCache.insertionOrder.Remove(originalHandler);
6512
6519
  localCache.version++;
6513
6520
  if (localCache.entries.Count == 0)
6514
6521
  {
@@ -6556,10 +6563,6 @@ namespace DxMessaging.Core
6556
6563
  : new HandlerActionCache<TU>.Entry(entry.handler, entry.count + 1);
6557
6564
 
6558
6565
  cache.entries[originalHandler] = entry;
6559
- if (firstRegistration)
6560
- {
6561
- cache.insertionOrder.Add(originalHandler);
6562
- }
6563
6566
  cache.version++;
6564
6567
 
6565
6568
  HandlerActionCache<TU> localCache = cache;
@@ -6583,7 +6586,6 @@ namespace DxMessaging.Core
6583
6586
  if (localEntry.count <= 1)
6584
6587
  {
6585
6588
  _ = localCache.entries.Remove(originalHandler);
6586
- _ = localCache.insertionOrder.Remove(originalHandler);
6587
6589
  localCache.version++;
6588
6590
  return;
6589
6591
  }
@@ -6629,10 +6631,6 @@ namespace DxMessaging.Core
6629
6631
  : new HandlerActionCache<TU>.Entry(entry.handler, entry.count + 1);
6630
6632
 
6631
6633
  cache.entries[originalHandler] = entry;
6632
- if (firstRegistration)
6633
- {
6634
- cache.insertionOrder.Add(originalHandler);
6635
- }
6636
6634
  cache.version++;
6637
6635
 
6638
6636
  Dictionary<int, HandlerActionCache<TU>> localHandlers = handlers;
@@ -6661,7 +6659,6 @@ namespace DxMessaging.Core
6661
6659
  if (localEntry.count <= 1)
6662
6660
  {
6663
6661
  _ = localCache.entries.Remove(originalHandler);
6664
- _ = localCache.insertionOrder.Remove(originalHandler);
6665
6662
  localCache.version++;
6666
6663
  if (localCache.entries.Count == 0)
6667
6664
  {
@@ -6686,7 +6683,7 @@ namespace DxMessaging.Core
6686
6683
  // `flatInvoker` carries the pre-resolved flat-dispatch invoker for
6687
6684
  // registrations the bus-side flat snapshot consumes (untargeted
6688
6685
  // handle/post default handlers); see HandlerActionCache.Entry.flatInvoker.
6689
- private HandlerDeregistration AddHandlerPreservingPriorityKey<TU>(
6686
+ private TypedHandlerDeregistrationState AddHandlerPreservingPriorityKey<TU>(
6690
6687
  Dictionary<int, IHandlerActionCache> handlers,
6691
6688
  TU originalHandler,
6692
6689
  TU augmentedHandler,
@@ -6725,10 +6722,6 @@ namespace DxMessaging.Core
6725
6722
  );
6726
6723
 
6727
6724
  cache.entries[originalHandler] = entry;
6728
- if (firstRegistration)
6729
- {
6730
- cache.insertionOrder.Add(originalHandler);
6731
- }
6732
6725
  cache.version++;
6733
6726
  TypedSlot<T> slot = FindPrioritySlot(handlers);
6734
6727
  if (slot != null)
@@ -6753,7 +6746,7 @@ namespace DxMessaging.Core
6753
6746
 
6754
6747
  // See the context overload: this re-expresses the old scalar
6755
6748
  // deregistration closure verbatim as a per-handle object.
6756
- return new TypedHandlerDeregistration(
6749
+ return new TypedHandlerDeregistrationState(
6757
6750
  messageBus,
6758
6751
  localResetGeneration,
6759
6752
  slot,
@@ -6798,10 +6791,6 @@ namespace DxMessaging.Core
6798
6791
  : new HandlerActionCache<TU>.Entry(entry.handler, entry.count + 1);
6799
6792
 
6800
6793
  cache.entries[originalHandler] = entry;
6801
- if (firstRegistration)
6802
- {
6803
- cache.insertionOrder.Add(originalHandler);
6804
- }
6805
6794
  cache.version++;
6806
6795
 
6807
6796
  Dictionary<int, HandlerActionCache<TU>> localHandlers = handlers;
@@ -6830,7 +6819,6 @@ namespace DxMessaging.Core
6830
6819
  if (localEntry.count <= 1)
6831
6820
  {
6832
6821
  _ = localCache.entries.Remove(originalHandler);
6833
- _ = localCache.insertionOrder.Remove(originalHandler);
6834
6822
  localCache.version++;
6835
6823
  // Intentionally DO NOT remove the priority key here to preserve
6836
6824
  // the cache handle during an in-flight emission.