com.wallstop-studios.dxmessaging 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +286 -0
  2. package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +3 -3
  3. package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +3 -3
  4. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +3 -3
  5. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
  6. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
  7. package/Editor/DxMessagingMenu.cs.meta +3 -3
  8. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
  9. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +2 -3
  10. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
  11. package/Editor/SetupCscRsp.cs +232 -163
  12. package/README.md +1 -1
  13. package/Runtime/Analyzers.meta +8 -0
  14. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
  15. package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
  16. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
  17. package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
  18. package/Runtime/Core/IMessage.cs.meta +3 -3
  19. package/Runtime/Core/InstanceId.cs.meta +3 -3
  20. package/Runtime/Core/Internal/TypedSlots.cs +42 -0
  21. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
  22. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
  23. package/Runtime/Core/MessageBus/IMessageBus.cs +63 -28
  24. package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
  25. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
  26. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
  27. package/Runtime/Core/MessageBus/MessageBus.cs +548 -474
  28. package/Runtime/Core/MessageBus/MessageBus.cs.meta +3 -3
  29. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +3 -3
  30. package/Runtime/Core/MessageBus/MessageBusRegistration.cs +207 -0
  31. package/Runtime/Core/MessageBus/MessageBusRegistration.cs.meta +11 -0
  32. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +3 -3
  33. package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +3 -3
  34. package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +3 -3
  35. package/Runtime/Core/MessageHandler.cs +1210 -394
  36. package/Runtime/Core/MessageHandler.cs.meta +3 -3
  37. package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
  38. package/Runtime/Core/MessageRegistrationToken.cs +1296 -819
  39. package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
  40. package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +3 -3
  41. package/Runtime/Core/Messages/ITargetedMessage.cs.meta +3 -3
  42. package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +3 -3
  43. package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +3 -3
  44. package/Runtime/Core/MessagingDebug.cs.meta +3 -3
  45. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +3 -3
  46. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +3 -3
  47. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +3 -3
  48. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +3 -3
  49. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
  50. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
  51. package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
  52. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
  53. package/Runtime/Unity/MessagingComponent.cs.meta +3 -3
  54. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +3 -3
  55. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +3 -3
  56. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +3 -3
  57. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +3 -3
  58. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +3 -3
  59. package/Samples~/Mini Combat/Boot.cs.meta +3 -3
  60. package/Samples~/Mini Combat/Enemy.cs.meta +3 -3
  61. package/Samples~/Mini Combat/Messages.cs.meta +3 -3
  62. package/Samples~/Mini Combat/Player.cs.meta +3 -3
  63. package/Samples~/Mini Combat/UIOverlay.cs.meta +3 -3
  64. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +3 -3
  65. package/Samples~/UI Buttons + Inspector/Messages.cs.meta +3 -3
  66. package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +3 -3
  67. package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +3 -3
  68. package/SourceGenerators/Directory.Build.props +5 -5
  69. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
  70. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +12 -11
  71. package/package.json +6 -3
  72. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  73. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +0 -0
  74. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
  75. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +0 -0
  76. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
  77. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +0 -0
  78. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
  79. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +0 -0
  80. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
  81. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +0 -0
  82. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  83. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -0
  84. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  85. /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +0 -0
@@ -769,6 +769,99 @@ namespace DxMessaging.Core
769
769
  }
770
770
  }
771
771
 
772
+ /// <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.
781
+ /// </summary>
782
+ /// <remarks>
783
+ /// The implicit conversion to <see cref="Action"/> exists so the
784
+ /// handful of internal callers (contract tests through the
785
+ /// bus&lt;-&gt;handler boundary) that stored the old <see cref="Action"/>
786
+ /// continue to compile unchanged; it allocates a single delegate ONLY
787
+ /// 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).
790
+ /// </remarks>
791
+ internal abstract class HandlerDeregistration
792
+ {
793
+ internal abstract void Deregister();
794
+
795
+ public static implicit operator Action(HandlerDeregistration deregistration)
796
+ {
797
+ return deregistration == null ? null : deregistration.Deregister;
798
+ }
799
+ }
800
+
801
+ /// <summary>
802
+ /// Teardown for the interceptor facades: the bus-side registration is
803
+ /// the only thing to undo (there is no typed-handler cache entry).
804
+ /// Mirrors the old <c>() =&gt; messageBus.Deregister&lt;T&gt;(in registration)</c>.
805
+ /// </summary>
806
+ internal sealed class InterceptorDeregistration<T> : HandlerDeregistration
807
+ where T : IMessage
808
+ {
809
+ private readonly IMessageBus _messageBus;
810
+ private readonly MessageBusRegistration _registration;
811
+
812
+ internal InterceptorDeregistration(
813
+ IMessageBus messageBus,
814
+ in MessageBusRegistration registration
815
+ )
816
+ {
817
+ _messageBus = messageBus;
818
+ _registration = registration;
819
+ }
820
+
821
+ internal override void Deregister()
822
+ {
823
+ _messageBus.Deregister<T>(in _registration);
824
+ }
825
+ }
826
+
827
+ /// <summary>
828
+ /// Composite teardown for the global accept-all registration: undoes the
829
+ /// bus-side global registration then the three typed sub-handler
830
+ /// deregistrations, preserving the exact order of the old closure
831
+ /// (<c>messageBus.Deregister&lt;IMessage&gt;(...); untargeted(); targeted(); broadcast();</c>).
832
+ /// </summary>
833
+ internal sealed class GlobalAcceptAllDeregistration : HandlerDeregistration
834
+ {
835
+ private readonly IMessageBus _messageBus;
836
+ private readonly MessageBusRegistration _messageBusRegistration;
837
+ private readonly Action _untargetedDeregistration;
838
+ private readonly Action _targetedDeregistration;
839
+ private readonly Action _broadcastDeregistration;
840
+
841
+ internal GlobalAcceptAllDeregistration(
842
+ IMessageBus messageBus,
843
+ in MessageBusRegistration messageBusRegistration,
844
+ Action untargetedDeregistration,
845
+ Action targetedDeregistration,
846
+ Action broadcastDeregistration
847
+ )
848
+ {
849
+ _messageBus = messageBus;
850
+ _messageBusRegistration = messageBusRegistration;
851
+ _untargetedDeregistration = untargetedDeregistration;
852
+ _targetedDeregistration = targetedDeregistration;
853
+ _broadcastDeregistration = broadcastDeregistration;
854
+ }
855
+
856
+ internal override void Deregister()
857
+ {
858
+ _messageBus.Deregister<IMessage>(in _messageBusRegistration);
859
+ _untargetedDeregistration();
860
+ _targetedDeregistration();
861
+ _broadcastDeregistration();
862
+ }
863
+ }
864
+
772
865
  /// <summary>
773
866
  /// Registers this MessageHandler to Globally Accept All Messages via the MessageBus, properly handling deregistration.
774
867
  /// </summary>
@@ -777,7 +870,7 @@ namespace DxMessaging.Core
777
870
  /// <param name="targetedMessageHandler">MessageHandler to accept all BroadcastMessages for all entities.</param>
778
871
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
779
872
  /// <returns>The de-registration action.</returns>
780
- public Action RegisterGlobalAcceptAll(
873
+ internal HandlerDeregistration RegisterGlobalAcceptAll(
781
874
  Action<IUntargetedMessage> originalUntargetedMessageHandler,
782
875
  Action<IUntargetedMessage> untargetedMessageHandler,
783
876
  Action<InstanceId, ITargetedMessage> originalTargetedMessageHandler,
@@ -788,7 +881,9 @@ namespace DxMessaging.Core
788
881
  )
789
882
  {
790
883
  messageBus = ResolveMessageBus(messageBus);
791
- Action messageBusDeregistration = messageBus.RegisterGlobalAcceptAll(this);
884
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterGlobalAcceptAll(
885
+ this
886
+ );
792
887
  TypedHandler<IMessage> typedHandler = GetOrCreateHandlerForType<IMessage>(messageBus);
793
888
 
794
889
  Action untargetedDeregistration = typedHandler.AddGlobalUntargetedHandler(
@@ -810,13 +905,13 @@ namespace DxMessaging.Core
810
905
  messageBus
811
906
  );
812
907
 
813
- return () =>
814
- {
815
- messageBusDeregistration?.Invoke();
816
- untargetedDeregistration();
817
- targetedDeregistration();
818
- broadcastDeregistration();
819
- };
908
+ return new GlobalAcceptAllDeregistration(
909
+ messageBus,
910
+ in messageBusDeregistration,
911
+ untargetedDeregistration,
912
+ targetedDeregistration,
913
+ broadcastDeregistration
914
+ );
820
915
 
821
916
  void NullDeregistration()
822
917
  {
@@ -832,7 +927,7 @@ namespace DxMessaging.Core
832
927
  /// <param name="targetedMessageHandler">MessageHandler to accept all BroadcastMessages for all entities.</param>
833
928
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
834
929
  /// <returns>The de-registration action.</returns>
835
- public Action RegisterGlobalAcceptAll(
930
+ internal HandlerDeregistration RegisterGlobalAcceptAll(
836
931
  FastHandler<IUntargetedMessage> originalUntargetedMessageHandler,
837
932
  FastHandler<IUntargetedMessage> untargetedMessageHandler,
838
933
  FastHandlerWithContext<ITargetedMessage> originalTargetedMessageHandler,
@@ -843,7 +938,9 @@ namespace DxMessaging.Core
843
938
  )
844
939
  {
845
940
  messageBus = ResolveMessageBus(messageBus);
846
- Action messageBusDeregistration = messageBus.RegisterGlobalAcceptAll(this);
941
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterGlobalAcceptAll(
942
+ this
943
+ );
847
944
  TypedHandler<IMessage> typedHandler = GetOrCreateHandlerForType<IMessage>(messageBus);
848
945
 
849
946
  Action untargetedDeregistration = typedHandler.AddGlobalUntargetedHandler(
@@ -865,13 +962,13 @@ namespace DxMessaging.Core
865
962
  messageBus
866
963
  );
867
964
 
868
- return () =>
869
- {
870
- messageBusDeregistration?.Invoke();
871
- untargetedDeregistration();
872
- targetedDeregistration();
873
- broadcastDeregistration();
874
- };
965
+ return new GlobalAcceptAllDeregistration(
966
+ messageBus,
967
+ in messageBusDeregistration,
968
+ untargetedDeregistration,
969
+ targetedDeregistration,
970
+ broadcastDeregistration
971
+ );
875
972
 
876
973
  void NullDeregistration()
877
974
  {
@@ -888,7 +985,7 @@ namespace DxMessaging.Core
888
985
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
889
986
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
890
987
  /// <returns>The de-registration action.</returns>
891
- public Action RegisterTargetedMessageHandler<T>(
988
+ internal HandlerDeregistration RegisterTargetedMessageHandler<T>(
892
989
  InstanceId target,
893
990
  Action<T> originalHandler,
894
991
  Action<T> messageHandler,
@@ -898,7 +995,7 @@ namespace DxMessaging.Core
898
995
  where T : ITargetedMessage
899
996
  {
900
997
  messageBus = ResolveMessageBus(messageBus);
901
- Action messageBusDeregistration = messageBus.RegisterTargeted<T>(
998
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterTargeted<T>(
902
999
  target,
903
1000
  this,
904
1001
  priority: priority
@@ -914,6 +1011,39 @@ namespace DxMessaging.Core
914
1011
  );
915
1012
  }
916
1013
 
1014
+ /// <summary>
1015
+ /// Registers a targeted handler whose diagnostics-augmented by-ref flat
1016
+ /// invoker was already built by the caller (the registration token), so the
1017
+ /// default slot stores a single closure instead of an <see cref="Action{T}"/>
1018
+ /// wrapper plus a separately allocated FastHandler adapter.
1019
+ /// <paramref name="originalHandler"/> stays the dedup/identity key.
1020
+ /// </summary>
1021
+ internal HandlerDeregistration RegisterTargetedMessageHandler<T>(
1022
+ InstanceId target,
1023
+ Action<T> originalHandler,
1024
+ FastHandler<T> flatInvoker,
1025
+ int priority = 0,
1026
+ IMessageBus messageBus = null
1027
+ )
1028
+ where T : ITargetedMessage
1029
+ {
1030
+ messageBus = ResolveMessageBus(messageBus);
1031
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterTargeted<T>(
1032
+ target,
1033
+ this,
1034
+ priority: priority
1035
+ );
1036
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1037
+ return typedHandler.AddTargetedHandler(
1038
+ target,
1039
+ originalHandler,
1040
+ flatInvoker,
1041
+ messageBusDeregistration,
1042
+ priority,
1043
+ messageBus
1044
+ );
1045
+ }
1046
+
917
1047
  /// <summary>
918
1048
  /// Registers this MessageHandler to accept fast TargetedMessages via the MessageBus, properly handling deregistration.
919
1049
  /// </summary>
@@ -923,7 +1053,7 @@ namespace DxMessaging.Core
923
1053
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
924
1054
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
925
1055
  /// <returns>The de-registration action.</returns>
926
- public Action RegisterTargetedMessageHandler<T>(
1056
+ internal HandlerDeregistration RegisterTargetedMessageHandler<T>(
927
1057
  InstanceId target,
928
1058
  FastHandler<T> originalHandler,
929
1059
  FastHandler<T> messageHandler,
@@ -933,7 +1063,7 @@ namespace DxMessaging.Core
933
1063
  where T : ITargetedMessage
934
1064
  {
935
1065
  messageBus = ResolveMessageBus(messageBus);
936
- Action messageBusDeregistration = messageBus.RegisterTargeted<T>(
1066
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterTargeted<T>(
937
1067
  target,
938
1068
  this,
939
1069
  priority: priority
@@ -958,7 +1088,7 @@ namespace DxMessaging.Core
958
1088
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
959
1089
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
960
1090
  /// <returns>The de-registration action.</returns>
961
- public Action RegisterTargetedPostProcessor<T>(
1091
+ internal HandlerDeregistration RegisterTargetedPostProcessor<T>(
962
1092
  InstanceId target,
963
1093
  Action<T> originalHandler,
964
1094
  Action<T> messageHandler,
@@ -968,11 +1098,8 @@ namespace DxMessaging.Core
968
1098
  where T : ITargetedMessage
969
1099
  {
970
1100
  messageBus = ResolveMessageBus(messageBus);
971
- Action messageBusDeregistration = messageBus.RegisterTargetedPostProcessor<T>(
972
- target,
973
- this,
974
- priority
975
- );
1101
+ MessageBusRegistration messageBusDeregistration =
1102
+ messageBus.RegisterTargetedPostProcessor<T>(target, this, priority);
976
1103
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
977
1104
  return typedHandler.AddTargetedPostProcessor(
978
1105
  target,
@@ -993,7 +1120,7 @@ namespace DxMessaging.Core
993
1120
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
994
1121
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
995
1122
  /// <returns>The de-registration action.</returns>
996
- public Action RegisterTargetedPostProcessor<T>(
1123
+ internal HandlerDeregistration RegisterTargetedPostProcessor<T>(
997
1124
  InstanceId target,
998
1125
  FastHandler<T> originalHandler,
999
1126
  FastHandler<T> messageHandler,
@@ -1003,16 +1130,43 @@ namespace DxMessaging.Core
1003
1130
  where T : ITargetedMessage
1004
1131
  {
1005
1132
  messageBus = ResolveMessageBus(messageBus);
1006
- Action messageBusDeregistration = messageBus.RegisterTargetedPostProcessor<T>(
1133
+ MessageBusRegistration messageBusDeregistration =
1134
+ messageBus.RegisterTargetedPostProcessor<T>(target, this, priority: priority);
1135
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1136
+ return typedHandler.AddTargetedPostProcessor(
1007
1137
  target,
1008
- this,
1009
- priority: priority
1138
+ originalHandler,
1139
+ messageHandler,
1140
+ messageBusDeregistration,
1141
+ priority,
1142
+ messageBus
1010
1143
  );
1144
+ }
1145
+
1146
+ /// <summary>
1147
+ /// Registers a targeted post-processor whose diagnostics-augmented by-ref flat
1148
+ /// invoker was already built by the caller (the registration token), so the
1149
+ /// default slot stores a single closure instead of an <see cref="Action{T}"/>
1150
+ /// wrapper plus a separately allocated FastHandler adapter.
1151
+ /// <paramref name="originalHandler"/> stays the dedup/identity key.
1152
+ /// </summary>
1153
+ internal HandlerDeregistration RegisterTargetedPostProcessor<T>(
1154
+ InstanceId target,
1155
+ Action<T> originalHandler,
1156
+ FastHandler<T> flatInvoker,
1157
+ int priority = 0,
1158
+ IMessageBus messageBus = null
1159
+ )
1160
+ where T : ITargetedMessage
1161
+ {
1162
+ messageBus = ResolveMessageBus(messageBus);
1163
+ MessageBusRegistration messageBusDeregistration =
1164
+ messageBus.RegisterTargetedPostProcessor<T>(target, this, priority);
1011
1165
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1012
1166
  return typedHandler.AddTargetedPostProcessor(
1013
1167
  target,
1014
1168
  originalHandler,
1015
- messageHandler,
1169
+ flatInvoker,
1016
1170
  messageBusDeregistration,
1017
1171
  priority,
1018
1172
  messageBus
@@ -1027,7 +1181,7 @@ namespace DxMessaging.Core
1027
1181
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1028
1182
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1029
1183
  /// <returns>The de-registration action.</returns>
1030
- public Action RegisterTargetedWithoutTargetingPostProcessor<T>(
1184
+ internal HandlerDeregistration RegisterTargetedWithoutTargetingPostProcessor<T>(
1031
1185
  Action<InstanceId, T> originalHandler,
1032
1186
  Action<InstanceId, T> messageHandler,
1033
1187
  int priority = 0,
@@ -1036,7 +1190,7 @@ namespace DxMessaging.Core
1036
1190
  where T : ITargetedMessage
1037
1191
  {
1038
1192
  messageBus = ResolveMessageBus(messageBus);
1039
- Action messageBusDeregistration =
1193
+ MessageBusRegistration messageBusDeregistration =
1040
1194
  messageBus.RegisterTargetedWithoutTargetingPostProcessor<T>(
1041
1195
  priority: priority,
1042
1196
  messageHandler: this
@@ -1059,7 +1213,7 @@ namespace DxMessaging.Core
1059
1213
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1060
1214
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1061
1215
  /// <returns>The de-registration action.</returns>
1062
- public Action RegisterTargetedWithoutTargetingPostProcessor<T>(
1216
+ internal HandlerDeregistration RegisterTargetedWithoutTargetingPostProcessor<T>(
1063
1217
  FastHandlerWithContext<T> originalHandler,
1064
1218
  FastHandlerWithContext<T> messageHandler,
1065
1219
  int priority = 0,
@@ -1068,7 +1222,7 @@ namespace DxMessaging.Core
1068
1222
  where T : ITargetedMessage
1069
1223
  {
1070
1224
  messageBus = ResolveMessageBus(messageBus);
1071
- Action messageBusDeregistration =
1225
+ MessageBusRegistration messageBusDeregistration =
1072
1226
  messageBus.RegisterTargetedWithoutTargetingPostProcessor<T>(
1073
1227
  priority: priority,
1074
1228
  messageHandler: this
@@ -1083,6 +1237,38 @@ namespace DxMessaging.Core
1083
1237
  );
1084
1238
  }
1085
1239
 
1240
+ /// <summary>
1241
+ /// Registers a targeted (without-targeting) post-processor whose
1242
+ /// diagnostics-augmented by-ref context flat invoker was already built by the
1243
+ /// caller (the registration token), so the default slot stores a single closure
1244
+ /// instead of an <see cref="Action{T1, T2}"/> wrapper plus a separately
1245
+ /// allocated FastHandlerWithContext adapter. <paramref name="originalHandler"/>
1246
+ /// stays the dedup/identity key.
1247
+ /// </summary>
1248
+ internal HandlerDeregistration RegisterTargetedWithoutTargetingPostProcessor<T>(
1249
+ Action<InstanceId, T> originalHandler,
1250
+ FastHandlerWithContext<T> flatInvoker,
1251
+ int priority = 0,
1252
+ IMessageBus messageBus = null
1253
+ )
1254
+ where T : ITargetedMessage
1255
+ {
1256
+ messageBus = ResolveMessageBus(messageBus);
1257
+ MessageBusRegistration messageBusDeregistration =
1258
+ messageBus.RegisterTargetedWithoutTargetingPostProcessor<T>(
1259
+ priority: priority,
1260
+ messageHandler: this
1261
+ );
1262
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1263
+ return typedHandler.AddTargetedWithoutTargetingPostProcessor(
1264
+ originalHandler,
1265
+ flatInvoker,
1266
+ messageBusDeregistration,
1267
+ priority,
1268
+ messageBus
1269
+ );
1270
+ }
1271
+
1086
1272
  /// <summary>
1087
1273
  /// Registers this MessageHandler to accept TargetedMessages without Targeting via the MessageBus, properly handling deregistration.
1088
1274
  /// </summary>
@@ -1091,7 +1277,7 @@ namespace DxMessaging.Core
1091
1277
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1092
1278
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1093
1279
  /// <returns>The de-registration action.</returns>
1094
- public Action RegisterTargetedWithoutTargeting<T>(
1280
+ internal HandlerDeregistration RegisterTargetedWithoutTargeting<T>(
1095
1281
  Action<InstanceId, T> originalHandler,
1096
1282
  Action<InstanceId, T> messageHandler,
1097
1283
  int priority = 0,
@@ -1100,10 +1286,8 @@ namespace DxMessaging.Core
1100
1286
  where T : ITargetedMessage
1101
1287
  {
1102
1288
  messageBus = ResolveMessageBus(messageBus);
1103
- Action messageBusDeregistration = messageBus.RegisterTargetedWithoutTargeting<T>(
1104
- this,
1105
- priority: priority
1106
- );
1289
+ MessageBusRegistration messageBusDeregistration =
1290
+ messageBus.RegisterTargetedWithoutTargeting<T>(this, priority: priority);
1107
1291
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1108
1292
  return typedHandler.AddTargetedWithoutTargetingHandler(
1109
1293
  originalHandler,
@@ -1114,6 +1298,35 @@ namespace DxMessaging.Core
1114
1298
  );
1115
1299
  }
1116
1300
 
1301
+ /// <summary>
1302
+ /// Registers a targeted-without-targeting handler whose diagnostics-augmented
1303
+ /// by-ref-with-context flat invoker was already built by the caller, so the
1304
+ /// default slot stores a single closure instead of an
1305
+ /// <see cref="Action{InstanceId, T}"/> wrapper plus a separately allocated
1306
+ /// FastHandlerWithContext adapter. <paramref name="originalHandler"/> stays
1307
+ /// the dedup/identity key.
1308
+ /// </summary>
1309
+ internal HandlerDeregistration RegisterTargetedWithoutTargeting<T>(
1310
+ Action<InstanceId, T> originalHandler,
1311
+ FastHandlerWithContext<T> flatInvoker,
1312
+ int priority = 0,
1313
+ IMessageBus messageBus = null
1314
+ )
1315
+ where T : ITargetedMessage
1316
+ {
1317
+ messageBus = ResolveMessageBus(messageBus);
1318
+ MessageBusRegistration messageBusDeregistration =
1319
+ messageBus.RegisterTargetedWithoutTargeting<T>(this, priority: priority);
1320
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1321
+ return typedHandler.AddTargetedWithoutTargetingHandler(
1322
+ originalHandler,
1323
+ flatInvoker,
1324
+ messageBusDeregistration,
1325
+ priority,
1326
+ messageBus
1327
+ );
1328
+ }
1329
+
1117
1330
  /// <summary>
1118
1331
  /// Registers this MessageHandler to accept fast TargetedMessages without Targeting via the MessageBus, properly handling deregistration.
1119
1332
  /// </summary>
@@ -1122,7 +1335,7 @@ namespace DxMessaging.Core
1122
1335
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1123
1336
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1124
1337
  /// <returns>The de-registration action.</returns>
1125
- public Action RegisterTargetedWithoutTargeting<T>(
1338
+ internal HandlerDeregistration RegisterTargetedWithoutTargeting<T>(
1126
1339
  FastHandlerWithContext<T> originalHandler,
1127
1340
  FastHandlerWithContext<T> messageHandler,
1128
1341
  int priority = 0,
@@ -1131,10 +1344,8 @@ namespace DxMessaging.Core
1131
1344
  where T : ITargetedMessage
1132
1345
  {
1133
1346
  messageBus = ResolveMessageBus(messageBus);
1134
- Action messageBusDeregistration = messageBus.RegisterTargetedWithoutTargeting<T>(
1135
- this,
1136
- priority: priority
1137
- );
1347
+ MessageBusRegistration messageBusDeregistration =
1348
+ messageBus.RegisterTargetedWithoutTargeting<T>(this, priority: priority);
1138
1349
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1139
1350
  return typedHandler.AddTargetedWithoutTargetingHandler(
1140
1351
  originalHandler,
@@ -1153,7 +1364,7 @@ namespace DxMessaging.Core
1153
1364
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1154
1365
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1155
1366
  /// <returns>The de-registration action.</returns>
1156
- public Action RegisterUntargetedMessageHandler<T>(
1367
+ internal HandlerDeregistration RegisterUntargetedMessageHandler<T>(
1157
1368
  Action<T> originalHandler,
1158
1369
  Action<T> messageHandler,
1159
1370
  int priority = 0,
@@ -1162,7 +1373,7 @@ namespace DxMessaging.Core
1162
1373
  where T : IUntargetedMessage
1163
1374
  {
1164
1375
  messageBus = ResolveMessageBus(messageBus);
1165
- Action messageBusDeregistration = messageBus.RegisterUntargeted<T>(
1376
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterUntargeted<T>(
1166
1377
  this,
1167
1378
  priority: priority
1168
1379
  );
@@ -1176,6 +1387,36 @@ namespace DxMessaging.Core
1176
1387
  );
1177
1388
  }
1178
1389
 
1390
+ /// <summary>
1391
+ /// Registers an untargeted handler whose diagnostics-augmented by-ref flat
1392
+ /// invoker was already built by the caller (the registration token), so the
1393
+ /// default slot stores a single closure instead of an <see cref="Action{T}"/>
1394
+ /// wrapper plus a separately allocated FastHandler adapter.
1395
+ /// <paramref name="originalHandler"/> stays the dedup/identity key.
1396
+ /// </summary>
1397
+ internal HandlerDeregistration RegisterUntargetedMessageHandler<T>(
1398
+ Action<T> originalHandler,
1399
+ FastHandler<T> flatInvoker,
1400
+ int priority = 0,
1401
+ IMessageBus messageBus = null
1402
+ )
1403
+ where T : IUntargetedMessage
1404
+ {
1405
+ messageBus = ResolveMessageBus(messageBus);
1406
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterUntargeted<T>(
1407
+ this,
1408
+ priority: priority
1409
+ );
1410
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1411
+ return typedHandler.AddUntargetedHandler(
1412
+ originalHandler,
1413
+ flatInvoker,
1414
+ messageBusDeregistration,
1415
+ priority,
1416
+ messageBus
1417
+ );
1418
+ }
1419
+
1179
1420
  /// <summary>
1180
1421
  /// Registers this MessageHandler to accept fast UntargetedMessages via the MessageBus, properly handling deregistration.
1181
1422
  /// </summary>
@@ -1184,7 +1425,7 @@ namespace DxMessaging.Core
1184
1425
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1185
1426
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1186
1427
  /// <returns>The de-registration action.</returns>
1187
- public Action RegisterUntargetedMessageHandler<T>(
1428
+ internal HandlerDeregistration RegisterUntargetedMessageHandler<T>(
1188
1429
  FastHandler<T> originalHandler,
1189
1430
  FastHandler<T> messageHandler,
1190
1431
  int priority = 0,
@@ -1193,7 +1434,7 @@ namespace DxMessaging.Core
1193
1434
  where T : IUntargetedMessage
1194
1435
  {
1195
1436
  messageBus = ResolveMessageBus(messageBus);
1196
- Action messageBusDeregistration = messageBus.RegisterUntargeted<T>(
1437
+ MessageBusRegistration messageBusDeregistration = messageBus.RegisterUntargeted<T>(
1197
1438
  this,
1198
1439
  priority: priority
1199
1440
  );
@@ -1215,7 +1456,7 @@ namespace DxMessaging.Core
1215
1456
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1216
1457
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1217
1458
  /// <returns>The de-registration action.</returns>
1218
- public Action RegisterUntargetedPostProcessor<T>(
1459
+ internal HandlerDeregistration RegisterUntargetedPostProcessor<T>(
1219
1460
  Action<T> originalHandler,
1220
1461
  Action<T> messageHandler,
1221
1462
  int priority = 0,
@@ -1224,10 +1465,11 @@ namespace DxMessaging.Core
1224
1465
  where T : IUntargetedMessage
1225
1466
  {
1226
1467
  messageBus = ResolveMessageBus(messageBus);
1227
- Action messageBusDeregistration = messageBus.RegisterUntargetedPostProcessor<T>(
1228
- priority: priority,
1229
- messageHandler: this
1230
- );
1468
+ MessageBusRegistration messageBusDeregistration =
1469
+ messageBus.RegisterUntargetedPostProcessor<T>(
1470
+ priority: priority,
1471
+ messageHandler: this
1472
+ );
1231
1473
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1232
1474
  return typedHandler.AddUntargetedPostProcessor(
1233
1475
  originalHandler,
@@ -1246,7 +1488,7 @@ namespace DxMessaging.Core
1246
1488
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1247
1489
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1248
1490
  /// <returns>The de-registration action.</returns>
1249
- public Action RegisterUntargetedPostProcessor<T>(
1491
+ internal HandlerDeregistration RegisterUntargetedPostProcessor<T>(
1250
1492
  FastHandler<T> originalHandler,
1251
1493
  FastHandler<T> messageHandler,
1252
1494
  int priority = 0,
@@ -1255,10 +1497,11 @@ namespace DxMessaging.Core
1255
1497
  where T : IUntargetedMessage
1256
1498
  {
1257
1499
  messageBus = ResolveMessageBus(messageBus);
1258
- Action messageBusDeregistration = messageBus.RegisterUntargetedPostProcessor<T>(
1259
- priority: priority,
1260
- messageHandler: this
1261
- );
1500
+ MessageBusRegistration messageBusDeregistration =
1501
+ messageBus.RegisterUntargetedPostProcessor<T>(
1502
+ priority: priority,
1503
+ messageHandler: this
1504
+ );
1262
1505
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1263
1506
  return typedHandler.AddUntargetedPostProcessor(
1264
1507
  originalHandler,
@@ -1278,7 +1521,7 @@ namespace DxMessaging.Core
1278
1521
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1279
1522
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1280
1523
  /// <returns>The de-registration action.</returns>
1281
- public Action RegisterSourcedBroadcastMessageHandler<T>(
1524
+ internal HandlerDeregistration RegisterSourcedBroadcastMessageHandler<T>(
1282
1525
  InstanceId source,
1283
1526
  Action<T> originalHandler,
1284
1527
  Action<T> messageHandler,
@@ -1288,11 +1531,8 @@ namespace DxMessaging.Core
1288
1531
  where T : IBroadcastMessage
1289
1532
  {
1290
1533
  messageBus = ResolveMessageBus(messageBus);
1291
- Action messageBusDeregistration = messageBus.RegisterSourcedBroadcast<T>(
1292
- source,
1293
- this,
1294
- priority: priority
1295
- );
1534
+ MessageBusRegistration messageBusDeregistration =
1535
+ messageBus.RegisterSourcedBroadcast<T>(source, this, priority: priority);
1296
1536
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1297
1537
 
1298
1538
  return typedHandler.AddSourcedBroadcastHandler(
@@ -1305,6 +1545,36 @@ namespace DxMessaging.Core
1305
1545
  );
1306
1546
  }
1307
1547
 
1548
+ /// <summary>
1549
+ /// Registers a sourced-broadcast handler whose diagnostics-augmented by-ref
1550
+ /// flat invoker was already built by the caller, so the default slot stores a
1551
+ /// single closure instead of an <see cref="Action{T}"/> wrapper plus a
1552
+ /// separately allocated FastHandler adapter.
1553
+ /// <paramref name="originalHandler"/> stays the dedup/identity key.
1554
+ /// </summary>
1555
+ internal HandlerDeregistration RegisterSourcedBroadcastMessageHandler<T>(
1556
+ InstanceId source,
1557
+ Action<T> originalHandler,
1558
+ FastHandler<T> flatInvoker,
1559
+ int priority = 0,
1560
+ IMessageBus messageBus = null
1561
+ )
1562
+ where T : IBroadcastMessage
1563
+ {
1564
+ messageBus = ResolveMessageBus(messageBus);
1565
+ MessageBusRegistration messageBusDeregistration =
1566
+ messageBus.RegisterSourcedBroadcast<T>(source, this, priority: priority);
1567
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1568
+ return typedHandler.AddSourcedBroadcastHandler(
1569
+ source,
1570
+ originalHandler,
1571
+ flatInvoker,
1572
+ messageBusDeregistration,
1573
+ priority,
1574
+ messageBus
1575
+ );
1576
+ }
1577
+
1308
1578
  /// <summary>
1309
1579
  /// Registers this MessageHandler to accept fast BroadcastMessages via their MessageBus, properly handling deregistration.
1310
1580
  /// </summary>
@@ -1314,7 +1584,7 @@ namespace DxMessaging.Core
1314
1584
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1315
1585
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1316
1586
  /// <returns>The de-registration action.</returns>
1317
- public Action RegisterSourcedBroadcastMessageHandler<T>(
1587
+ internal HandlerDeregistration RegisterSourcedBroadcastMessageHandler<T>(
1318
1588
  InstanceId source,
1319
1589
  FastHandler<T> originalHandler,
1320
1590
  FastHandler<T> messageHandler,
@@ -1324,11 +1594,8 @@ namespace DxMessaging.Core
1324
1594
  where T : IBroadcastMessage
1325
1595
  {
1326
1596
  messageBus = ResolveMessageBus(messageBus);
1327
- Action messageBusDeregistration = messageBus.RegisterSourcedBroadcast<T>(
1328
- source,
1329
- this,
1330
- priority: priority
1331
- );
1597
+ MessageBusRegistration messageBusDeregistration =
1598
+ messageBus.RegisterSourcedBroadcast<T>(source, this, priority: priority);
1332
1599
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1333
1600
  return typedHandler.AddSourcedBroadcastHandler(
1334
1601
  source,
@@ -1348,7 +1615,7 @@ namespace DxMessaging.Core
1348
1615
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1349
1616
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1350
1617
  /// <returns>The de-registration action.</returns>
1351
- public Action RegisterSourcedBroadcastWithoutSource<T>(
1618
+ internal HandlerDeregistration RegisterSourcedBroadcastWithoutSource<T>(
1352
1619
  Action<InstanceId, T> originalHandler,
1353
1620
  Action<InstanceId, T> messageHandler,
1354
1621
  int priority = 0,
@@ -1357,10 +1624,8 @@ namespace DxMessaging.Core
1357
1624
  where T : IBroadcastMessage
1358
1625
  {
1359
1626
  messageBus = ResolveMessageBus(messageBus);
1360
- Action messageBusDeregistration = messageBus.RegisterSourcedBroadcastWithoutSource<T>(
1361
- this,
1362
- priority: priority
1363
- );
1627
+ MessageBusRegistration messageBusDeregistration =
1628
+ messageBus.RegisterSourcedBroadcastWithoutSource<T>(this, priority: priority);
1364
1629
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1365
1630
  return typedHandler.AddSourcedBroadcastWithoutSourceHandler(
1366
1631
  originalHandler,
@@ -1371,6 +1636,35 @@ namespace DxMessaging.Core
1371
1636
  );
1372
1637
  }
1373
1638
 
1639
+ /// <summary>
1640
+ /// Registers a broadcast-without-source handler whose diagnostics-augmented
1641
+ /// by-ref-with-context flat invoker was already built by the caller, so the
1642
+ /// default slot stores a single closure instead of an
1643
+ /// <see cref="Action{InstanceId, T}"/> wrapper plus a separately allocated
1644
+ /// FastHandlerWithContext adapter. <paramref name="originalHandler"/> stays
1645
+ /// the dedup/identity key.
1646
+ /// </summary>
1647
+ internal HandlerDeregistration RegisterSourcedBroadcastWithoutSource<T>(
1648
+ Action<InstanceId, T> originalHandler,
1649
+ FastHandlerWithContext<T> flatInvoker,
1650
+ int priority = 0,
1651
+ IMessageBus messageBus = null
1652
+ )
1653
+ where T : IBroadcastMessage
1654
+ {
1655
+ messageBus = ResolveMessageBus(messageBus);
1656
+ MessageBusRegistration messageBusDeregistration =
1657
+ messageBus.RegisterSourcedBroadcastWithoutSource<T>(this, priority: priority);
1658
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1659
+ return typedHandler.AddSourcedBroadcastWithoutSourceHandler(
1660
+ originalHandler,
1661
+ flatInvoker,
1662
+ messageBusDeregistration,
1663
+ priority,
1664
+ messageBus
1665
+ );
1666
+ }
1667
+
1374
1668
  /// <summary>
1375
1669
  /// Registers this MessageHandler to accept fast BroadcastMessage regardless of source via their MessageBus, properly handling deregistration.
1376
1670
  /// </summary>
@@ -1379,7 +1673,7 @@ namespace DxMessaging.Core
1379
1673
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1380
1674
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1381
1675
  /// <returns>The de-registration action.</returns>
1382
- public Action RegisterSourcedBroadcastWithoutSource<T>(
1676
+ internal HandlerDeregistration RegisterSourcedBroadcastWithoutSource<T>(
1383
1677
  FastHandlerWithContext<T> originalHandler,
1384
1678
  FastHandlerWithContext<T> messageHandler,
1385
1679
  int priority = 0,
@@ -1388,10 +1682,8 @@ namespace DxMessaging.Core
1388
1682
  where T : IBroadcastMessage
1389
1683
  {
1390
1684
  messageBus = ResolveMessageBus(messageBus);
1391
- Action messageBusDeregistration = messageBus.RegisterSourcedBroadcastWithoutSource<T>(
1392
- this,
1393
- priority: priority
1394
- );
1685
+ MessageBusRegistration messageBusDeregistration =
1686
+ messageBus.RegisterSourcedBroadcastWithoutSource<T>(this, priority: priority);
1395
1687
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1396
1688
  return typedHandler.AddSourcedBroadcastWithoutSourceHandler(
1397
1689
  originalHandler,
@@ -1411,7 +1703,7 @@ namespace DxMessaging.Core
1411
1703
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1412
1704
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1413
1705
  /// <returns>The de-registration action.</returns>
1414
- public Action RegisterSourcedBroadcastPostProcessor<T>(
1706
+ internal HandlerDeregistration RegisterSourcedBroadcastPostProcessor<T>(
1415
1707
  InstanceId source,
1416
1708
  Action<T> originalHandler,
1417
1709
  Action<T> messageHandler,
@@ -1421,11 +1713,12 @@ namespace DxMessaging.Core
1421
1713
  where T : IBroadcastMessage
1422
1714
  {
1423
1715
  messageBus = ResolveMessageBus(messageBus);
1424
- Action messageBusDeregistration = messageBus.RegisterBroadcastPostProcessor<T>(
1425
- source,
1426
- messageHandler: this,
1427
- priority: priority
1428
- );
1716
+ MessageBusRegistration messageBusDeregistration =
1717
+ messageBus.RegisterBroadcastPostProcessor<T>(
1718
+ source,
1719
+ messageHandler: this,
1720
+ priority: priority
1721
+ );
1429
1722
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1430
1723
  return typedHandler.AddBroadcastPostProcessor(
1431
1724
  source,
@@ -1446,7 +1739,7 @@ namespace DxMessaging.Core
1446
1739
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1447
1740
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1448
1741
  /// <returns>The de-registration action.</returns>
1449
- public Action RegisterSourcedBroadcastPostProcessor<T>(
1742
+ internal HandlerDeregistration RegisterSourcedBroadcastPostProcessor<T>(
1450
1743
  InstanceId source,
1451
1744
  FastHandler<T> originalHandler,
1452
1745
  FastHandler<T> messageHandler,
@@ -1456,16 +1749,51 @@ namespace DxMessaging.Core
1456
1749
  where T : IBroadcastMessage
1457
1750
  {
1458
1751
  messageBus = ResolveMessageBus(messageBus);
1459
- Action messageBusDeregistration = messageBus.RegisterBroadcastPostProcessor<T>(
1752
+ MessageBusRegistration messageBusDeregistration =
1753
+ messageBus.RegisterBroadcastPostProcessor<T>(
1754
+ source,
1755
+ priority: priority,
1756
+ messageHandler: this
1757
+ );
1758
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1759
+ return typedHandler.AddBroadcastPostProcessor(
1460
1760
  source,
1461
- priority: priority,
1462
- messageHandler: this
1761
+ originalHandler,
1762
+ messageHandler,
1763
+ messageBusDeregistration,
1764
+ priority,
1765
+ messageBus
1463
1766
  );
1767
+ }
1768
+
1769
+ /// <summary>
1770
+ /// Registers a sourced-broadcast post-processor whose diagnostics-augmented
1771
+ /// by-ref flat invoker was already built by the caller (the registration
1772
+ /// token), so the default slot stores a single closure instead of an
1773
+ /// <see cref="Action{T}"/> wrapper plus a separately allocated FastHandler
1774
+ /// adapter. <paramref name="originalHandler"/> stays the dedup/identity key.
1775
+ /// </summary>
1776
+ internal HandlerDeregistration RegisterSourcedBroadcastPostProcessor<T>(
1777
+ InstanceId source,
1778
+ Action<T> originalHandler,
1779
+ FastHandler<T> flatInvoker,
1780
+ int priority = 0,
1781
+ IMessageBus messageBus = null
1782
+ )
1783
+ where T : IBroadcastMessage
1784
+ {
1785
+ messageBus = ResolveMessageBus(messageBus);
1786
+ MessageBusRegistration messageBusDeregistration =
1787
+ messageBus.RegisterBroadcastPostProcessor<T>(
1788
+ source,
1789
+ priority: priority,
1790
+ messageHandler: this
1791
+ );
1464
1792
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1465
1793
  return typedHandler.AddBroadcastPostProcessor(
1466
1794
  source,
1467
1795
  originalHandler,
1468
- messageHandler,
1796
+ flatInvoker,
1469
1797
  messageBusDeregistration,
1470
1798
  priority,
1471
1799
  messageBus
@@ -1480,7 +1808,7 @@ namespace DxMessaging.Core
1480
1808
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1481
1809
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1482
1810
  /// <returns>The de-registration action.</returns>
1483
- public Action RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1811
+ internal HandlerDeregistration RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1484
1812
  Action<InstanceId, T> originalHandler,
1485
1813
  Action<InstanceId, T> messageHandler,
1486
1814
  int priority = 0,
@@ -1489,7 +1817,7 @@ namespace DxMessaging.Core
1489
1817
  where T : IBroadcastMessage
1490
1818
  {
1491
1819
  messageBus = ResolveMessageBus(messageBus);
1492
- Action messageBusDeregistration =
1820
+ MessageBusRegistration messageBusDeregistration =
1493
1821
  messageBus.RegisterBroadcastWithoutSourcePostProcessor<T>(
1494
1822
  priority: priority,
1495
1823
  messageHandler: this
@@ -1512,7 +1840,7 @@ namespace DxMessaging.Core
1512
1840
  /// <param name="priority">Priority at which to run the handler, lower runs earlier than higher.</param>
1513
1841
  /// <param name="messageBus">IMessageBus override to register with, if any. Null/not provided defaults to the GlobalMessageBus.</param>
1514
1842
  /// <returns>The de-registration action.</returns>
1515
- public Action RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1843
+ internal HandlerDeregistration RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1516
1844
  FastHandlerWithContext<T> originalHandler,
1517
1845
  FastHandlerWithContext<T> messageHandler,
1518
1846
  int priority = 0,
@@ -1521,7 +1849,39 @@ namespace DxMessaging.Core
1521
1849
  where T : IBroadcastMessage
1522
1850
  {
1523
1851
  messageBus = ResolveMessageBus(messageBus);
1524
- Action messageBusDeregistration =
1852
+ MessageBusRegistration messageBusDeregistration =
1853
+ messageBus.RegisterBroadcastWithoutSourcePostProcessor<T>(
1854
+ priority: priority,
1855
+ messageHandler: this
1856
+ );
1857
+ TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1858
+ return typedHandler.AddBroadcastWithoutSourcePostProcessor(
1859
+ originalHandler,
1860
+ messageHandler,
1861
+ messageBusDeregistration,
1862
+ priority,
1863
+ messageBus
1864
+ );
1865
+ }
1866
+
1867
+ /// <summary>
1868
+ /// Registers a sourced-broadcast (without-source) post-processor whose
1869
+ /// diagnostics-augmented by-ref context flat invoker was already built by the
1870
+ /// caller (the registration token), so the default slot stores a single closure
1871
+ /// instead of an <see cref="Action{T1, T2}"/> wrapper plus a separately
1872
+ /// allocated FastHandlerWithContext adapter. <paramref name="originalHandler"/>
1873
+ /// stays the dedup/identity key.
1874
+ /// </summary>
1875
+ internal HandlerDeregistration RegisterSourcedBroadcastWithoutSourcePostProcessor<T>(
1876
+ Action<InstanceId, T> originalHandler,
1877
+ FastHandlerWithContext<T> flatInvoker,
1878
+ int priority = 0,
1879
+ IMessageBus messageBus = null
1880
+ )
1881
+ where T : IBroadcastMessage
1882
+ {
1883
+ messageBus = ResolveMessageBus(messageBus);
1884
+ MessageBusRegistration messageBusDeregistration =
1525
1885
  messageBus.RegisterBroadcastWithoutSourcePostProcessor<T>(
1526
1886
  priority: priority,
1527
1887
  messageHandler: this
@@ -1529,7 +1889,7 @@ namespace DxMessaging.Core
1529
1889
  TypedHandler<T> typedHandler = GetOrCreateHandlerForType<T>(messageBus);
1530
1890
  return typedHandler.AddBroadcastWithoutSourcePostProcessor(
1531
1891
  originalHandler,
1532
- messageHandler,
1892
+ flatInvoker,
1533
1893
  messageBusDeregistration,
1534
1894
  priority,
1535
1895
  messageBus
@@ -1544,7 +1904,7 @@ namespace DxMessaging.Core
1544
1904
  /// <param name="priority">Priority to register the interceptor at (interceptors are run from low -> high priority)</param>
1545
1905
  /// <param name="messageBus">Message bus to register the interceptor on.</param>
1546
1906
  /// <returns>The de-registration action.</returns>
1547
- public Action RegisterUntargetedInterceptor<T>(
1907
+ internal HandlerDeregistration RegisterUntargetedInterceptor<T>(
1548
1908
  IMessageBus.UntargetedInterceptor<T> interceptor,
1549
1909
  int priority = 0,
1550
1910
  IMessageBus messageBus = null
@@ -1552,7 +1912,11 @@ namespace DxMessaging.Core
1552
1912
  where T : IUntargetedMessage
1553
1913
  {
1554
1914
  messageBus = ResolveMessageBus(messageBus);
1555
- return messageBus.RegisterUntargetedInterceptor(interceptor, priority);
1915
+ MessageBusRegistration registration = messageBus.RegisterUntargetedInterceptor(
1916
+ interceptor,
1917
+ priority
1918
+ );
1919
+ return new InterceptorDeregistration<T>(messageBus, in registration);
1556
1920
  }
1557
1921
 
1558
1922
  /// <summary>
@@ -1563,7 +1927,7 @@ namespace DxMessaging.Core
1563
1927
  /// <param name="priority">Priority to register the interceptor at (interceptors are run from low -> high priority)</param>
1564
1928
  /// <param name="messageBus">Message bus to register the interceptor on.</param>
1565
1929
  /// <returns>The de-registration action.</returns>
1566
- public Action RegisterBroadcastInterceptor<T>(
1930
+ internal HandlerDeregistration RegisterBroadcastInterceptor<T>(
1567
1931
  IMessageBus.BroadcastInterceptor<T> interceptor,
1568
1932
  int priority = 0,
1569
1933
  IMessageBus messageBus = null
@@ -1571,7 +1935,11 @@ namespace DxMessaging.Core
1571
1935
  where T : IBroadcastMessage
1572
1936
  {
1573
1937
  messageBus = ResolveMessageBus(messageBus);
1574
- return messageBus.RegisterBroadcastInterceptor(interceptor, priority);
1938
+ MessageBusRegistration registration = messageBus.RegisterBroadcastInterceptor(
1939
+ interceptor,
1940
+ priority
1941
+ );
1942
+ return new InterceptorDeregistration<T>(messageBus, in registration);
1575
1943
  }
1576
1944
 
1577
1945
  /// <summary>
@@ -1582,7 +1950,7 @@ namespace DxMessaging.Core
1582
1950
  /// <param name="priority">Priority to register the interceptor at (interceptors are run from low -> high priority)</param>
1583
1951
  /// <param name="messageBus">Message bus to register the interceptor on.</param>
1584
1952
  /// <returns>The de-registration action.</returns>
1585
- public Action RegisterTargetedInterceptor<T>(
1953
+ internal HandlerDeregistration RegisterTargetedInterceptor<T>(
1586
1954
  IMessageBus.TargetedInterceptor<T> interceptor,
1587
1955
  int priority = 0,
1588
1956
  IMessageBus messageBus = null
@@ -1590,7 +1958,11 @@ namespace DxMessaging.Core
1590
1958
  where T : ITargetedMessage
1591
1959
  {
1592
1960
  messageBus = ResolveMessageBus(messageBus);
1593
- return messageBus.RegisterTargetedInterceptor(interceptor, priority);
1961
+ MessageBusRegistration registration = messageBus.RegisterTargetedInterceptor(
1962
+ interceptor,
1963
+ priority
1964
+ );
1965
+ return new InterceptorDeregistration<T>(messageBus, in registration);
1594
1966
  }
1595
1967
 
1596
1968
  /// <summary>
@@ -2136,6 +2508,12 @@ namespace DxMessaging.Core
2136
2508
  // post-processor siblings). The type test doubles as a null
2137
2509
  // guard; a missing adapter would indicate a new registration
2138
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.
2139
2517
  if (entry.flatInvoker is FastHandler<T> invoker)
2140
2518
  {
2141
2519
  target[writeIndex++] = new FlatDispatchEntry<T>(this, invoker);
@@ -2273,20 +2651,32 @@ namespace DxMessaging.Core
2273
2651
  this.flatInvoker = flatInvoker;
2274
2652
  }
2275
2653
 
2654
+ // The stored handler delegate. For default Action<TMessage>
2655
+ // slots this is the dedup/refcount delegate and is not the
2656
+ // dispatch target; both bus-side flat dispatch and legacy
2657
+ // Handle*/RunHandlers dispatch go through `flatInvoker`.
2658
+ // Registration paths that pre-build the augmented invoker store
2659
+ // the RAW user handler here (so no extra augmented Action wrapper
2660
+ // is allocated); paths that adapt at registration time store the
2661
+ // augmented handler. For fast/global slots, by contrast,
2662
+ // `handler` IS the dispatched delegate.
2276
2663
  public readonly T handler;
2277
2664
  public readonly int count;
2278
2665
 
2279
- // Pre-resolved invoker consumed by the bus-side flat dispatch
2280
- // snapshot (MessageBus.BuildMessageFlatDispatch). For
2281
- // default Action<TMessage> registrations this holds a
2282
- // FastHandler<TMessage> adapter wrapping the AUGMENTED
2283
- // handler, created exactly ONCE at registration time so
2284
- // snapshot rebuilds never allocate closures. For delegate
2285
- // shapes the flat path does not consume (fast handlers, which
2286
- // already ARE the invoker, and every targeted/broadcast
2287
- // shape) this stays null. Refcount increments and decrements
2666
+ // Pre-resolved invoker consumed by bus-side flat dispatch
2667
+ // snapshots and legacy default-slot Handle*/RunHandlers
2668
+ // snapshots. For default Action<TMessage> registrations this holds the
2669
+ // diagnostics-AUGMENTED FastHandler<TMessage> closure (either a
2670
+ // standalone adapter wrapping an augmented Action, or the single
2671
+ // folded augmented closure the registration token now builds
2672
+ // directly), created exactly ONCE at registration time so
2673
+ // snapshot rebuilds never allocate closures. It -- not
2674
+ // `handler` -- is the dispatch target for default slots.
2675
+ // For delegate shapes the flat path does not consume (fast
2676
+ // handlers, which already ARE the invoker, and global accept-all
2677
+ // shapes) this stays null. Refcount increments and decrements
2288
2678
  // preserve the first registration's invoker, mirroring the
2289
- // first-augmented-handler-wins semantics of `handler`.
2679
+ // first-registration-wins semantics of `handler`.
2290
2680
  public readonly object flatInvoker;
2291
2681
  }
2292
2682
 
@@ -2304,9 +2694,12 @@ namespace DxMessaging.Core
2304
2694
  // <see cref="DxMessaging.Core.Internal.IHandlerActionCache.Reset"/>.
2305
2695
  public readonly List<T> insertionOrder = new();
2306
2696
  public readonly List<T> cache = new();
2697
+ private System.Collections.IList _flatInvokerCache;
2307
2698
  public long version;
2308
2699
  public long lastSeenVersion = -1;
2309
2700
  public long lastSeenEmissionId = -1;
2701
+ public long flatInvokerLastSeenVersion = -1;
2702
+ public long flatInvokerLastSeenEmissionId = -1;
2310
2703
 
2311
2704
  /// <summary>Monotonic version field, read-only on the interface surface.</summary>
2312
2705
  long DxMessaging.Core.Internal.IHandlerActionCache.Version
@@ -2349,13 +2742,77 @@ namespace DxMessaging.Core
2349
2742
  entries.Clear();
2350
2743
  insertionOrder.Clear();
2351
2744
  cache.Clear();
2745
+ _flatInvokerCache?.Clear();
2352
2746
  lastSeenVersion = -1;
2353
2747
  lastSeenEmissionId = -1;
2748
+ flatInvokerLastSeenVersion = -1;
2749
+ flatInvokerLastSeenEmissionId = -1;
2354
2750
  unchecked
2355
2751
  {
2356
2752
  ++version;
2357
2753
  }
2358
2754
  }
2755
+
2756
+ /// <summary>
2757
+ /// Non-generic membership probe; casts the boxed key to the
2758
+ /// cache's delegate shape. Mirrors
2759
+ /// <c>entries.ContainsKey(originalHandler)</c>.
2760
+ /// </summary>
2761
+ bool DxMessaging.Core.Internal.IHandlerActionCache.ContainsEntry(object originalHandler)
2762
+ {
2763
+ return entries.ContainsKey((T)originalHandler);
2764
+ }
2765
+
2766
+ /// <summary>Bumps <see cref="version"/> by one (the closure's top-of-body <c>version++</c>).</summary>
2767
+ void DxMessaging.Core.Internal.IHandlerActionCache.BumpVersion()
2768
+ {
2769
+ version++;
2770
+ }
2771
+
2772
+ /// <summary>
2773
+ /// Non-generic decrement-or-remove mirroring the legacy
2774
+ /// deregistration closure's refcount logic exactly.
2775
+ /// </summary>
2776
+ bool DxMessaging.Core.Internal.IHandlerActionCache.DeregisterEntry(
2777
+ object originalHandler,
2778
+ out bool wasLastForEntry
2779
+ )
2780
+ {
2781
+ wasLastForEntry = false;
2782
+ T key = (T)originalHandler;
2783
+ if (!entries.TryGetValue(key, out Entry entry))
2784
+ {
2785
+ return false;
2786
+ }
2787
+
2788
+ if (entry.count <= 1)
2789
+ {
2790
+ _ = entries.Remove(key);
2791
+ // List.Remove is O(n) over the same-priority bucket;
2792
+ // accepted (see AddHandlerPreservingPriorityKey).
2793
+ _ = insertionOrder.Remove(key);
2794
+ version++;
2795
+ wasLastForEntry = true;
2796
+ return true;
2797
+ }
2798
+
2799
+ entries[key] = new Entry(entry.handler, entry.count - 1, entry.flatInvoker);
2800
+ return true;
2801
+ }
2802
+
2803
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
2804
+ internal List<TInvoker> GetOrCreateFlatInvokerCache<TInvoker>()
2805
+ where TInvoker : class
2806
+ {
2807
+ if (_flatInvokerCache == null)
2808
+ {
2809
+ List<TInvoker> typedCache = new();
2810
+ _flatInvokerCache = typedCache;
2811
+ return typedCache;
2812
+ }
2813
+
2814
+ return (List<TInvoker>)_flatInvokerCache;
2815
+ }
2359
2816
  }
2360
2817
 
2361
2818
  /// <summary>
@@ -2386,6 +2843,161 @@ namespace DxMessaging.Core
2386
2843
 
2387
2844
  internal bool _markedForOuterRemoval;
2388
2845
 
2846
+ /// <summary>
2847
+ /// Per-handle teardown object for the typed-handler registration
2848
+ /// path, replacing the deregistration <see cref="Action"/> closure
2849
+ /// the two instance <c>AddHandlerPreservingPriorityKey</c> overloads
2850
+ /// used to build and return. All of the closure's captured locals
2851
+ /// are stored as fields; <see cref="Deregister"/> re-expresses the
2852
+ /// closure body verbatim, choosing the scalar or context cache
2853
+ /// resolution by <see cref="_keyed"/> and using the non-generic
2854
+ /// <see cref="IHandlerActionCache"/> teardown ops
2855
+ /// (<c>ContainsEntry</c> / <c>BumpVersion</c> / <c>DeregisterEntry</c>)
2856
+ /// so it needs no delegate-shape type argument.
2857
+ /// </summary>
2858
+ internal sealed class TypedHandlerDeregistration : HandlerDeregistration
2859
+ {
2860
+ // Captured state (was the closure's captures).
2861
+ private readonly IMessageBus _messageBus;
2862
+ private readonly long _resetGeneration;
2863
+ private readonly TypedSlot<T> _slot;
2864
+ private readonly long _slotVersion;
2865
+
2866
+ // Scalar path: priority -> cache. Null on the context path.
2867
+ private readonly Dictionary<int, IHandlerActionCache> _handlers;
2868
+
2869
+ // Context path: context -> (priority -> cache). Null on the scalar path.
2870
+ private readonly Dictionary<
2871
+ InstanceId,
2872
+ Dictionary<int, IHandlerActionCache>
2873
+ > _handlersByContext;
2874
+ private readonly InstanceId _context;
2875
+
2876
+ private readonly bool _keyed;
2877
+ private readonly object _originalHandler;
2878
+ private readonly int _priority;
2879
+ private readonly MessageBusRegistration _busRegistration;
2880
+
2881
+ // Scalar constructor.
2882
+ internal TypedHandlerDeregistration(
2883
+ IMessageBus messageBus,
2884
+ long resetGeneration,
2885
+ TypedSlot<T> slot,
2886
+ long slotVersion,
2887
+ Dictionary<int, IHandlerActionCache> handlers,
2888
+ object originalHandler,
2889
+ int priority,
2890
+ in MessageBusRegistration busRegistration
2891
+ )
2892
+ {
2893
+ _messageBus = messageBus;
2894
+ _resetGeneration = resetGeneration;
2895
+ _slot = slot;
2896
+ _slotVersion = slotVersion;
2897
+ _handlers = handlers;
2898
+ _handlersByContext = null;
2899
+ _context = default;
2900
+ _keyed = false;
2901
+ _originalHandler = originalHandler;
2902
+ _priority = priority;
2903
+ _busRegistration = busRegistration;
2904
+ }
2905
+
2906
+ // Context constructor.
2907
+ internal TypedHandlerDeregistration(
2908
+ IMessageBus messageBus,
2909
+ long resetGeneration,
2910
+ TypedSlot<T> slot,
2911
+ long slotVersion,
2912
+ Dictionary<InstanceId, Dictionary<int, IHandlerActionCache>> handlersByContext,
2913
+ InstanceId context,
2914
+ object originalHandler,
2915
+ int priority,
2916
+ in MessageBusRegistration busRegistration
2917
+ )
2918
+ {
2919
+ _messageBus = messageBus;
2920
+ _resetGeneration = resetGeneration;
2921
+ _slot = slot;
2922
+ _slotVersion = slotVersion;
2923
+ _handlers = null;
2924
+ _handlersByContext = handlersByContext;
2925
+ _context = context;
2926
+ _keyed = true;
2927
+ _originalHandler = originalHandler;
2928
+ _priority = priority;
2929
+ _busRegistration = busRegistration;
2930
+ }
2931
+
2932
+ internal override void Deregister()
2933
+ {
2934
+ if (
2935
+ !global::DxMessaging.Core.MessageBus.MessageBus.IsResetGenerationCurrent(
2936
+ _messageBus,
2937
+ _resetGeneration
2938
+ )
2939
+ )
2940
+ {
2941
+ return;
2942
+ }
2943
+
2944
+ if (_slot != null && _slot.version != _slotVersion)
2945
+ {
2946
+ return;
2947
+ }
2948
+
2949
+ IHandlerActionCache cache;
2950
+ if (_keyed)
2951
+ {
2952
+ if (
2953
+ !_handlersByContext.TryGetValue(
2954
+ _context,
2955
+ out Dictionary<int, IHandlerActionCache> sortedHandlers
2956
+ )
2957
+ )
2958
+ {
2959
+ return;
2960
+ }
2961
+
2962
+ if (!sortedHandlers.TryGetValue(_priority, out cache) || cache == null)
2963
+ {
2964
+ return;
2965
+ }
2966
+ }
2967
+ else
2968
+ {
2969
+ if (!_handlers.TryGetValue(_priority, out cache) || cache == null)
2970
+ {
2971
+ return;
2972
+ }
2973
+ }
2974
+
2975
+ if (!cache.ContainsEntry(_originalHandler))
2976
+ {
2977
+ return;
2978
+ }
2979
+
2980
+ cache.BumpVersion();
2981
+
2982
+ _messageBus.Deregister<T>(in _busRegistration);
2983
+ if (_slot != null)
2984
+ {
2985
+ _slot.lastTouchTicks =
2986
+ global::DxMessaging.Core.MessageBus.MessageBus.GetCurrentTouchTick(
2987
+ _messageBus
2988
+ );
2989
+ }
2990
+
2991
+ _ = cache.DeregisterEntry(_originalHandler, out bool wasLastForEntry);
2992
+ if (wasLastForEntry && _slot != null)
2993
+ {
2994
+ _slot.liveCount--;
2995
+ }
2996
+ // Deliberately keep the priority and context mappings to
2997
+ // preserve frozen snapshots for the current emission.
2998
+ }
2999
+ }
3000
+
2389
3001
  int ITypedHandlerSlotSweeper.MessageTypeIndex
2390
3002
  {
2391
3003
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -3137,11 +3749,11 @@ namespace DxMessaging.Core
3137
3749
  /// <param name="deregistration">Deregistration action for the handler.</param>
3138
3750
  /// <param name="priority">Priority at which to add the handler.</param>
3139
3751
  /// <returns>De-registration action to unregister the handler.</returns>
3140
- public Action AddTargetedHandler(
3752
+ public HandlerDeregistration AddTargetedHandler(
3141
3753
  InstanceId target,
3142
3754
  Action<T> originalHandler,
3143
3755
  Action<T> handler,
3144
- Action deregistration,
3756
+ MessageBusRegistration deregistration,
3145
3757
  int priority,
3146
3758
  IMessageBus messageBus
3147
3759
  )
@@ -3162,6 +3774,35 @@ namespace DxMessaging.Core
3162
3774
  );
3163
3775
  }
3164
3776
 
3777
+ /// <summary>
3778
+ /// Adds a TargetedHandler whose by-ref flat invoker was already built
3779
+ /// by the caller, so the default slot stores a single closure instead
3780
+ /// of an <see cref="Action{T}"/> wrapper plus a separately allocated
3781
+ /// FastHandler adapter (the registration token folds diagnostics into
3782
+ /// one <see cref="FastHandler{T}"/>). <paramref name="originalHandler"/>
3783
+ /// is the dedup/identity key and is never invoked for the default slot.
3784
+ /// </summary>
3785
+ internal HandlerDeregistration AddTargetedHandler(
3786
+ InstanceId target,
3787
+ Action<T> originalHandler,
3788
+ FastHandler<T> flatInvoker,
3789
+ MessageBusRegistration deregistration,
3790
+ int priority,
3791
+ IMessageBus messageBus
3792
+ )
3793
+ {
3794
+ return AddHandlerPreservingPriorityKey(
3795
+ target,
3796
+ GetOrCreateContextHandlers(TypedSlotIndex.TargetedHandleDefault),
3797
+ originalHandler,
3798
+ originalHandler,
3799
+ deregistration,
3800
+ priority,
3801
+ messageBus,
3802
+ flatInvoker
3803
+ );
3804
+ }
3805
+
3165
3806
  /// <summary>
3166
3807
  /// Adds a fast TargetedHandler to listen to Messages of the given type, returning a deregistration action.
3167
3808
  /// </summary>
@@ -3170,11 +3811,11 @@ namespace DxMessaging.Core
3170
3811
  /// <param name="deregistration">Deregistration action for the handler.</param>
3171
3812
  /// <param name="priority">Priority at which to add the handler.</param>
3172
3813
  /// <returns>De-registration action to unregister the handler.</returns>
3173
- public Action AddTargetedHandler(
3814
+ public HandlerDeregistration AddTargetedHandler(
3174
3815
  InstanceId target,
3175
3816
  FastHandler<T> originalHandler,
3176
3817
  FastHandler<T> handler,
3177
- Action deregistration,
3818
+ MessageBusRegistration deregistration,
3178
3819
  int priority,
3179
3820
  IMessageBus messageBus
3180
3821
  )
@@ -3197,10 +3838,10 @@ namespace DxMessaging.Core
3197
3838
  /// <param name="deregistration">Deregistration action for the handler.</param>
3198
3839
  /// <param name="priority">Priority at which to add the handler.</param>
3199
3840
  /// <returns>De-registration action to unregister the handler.</returns>
3200
- public Action AddTargetedWithoutTargetingHandler(
3841
+ public HandlerDeregistration AddTargetedWithoutTargetingHandler(
3201
3842
  Action<InstanceId, T> originalHandler,
3202
3843
  Action<InstanceId, T> handler,
3203
- Action deregistration,
3844
+ MessageBusRegistration deregistration,
3204
3845
  int priority,
3205
3846
  IMessageBus messageBus
3206
3847
  )
@@ -3225,6 +3866,36 @@ namespace DxMessaging.Core
3225
3866
  );
3226
3867
  }
3227
3868
 
3869
+ /// <summary>
3870
+ /// Adds a TargetedWithoutTargetingHandler whose by-ref-with-context flat
3871
+ /// invoker was already built by the caller, so the default slot stores a
3872
+ /// single closure instead of an <see cref="Action{InstanceId, T}"/>
3873
+ /// wrapper plus a separately allocated FastHandlerWithContext adapter.
3874
+ /// <paramref name="originalHandler"/> is the dedup/identity key and is
3875
+ /// never invoked for the default slot.
3876
+ /// </summary>
3877
+ internal HandlerDeregistration AddTargetedWithoutTargetingHandler(
3878
+ Action<InstanceId, T> originalHandler,
3879
+ FastHandlerWithContext<T> flatInvoker,
3880
+ MessageBusRegistration deregistration,
3881
+ int priority,
3882
+ IMessageBus messageBus
3883
+ )
3884
+ {
3885
+ return AddHandlerPreservingPriorityKey(
3886
+ GetOrCreatePriorityHandlers(
3887
+ TypedSlotIndex.TargetedHandleWithoutContext,
3888
+ requiresContext: false
3889
+ ),
3890
+ originalHandler,
3891
+ originalHandler,
3892
+ deregistration,
3893
+ priority,
3894
+ messageBus,
3895
+ flatInvoker
3896
+ );
3897
+ }
3898
+
3228
3899
  /// <summary>
3229
3900
  /// Adds a fast TargetedWithoutTargetingHandler to listen to Messages of the given type, returning a deregistration action.
3230
3901
  /// </summary>
@@ -3232,10 +3903,10 @@ namespace DxMessaging.Core
3232
3903
  /// <param name="deregistration">Deregistration action for the handler.</param>
3233
3904
  /// <param name="priority">Priority at which to add the handler.</param>
3234
3905
  /// <returns>De-registration action to unregister the handler.</returns>
3235
- public Action AddTargetedWithoutTargetingHandler(
3906
+ public HandlerDeregistration AddTargetedWithoutTargetingHandler(
3236
3907
  FastHandlerWithContext<T> originalHandler,
3237
3908
  FastHandlerWithContext<T> handler,
3238
- Action deregistration,
3909
+ MessageBusRegistration deregistration,
3239
3910
  int priority,
3240
3911
  IMessageBus messageBus
3241
3912
  )
@@ -3260,10 +3931,10 @@ namespace DxMessaging.Core
3260
3931
  /// <param name="deregistration">Deregistration action for the handler.</param>
3261
3932
  /// <param name="priority">Priority at which to add the handler.</param>
3262
3933
  /// <returns>De-registration action to unregister the handler.</returns>
3263
- public Action AddUntargetedHandler(
3934
+ public HandlerDeregistration AddUntargetedHandler(
3264
3935
  Action<T> originalHandler,
3265
3936
  Action<T> handler,
3266
- Action deregistration,
3937
+ MessageBusRegistration deregistration,
3267
3938
  int priority,
3268
3939
  IMessageBus messageBus
3269
3940
  )
@@ -3286,6 +3957,38 @@ namespace DxMessaging.Core
3286
3957
  );
3287
3958
  }
3288
3959
 
3960
+ /// <summary>
3961
+ /// Adds an UntargetedHandler whose by-ref flat invoker was already
3962
+ /// built by the caller, so the default slot stores a single closure
3963
+ /// instead of an <see cref="Action{T}"/> wrapper plus a separately
3964
+ /// allocated FastHandler adapter. The registration token uses this to
3965
+ /// fold diagnostics into one <see cref="FastHandler{T}"/>.
3966
+ /// <paramref name="originalHandler"/> is the dedup/identity key; it is
3967
+ /// stored as the entry handler but is never invoked for the default
3968
+ /// slot, which dispatches through <paramref name="flatInvoker"/>.
3969
+ /// </summary>
3970
+ internal HandlerDeregistration AddUntargetedHandler(
3971
+ Action<T> originalHandler,
3972
+ FastHandler<T> flatInvoker,
3973
+ MessageBusRegistration deregistration,
3974
+ int priority,
3975
+ IMessageBus messageBus
3976
+ )
3977
+ {
3978
+ return AddHandlerPreservingPriorityKey(
3979
+ GetOrCreatePriorityHandlers(
3980
+ TypedSlotIndex.UntargetedHandleDefault,
3981
+ requiresContext: false
3982
+ ),
3983
+ originalHandler,
3984
+ originalHandler,
3985
+ deregistration,
3986
+ priority,
3987
+ messageBus,
3988
+ flatInvoker
3989
+ );
3990
+ }
3991
+
3289
3992
  /// <summary>
3290
3993
  /// Adds a fast UntargetedHandler to listen to Messages of the given type, returning a deregistration action.
3291
3994
  /// </summary>
@@ -3293,10 +3996,10 @@ namespace DxMessaging.Core
3293
3996
  /// <param name="deregistration">Deregistration action for the handler.</param>
3294
3997
  /// <param name="priority">Priority at which to add the handler.</param>
3295
3998
  /// <returns>De-registration action to unregister the handler.</returns>
3296
- public Action AddUntargetedHandler(
3999
+ public HandlerDeregistration AddUntargetedHandler(
3297
4000
  FastHandler<T> originalHandler,
3298
4001
  FastHandler<T> handler,
3299
- Action deregistration,
4002
+ MessageBusRegistration deregistration,
3300
4003
  int priority,
3301
4004
  IMessageBus messageBus
3302
4005
  )
@@ -3322,11 +4025,11 @@ namespace DxMessaging.Core
3322
4025
  /// <param name="deregistration">Deregistration action for the handler.</param>
3323
4026
  /// <param name="priority">Priority at which to add the handler.</param>
3324
4027
  /// <returns>De-registration action to unregister the handler.</returns>
3325
- public Action AddSourcedBroadcastHandler(
4028
+ public HandlerDeregistration AddSourcedBroadcastHandler(
3326
4029
  InstanceId source,
3327
4030
  Action<T> originalHandler,
3328
4031
  Action<T> handler,
3329
- Action deregistration,
4032
+ MessageBusRegistration deregistration,
3330
4033
  int priority,
3331
4034
  IMessageBus messageBus
3332
4035
  )
@@ -3347,6 +4050,34 @@ namespace DxMessaging.Core
3347
4050
  );
3348
4051
  }
3349
4052
 
4053
+ /// <summary>
4054
+ /// Adds a SourcedBroadcastHandler whose by-ref flat invoker was already
4055
+ /// built by the caller, so the default slot stores a single closure
4056
+ /// instead of an <see cref="Action{T}"/> wrapper plus a separately
4057
+ /// allocated FastHandler adapter. <paramref name="originalHandler"/> is
4058
+ /// the dedup/identity key and is never invoked for the default slot.
4059
+ /// </summary>
4060
+ internal HandlerDeregistration AddSourcedBroadcastHandler(
4061
+ InstanceId source,
4062
+ Action<T> originalHandler,
4063
+ FastHandler<T> flatInvoker,
4064
+ MessageBusRegistration deregistration,
4065
+ int priority,
4066
+ IMessageBus messageBus
4067
+ )
4068
+ {
4069
+ return AddHandlerPreservingPriorityKey(
4070
+ source,
4071
+ GetOrCreateContextHandlers(TypedSlotIndex.BroadcastHandleDefault),
4072
+ originalHandler,
4073
+ originalHandler,
4074
+ deregistration,
4075
+ priority,
4076
+ messageBus,
4077
+ flatInvoker
4078
+ );
4079
+ }
4080
+
3350
4081
  /// <summary>
3351
4082
  /// Adds a fast SourcedBroadcastHandler to listen to Messages of the given type from an entity, returning a deregistration action.
3352
4083
  /// </summary>
@@ -3355,11 +4086,11 @@ namespace DxMessaging.Core
3355
4086
  /// <param name="deregistration">Deregistration action for the handler.</param>
3356
4087
  /// <param name="priority">Priority at which to add the handler.</param>
3357
4088
  /// <returns>De-registration action to unregister the handler.</returns>
3358
- public Action AddSourcedBroadcastHandler(
4089
+ public HandlerDeregistration AddSourcedBroadcastHandler(
3359
4090
  InstanceId source,
3360
4091
  FastHandler<T> originalHandler,
3361
4092
  FastHandler<T> handler,
3362
- Action deregistration,
4093
+ MessageBusRegistration deregistration,
3363
4094
  int priority,
3364
4095
  IMessageBus messageBus
3365
4096
  )
@@ -3382,10 +4113,10 @@ namespace DxMessaging.Core
3382
4113
  /// <param name="deregistration">Deregistration action for the handler.</param>
3383
4114
  /// <param name="priority">Priority at which to add the handler.</param>
3384
4115
  /// <returns>De-registration action to unregister the handler.</returns>
3385
- public Action AddSourcedBroadcastWithoutSourceHandler(
4116
+ public HandlerDeregistration AddSourcedBroadcastWithoutSourceHandler(
3386
4117
  Action<InstanceId, T> originalHandler,
3387
4118
  Action<InstanceId, T> handler,
3388
- Action deregistration,
4119
+ MessageBusRegistration deregistration,
3389
4120
  int priority,
3390
4121
  IMessageBus messageBus
3391
4122
  )
@@ -3411,6 +4142,37 @@ namespace DxMessaging.Core
3411
4142
  );
3412
4143
  }
3413
4144
 
4145
+ /// <summary>
4146
+ /// Adds a SourcedBroadcastWithoutSourceHandler whose by-ref-with-context
4147
+ /// flat invoker was already built by the caller, so the default slot
4148
+ /// stores a single closure instead of an
4149
+ /// <see cref="Action{InstanceId, T}"/> wrapper plus a separately
4150
+ /// allocated FastHandlerWithContext adapter.
4151
+ /// <paramref name="originalHandler"/> is the dedup/identity key and is
4152
+ /// never invoked for the default slot.
4153
+ /// </summary>
4154
+ internal HandlerDeregistration AddSourcedBroadcastWithoutSourceHandler(
4155
+ Action<InstanceId, T> originalHandler,
4156
+ FastHandlerWithContext<T> flatInvoker,
4157
+ MessageBusRegistration deregistration,
4158
+ int priority,
4159
+ IMessageBus messageBus
4160
+ )
4161
+ {
4162
+ return AddHandlerPreservingPriorityKey(
4163
+ GetOrCreatePriorityHandlers(
4164
+ TypedSlotIndex.BroadcastHandleWithoutContext,
4165
+ requiresContext: false
4166
+ ),
4167
+ originalHandler,
4168
+ originalHandler,
4169
+ deregistration,
4170
+ priority,
4171
+ messageBus,
4172
+ flatInvoker
4173
+ );
4174
+ }
4175
+
3414
4176
  /// <summary>
3415
4177
  /// Adds a fast SourcedBroadcastWithoutSourceHandler to listen to Messages of the given type from an entity, returning a deregistration action.
3416
4178
  /// </summary>
@@ -3418,10 +4180,10 @@ namespace DxMessaging.Core
3418
4180
  /// <param name="deregistration">Deregistration action for the handler.</param>
3419
4181
  /// <param name="priority">Priority at which to add the handler.</param>
3420
4182
  /// <returns>De-registration action to unregister the handler.</returns>
3421
- public Action AddSourcedBroadcastWithoutSourceHandler(
4183
+ public HandlerDeregistration AddSourcedBroadcastWithoutSourceHandler(
3422
4184
  FastHandlerWithContext<T> originalHandler,
3423
4185
  FastHandlerWithContext<T> handler,
3424
- Action deregistration,
4186
+ MessageBusRegistration deregistration,
3425
4187
  int priority,
3426
4188
  IMessageBus messageBus
3427
4189
  )
@@ -3579,10 +4341,10 @@ namespace DxMessaging.Core
3579
4341
  /// <param name="deregistration">Deregistration action for the handler.</param>
3580
4342
  /// <param name="priority">Priority at which to add the handler.</param>
3581
4343
  /// <returns>De-registration action to unregister the handler.</returns>
3582
- public Action AddUntargetedPostProcessor(
4344
+ public HandlerDeregistration AddUntargetedPostProcessor(
3583
4345
  Action<T> originalHandler,
3584
4346
  Action<T> handler,
3585
- Action deregistration,
4347
+ MessageBusRegistration deregistration,
3586
4348
  int priority,
3587
4349
  IMessageBus messageBus
3588
4350
  )
@@ -3612,10 +4374,10 @@ namespace DxMessaging.Core
3612
4374
  /// <param name="deregistration">Deregistration action for the handler.</param>
3613
4375
  /// <param name="priority">Priority at which to add the handler.</param>
3614
4376
  /// <returns>De-registration action to unregister the handler.</returns>
3615
- public Action AddUntargetedPostProcessor(
4377
+ public HandlerDeregistration AddUntargetedPostProcessor(
3616
4378
  FastHandler<T> originalHandler,
3617
4379
  FastHandler<T> handler,
3618
- Action deregistration,
4380
+ MessageBusRegistration deregistration,
3619
4381
  int priority,
3620
4382
  IMessageBus messageBus
3621
4383
  )
@@ -3641,11 +4403,11 @@ namespace DxMessaging.Core
3641
4403
  /// <param name="deregistration">Deregistration action for the handler.</param>
3642
4404
  /// <param name="priority">Priority at which to add the handler.</param>
3643
4405
  /// <returns>De-registration action to unregister the handler.</returns>
3644
- public Action AddTargetedPostProcessor(
4406
+ public HandlerDeregistration AddTargetedPostProcessor(
3645
4407
  InstanceId target,
3646
4408
  Action<T> originalHandler,
3647
4409
  Action<T> handler,
3648
- Action deregistration,
4410
+ MessageBusRegistration deregistration,
3649
4411
  int priority,
3650
4412
  IMessageBus messageBus
3651
4413
  )
@@ -3666,6 +4428,35 @@ namespace DxMessaging.Core
3666
4428
  );
3667
4429
  }
3668
4430
 
4431
+ /// <summary>
4432
+ /// Adds a Targeted post-processor whose by-ref flat invoker was already
4433
+ /// built by the caller (the registration token folds diagnostics into one
4434
+ /// <see cref="FastHandler{T}"/>), so the default slot stores a single
4435
+ /// closure instead of an <see cref="Action{T}"/> wrapper plus a separately
4436
+ /// allocated FastHandler adapter. <paramref name="originalHandler"/> is the
4437
+ /// dedup/identity key and is never invoked for the default slot.
4438
+ /// </summary>
4439
+ internal HandlerDeregistration AddTargetedPostProcessor(
4440
+ InstanceId target,
4441
+ Action<T> originalHandler,
4442
+ FastHandler<T> flatInvoker,
4443
+ MessageBusRegistration deregistration,
4444
+ int priority,
4445
+ IMessageBus messageBus
4446
+ )
4447
+ {
4448
+ return AddHandlerPreservingPriorityKey(
4449
+ target,
4450
+ GetOrCreateContextHandlers(TypedSlotIndex.TargetedPostProcessDefault),
4451
+ originalHandler,
4452
+ originalHandler,
4453
+ deregistration,
4454
+ priority,
4455
+ messageBus,
4456
+ flatInvoker
4457
+ );
4458
+ }
4459
+
3669
4460
  /// <summary>
3670
4461
  /// Adds a Targeted post-processor to be called after all other handlers have been called.
3671
4462
  /// </summary>
@@ -3674,11 +4465,11 @@ namespace DxMessaging.Core
3674
4465
  /// <param name="deregistration">Deregistration action for the handler.</param>
3675
4466
  /// <param name="priority">Priority at which to add the handler.</param>
3676
4467
  /// <returns>De-registration action to unregister the handler.</returns>
3677
- public Action AddTargetedPostProcessor(
4468
+ public HandlerDeregistration AddTargetedPostProcessor(
3678
4469
  InstanceId target,
3679
4470
  FastHandler<T> originalHandler,
3680
4471
  FastHandler<T> handler,
3681
- Action deregistration,
4472
+ MessageBusRegistration deregistration,
3682
4473
  int priority,
3683
4474
  IMessageBus messageBus
3684
4475
  )
@@ -3701,10 +4492,10 @@ namespace DxMessaging.Core
3701
4492
  /// <param name="deregistration">Deregistration action for the handler.</param>
3702
4493
  /// <param name="priority">Priority at which to add the handler.</param>
3703
4494
  /// <returns>De-registration action to unregister the handler.</returns>
3704
- public Action AddTargetedWithoutTargetingPostProcessor(
4495
+ public HandlerDeregistration AddTargetedWithoutTargetingPostProcessor(
3705
4496
  Action<InstanceId, T> originalHandler,
3706
4497
  Action<InstanceId, T> handler,
3707
- Action deregistration,
4498
+ MessageBusRegistration deregistration,
3708
4499
  int priority,
3709
4500
  IMessageBus messageBus
3710
4501
  )
@@ -3721,7 +4512,39 @@ namespace DxMessaging.Core
3721
4512
  requiresContext: false
3722
4513
  ),
3723
4514
  originalHandler,
3724
- handler,
4515
+ handler,
4516
+ deregistration,
4517
+ priority,
4518
+ messageBus,
4519
+ flatInvoker
4520
+ );
4521
+ }
4522
+
4523
+ /// <summary>
4524
+ /// Adds a Targeted (without-targeting) post-processor whose by-ref
4525
+ /// context flat invoker was already built by the caller (the registration
4526
+ /// token folds diagnostics into one
4527
+ /// <see cref="FastHandlerWithContext{T}"/>), so the default slot stores a
4528
+ /// single closure instead of an <see cref="Action{T1, T2}"/> wrapper plus a
4529
+ /// separately allocated FastHandlerWithContext adapter.
4530
+ /// <paramref name="originalHandler"/> is the dedup/identity key and is never
4531
+ /// invoked for the default slot.
4532
+ /// </summary>
4533
+ internal HandlerDeregistration AddTargetedWithoutTargetingPostProcessor(
4534
+ Action<InstanceId, T> originalHandler,
4535
+ FastHandlerWithContext<T> flatInvoker,
4536
+ MessageBusRegistration deregistration,
4537
+ int priority,
4538
+ IMessageBus messageBus
4539
+ )
4540
+ {
4541
+ return AddHandlerPreservingPriorityKey(
4542
+ GetOrCreatePriorityHandlers(
4543
+ TypedSlotIndex.TargetedPostProcessWithoutContext,
4544
+ requiresContext: false
4545
+ ),
4546
+ originalHandler,
4547
+ originalHandler,
3725
4548
  deregistration,
3726
4549
  priority,
3727
4550
  messageBus,
@@ -3736,10 +4559,10 @@ namespace DxMessaging.Core
3736
4559
  /// <param name="deregistration">Deregistration action for the handler.</param>
3737
4560
  /// <param name="priority">Priority at which to add the handler.</param>
3738
4561
  /// <returns>De-registration action to unregister the handler.</returns>
3739
- public Action AddTargetedWithoutTargetingPostProcessor(
4562
+ public HandlerDeregistration AddTargetedWithoutTargetingPostProcessor(
3740
4563
  FastHandlerWithContext<T> originalHandler,
3741
4564
  FastHandlerWithContext<T> handler,
3742
- Action deregistration,
4565
+ MessageBusRegistration deregistration,
3743
4566
  int priority,
3744
4567
  IMessageBus messageBus
3745
4568
  )
@@ -3765,11 +4588,11 @@ namespace DxMessaging.Core
3765
4588
  /// <param name="deregistration">Deregistration action for the handler.</param>
3766
4589
  /// <param name="priority">Priority at which to add the handler.</param>
3767
4590
  /// <returns>De-registration action to unregister the handler.</returns>
3768
- public Action AddBroadcastPostProcessor(
4591
+ public HandlerDeregistration AddBroadcastPostProcessor(
3769
4592
  InstanceId source,
3770
4593
  Action<T> originalHandler,
3771
4594
  Action<T> handler,
3772
- Action deregistration,
4595
+ MessageBusRegistration deregistration,
3773
4596
  int priority,
3774
4597
  IMessageBus messageBus
3775
4598
  )
@@ -3790,6 +4613,35 @@ namespace DxMessaging.Core
3790
4613
  );
3791
4614
  }
3792
4615
 
4616
+ /// <summary>
4617
+ /// Adds a Broadcast post-processor whose by-ref flat invoker was already
4618
+ /// built by the caller (the registration token folds diagnostics into one
4619
+ /// <see cref="FastHandler{T}"/>), so the default slot stores a single
4620
+ /// closure instead of an <see cref="Action{T}"/> wrapper plus a separately
4621
+ /// allocated FastHandler adapter. <paramref name="originalHandler"/> is the
4622
+ /// dedup/identity key and is never invoked for the default slot.
4623
+ /// </summary>
4624
+ internal HandlerDeregistration AddBroadcastPostProcessor(
4625
+ InstanceId source,
4626
+ Action<T> originalHandler,
4627
+ FastHandler<T> flatInvoker,
4628
+ MessageBusRegistration deregistration,
4629
+ int priority,
4630
+ IMessageBus messageBus
4631
+ )
4632
+ {
4633
+ return AddHandlerPreservingPriorityKey(
4634
+ source,
4635
+ GetOrCreateContextHandlers(TypedSlotIndex.BroadcastPostProcessDefault),
4636
+ originalHandler,
4637
+ originalHandler,
4638
+ deregistration,
4639
+ priority,
4640
+ messageBus,
4641
+ flatInvoker
4642
+ );
4643
+ }
4644
+
3793
4645
  /// <summary>
3794
4646
  /// Adds a fast Broadcast post-processor to be called after all other handlers have been called.
3795
4647
  /// </summary>
@@ -3798,11 +4650,11 @@ namespace DxMessaging.Core
3798
4650
  /// <param name="deregistration">Deregistration action for the handler.</param>
3799
4651
  /// <param name="priority">Priority at which to add the handler.</param>
3800
4652
  /// <returns>De-registration action to unregister the handler.</returns>
3801
- public Action AddBroadcastPostProcessor(
4653
+ public HandlerDeregistration AddBroadcastPostProcessor(
3802
4654
  InstanceId source,
3803
4655
  FastHandler<T> originalHandler,
3804
4656
  FastHandler<T> handler,
3805
- Action deregistration,
4657
+ MessageBusRegistration deregistration,
3806
4658
  int priority,
3807
4659
  IMessageBus messageBus
3808
4660
  )
@@ -3825,10 +4677,10 @@ namespace DxMessaging.Core
3825
4677
  /// <param name="deregistration">Deregistration action for the handler.</param>
3826
4678
  /// <param name="priority">Priority at which to add the handler.</param>
3827
4679
  /// <returns>De-registration action to unregister the handler.</returns>
3828
- public Action AddBroadcastWithoutSourcePostProcessor(
4680
+ public HandlerDeregistration AddBroadcastWithoutSourcePostProcessor(
3829
4681
  Action<InstanceId, T> originalHandler,
3830
4682
  Action<InstanceId, T> handler,
3831
- Action deregistration,
4683
+ MessageBusRegistration deregistration,
3832
4684
  int priority,
3833
4685
  IMessageBus messageBus
3834
4686
  )
@@ -3853,6 +4705,37 @@ namespace DxMessaging.Core
3853
4705
  );
3854
4706
  }
3855
4707
 
4708
+ /// <summary>
4709
+ /// Adds a Broadcast (without-source) post-processor whose by-ref context
4710
+ /// flat invoker was already built by the caller (the registration token
4711
+ /// folds diagnostics into one <see cref="FastHandlerWithContext{T}"/>), so
4712
+ /// the default slot stores a single closure instead of an
4713
+ /// <see cref="Action{T1, T2}"/> wrapper plus a separately allocated
4714
+ /// FastHandlerWithContext adapter. <paramref name="originalHandler"/> is the
4715
+ /// dedup/identity key and is never invoked for the default slot.
4716
+ /// </summary>
4717
+ internal HandlerDeregistration AddBroadcastWithoutSourcePostProcessor(
4718
+ Action<InstanceId, T> originalHandler,
4719
+ FastHandlerWithContext<T> flatInvoker,
4720
+ MessageBusRegistration deregistration,
4721
+ int priority,
4722
+ IMessageBus messageBus
4723
+ )
4724
+ {
4725
+ return AddHandlerPreservingPriorityKey(
4726
+ GetOrCreatePriorityHandlers(
4727
+ TypedSlotIndex.BroadcastPostProcessWithoutContext,
4728
+ requiresContext: false
4729
+ ),
4730
+ originalHandler,
4731
+ originalHandler,
4732
+ deregistration,
4733
+ priority,
4734
+ messageBus,
4735
+ flatInvoker
4736
+ );
4737
+ }
4738
+
3856
4739
  /// <summary>
3857
4740
  /// Adds a fast Broadcast post-processor to be called after all other handlers have been called.
3858
4741
  /// </summary>
@@ -3860,10 +4743,10 @@ namespace DxMessaging.Core
3860
4743
  /// <param name="deregistration">Deregistration action for the handler.</param>
3861
4744
  /// <param name="priority">Priority at which to add the handler.</param>
3862
4745
  /// <returns>De-registration action to unregister the handler.</returns>
3863
- public Action AddBroadcastWithoutSourcePostProcessor(
4746
+ public HandlerDeregistration AddBroadcastWithoutSourcePostProcessor(
3864
4747
  FastHandlerWithContext<T> originalHandler,
3865
4748
  FastHandlerWithContext<T> handler,
3866
- Action deregistration,
4749
+ MessageBusRegistration deregistration,
3867
4750
  int priority,
3868
4751
  IMessageBus messageBus
3869
4752
  )
@@ -3900,12 +4783,12 @@ namespace DxMessaging.Core
3900
4783
  // default-shape registrations the bus-side flat snapshot consumes
3901
4784
  // (FastHandler adapter wrapping the augmented handler); see
3902
4785
  // HandlerActionCache.Entry.flatInvoker.
3903
- private Action AddHandlerPreservingPriorityKey<TU>(
4786
+ private HandlerDeregistration AddHandlerPreservingPriorityKey<TU>(
3904
4787
  InstanceId context,
3905
4788
  Dictionary<InstanceId, Dictionary<int, IHandlerActionCache>> handlersByContext,
3906
4789
  TU originalHandler,
3907
4790
  TU augmentedHandler,
3908
- Action deregistration,
4791
+ MessageBusRegistration deregistration,
3909
4792
  int priority,
3910
4793
  IMessageBus messageBus,
3911
4794
  object flatInvoker = null
@@ -3969,98 +4852,28 @@ namespace DxMessaging.Core
3969
4852
  slot.liveCount++;
3970
4853
  }
3971
4854
 
3972
- Dictionary<
3973
- InstanceId,
3974
- Dictionary<int, IHandlerActionCache>
3975
- > localHandlersByContext = handlersByContext;
3976
- TypedSlot<T> localSlot = slot;
3977
4855
  long localSlotVersion = slot?.version ?? 0;
3978
4856
  long localResetGeneration =
3979
4857
  global::DxMessaging.Core.MessageBus.MessageBus.GetResetGeneration(messageBus);
3980
4858
 
3981
- return () =>
3982
- {
3983
- if (
3984
- !global::DxMessaging.Core.MessageBus.MessageBus.IsResetGenerationCurrent(
3985
- messageBus,
3986
- localResetGeneration
3987
- )
3988
- )
3989
- {
3990
- return;
3991
- }
3992
-
3993
- if (localSlot != null && localSlot.version != localSlotVersion)
3994
- {
3995
- return;
3996
- }
3997
-
3998
- if (!localHandlersByContext.TryGetValue(context, out sortedHandlers))
3999
- {
4000
- return;
4001
- }
4002
-
4003
- if (
4004
- !sortedHandlers.TryGetValue(
4005
- priority,
4006
- out IHandlerActionCache localErasedCache
4007
- ) || localErasedCache is not HandlerActionCache<TU> localCache
4008
- )
4009
- {
4010
- return;
4011
- }
4012
-
4013
- if (
4014
- !localCache.entries.TryGetValue(
4015
- originalHandler,
4016
- out HandlerActionCache<TU>.Entry localEntry
4017
- )
4018
- )
4019
- {
4020
- return;
4021
- }
4022
-
4023
- localCache.version++;
4024
-
4025
- deregistration?.Invoke();
4026
- if (localSlot != null)
4027
- {
4028
- localSlot.lastTouchTicks =
4029
- global::DxMessaging.Core.MessageBus.MessageBus.GetCurrentTouchTick(
4030
- messageBus
4031
- );
4032
- }
4033
-
4034
- if (localEntry.count <= 1)
4035
- {
4036
- _ = localCache.entries.Remove(originalHandler);
4037
- // List.Remove is O(n) over the same-priority bucket.
4038
- // Accepted tradeoff (here and at every sibling
4039
- // deregistration site): buckets are small in practice,
4040
- // removal is a cold churn path, and the list keeps
4041
- // steady-state dispatch allocation-free while
4042
- // preserving first-registration order, unlike
4043
- // Dictionary enumeration whose freed slots are reused
4044
- // LIFO.
4045
- _ = localCache.insertionOrder.Remove(originalHandler);
4046
- localCache.version++;
4047
- if (localSlot != null)
4048
- {
4049
- localSlot.liveCount--;
4050
- }
4051
- // Deliberately keep the priority and context mappings to preserve
4052
- // frozen snapshots for the current emission.
4053
- return;
4054
- }
4055
-
4056
- localEntry = new HandlerActionCache<TU>.Entry(
4057
- localEntry.handler,
4058
- localEntry.count - 1,
4059
- localEntry.flatInvoker
4060
- );
4061
-
4062
- localCache.entries[originalHandler] = localEntry;
4063
- };
4859
+ // The per-handle teardown object re-expresses the old
4860
+ // deregistration closure verbatim (generation + slot-version
4861
+ // guards, cache resolution, version bumps, refcount
4862
+ // decrement-or-remove, liveCount, keep-key-during-emission)
4863
+ // using the non-generic IHandlerActionCache teardown ops, so it
4864
+ // carries no TU type argument and replaces the closure's display
4865
+ // class + delegate with this single object.
4866
+ return new TypedHandlerDeregistration(
4867
+ messageBus,
4868
+ localResetGeneration,
4869
+ slot,
4870
+ localSlotVersion,
4871
+ handlersByContext,
4872
+ context,
4873
+ originalHandler,
4874
+ priority,
4875
+ in deregistration
4876
+ );
4064
4877
  }
4065
4878
 
4066
4879
  private static Action AddHandlerPreservingPriorityKey<TU>(
@@ -4946,91 +5759,94 @@ namespace DxMessaging.Core
4946
5759
  return;
4947
5760
  }
4948
5761
 
4949
- List<Action<T>> handlers = GetOrAddNewHandlerStack(cache, emissionId);
5762
+ List<FastHandler<T>> handlers = GetOrAddNewFlatInvokerStack<
5763
+ Action<T>,
5764
+ FastHandler<T>
5765
+ >(cache, emissionId);
4950
5766
  ref T typedMessage = ref DxUnsafe.As<TMessage, T>(ref message);
4951
5767
  int handlersCount = handlers.Count;
4952
5768
  switch (handlersCount)
4953
5769
  {
4954
5770
  case 1:
4955
5771
  {
4956
- handlers[0](typedMessage);
5772
+ handlers[0](ref typedMessage);
4957
5773
  return;
4958
5774
  }
4959
5775
  case 2:
4960
5776
  {
4961
- handlers[0](typedMessage);
5777
+ handlers[0](ref typedMessage);
4962
5778
  if (handlers.Count < 2)
4963
5779
  {
4964
5780
  return;
4965
5781
  }
4966
- handlers[1](typedMessage);
5782
+ handlers[1](ref typedMessage);
4967
5783
  return;
4968
5784
  }
4969
5785
  case 3:
4970
5786
  {
4971
- handlers[0](typedMessage);
5787
+ handlers[0](ref typedMessage);
4972
5788
  if (handlers.Count < 2)
4973
5789
  {
4974
5790
  return;
4975
5791
  }
4976
- handlers[1](typedMessage);
5792
+ handlers[1](ref typedMessage);
4977
5793
  if (handlers.Count < 3)
4978
5794
  {
4979
5795
  return;
4980
5796
  }
4981
- handlers[2](typedMessage);
5797
+ handlers[2](ref typedMessage);
4982
5798
  return;
4983
5799
  }
4984
5800
  case 4:
4985
5801
  {
4986
- handlers[0](typedMessage);
5802
+ handlers[0](ref typedMessage);
4987
5803
  if (handlers.Count < 2)
4988
5804
  {
4989
5805
  return;
4990
5806
  }
4991
- handlers[1](typedMessage);
5807
+ handlers[1](ref typedMessage);
4992
5808
  if (handlers.Count < 3)
4993
5809
  {
4994
5810
  return;
4995
5811
  }
4996
- handlers[2](typedMessage);
5812
+ handlers[2](ref typedMessage);
4997
5813
  if (handlers.Count < 4)
4998
5814
  {
4999
5815
  return;
5000
5816
  }
5001
- handlers[3](typedMessage);
5817
+ handlers[3](ref typedMessage);
5002
5818
  return;
5003
5819
  }
5004
5820
  case 5:
5005
5821
  {
5006
- handlers[0](typedMessage);
5822
+ handlers[0](ref typedMessage);
5007
5823
  if (handlers.Count < 2)
5008
5824
  {
5009
5825
  return;
5010
5826
  }
5011
- handlers[1](typedMessage);
5827
+ handlers[1](ref typedMessage);
5012
5828
  if (handlers.Count < 3)
5013
5829
  {
5014
5830
  return;
5015
5831
  }
5016
- handlers[2](typedMessage);
5832
+ handlers[2](ref typedMessage);
5017
5833
  if (handlers.Count < 4)
5018
5834
  {
5019
5835
  return;
5020
5836
  }
5021
- handlers[3](typedMessage);
5837
+ handlers[3](ref typedMessage);
5022
5838
  if (handlers.Count < 5)
5023
5839
  {
5024
5840
  return;
5025
5841
  }
5026
- handlers[4](typedMessage);
5842
+ handlers[4](ref typedMessage);
5027
5843
  return;
5028
5844
  }
5029
5845
  }
5030
5846
 
5031
5847
  for (int i = 0; i < handlersCount && i < handlers.Count; ++i)
5032
5848
  {
5033
- handlers[i](typedMessage);
5849
+ handlers[i](ref typedMessage);
5034
5850
  }
5035
5851
  }
5036
5852
 
@@ -5052,91 +5868,94 @@ namespace DxMessaging.Core
5052
5868
  return;
5053
5869
  }
5054
5870
 
5055
- List<Action<T>> handlers = GetOrAddNewHandlerStack(cache, emissionId);
5871
+ List<FastHandler<T>> handlers = GetOrAddNewFlatInvokerStack<
5872
+ Action<T>,
5873
+ FastHandler<T>
5874
+ >(cache, emissionId);
5056
5875
  ref T typedMessage = ref DxUnsafe.As<TMessage, T>(ref message);
5057
5876
  int handlersCount = handlers.Count;
5058
5877
  switch (handlersCount)
5059
5878
  {
5060
5879
  case 1:
5061
5880
  {
5062
- handlers[0](typedMessage);
5881
+ handlers[0](ref typedMessage);
5063
5882
  return;
5064
5883
  }
5065
5884
  case 2:
5066
5885
  {
5067
- handlers[0](typedMessage);
5886
+ handlers[0](ref typedMessage);
5068
5887
  if (handlers.Count < 2)
5069
5888
  {
5070
5889
  return;
5071
5890
  }
5072
- handlers[1](typedMessage);
5891
+ handlers[1](ref typedMessage);
5073
5892
  return;
5074
5893
  }
5075
5894
  case 3:
5076
5895
  {
5077
- handlers[0](typedMessage);
5896
+ handlers[0](ref typedMessage);
5078
5897
  if (handlers.Count < 2)
5079
5898
  {
5080
5899
  return;
5081
5900
  }
5082
- handlers[1](typedMessage);
5901
+ handlers[1](ref typedMessage);
5083
5902
  if (handlers.Count < 3)
5084
5903
  {
5085
5904
  return;
5086
5905
  }
5087
- handlers[2](typedMessage);
5906
+ handlers[2](ref typedMessage);
5088
5907
  return;
5089
5908
  }
5090
5909
  case 4:
5091
5910
  {
5092
- handlers[0](typedMessage);
5911
+ handlers[0](ref typedMessage);
5093
5912
  if (handlers.Count < 2)
5094
5913
  {
5095
5914
  return;
5096
5915
  }
5097
- handlers[1](typedMessage);
5916
+ handlers[1](ref typedMessage);
5098
5917
  if (handlers.Count < 3)
5099
5918
  {
5100
5919
  return;
5101
5920
  }
5102
- handlers[2](typedMessage);
5921
+ handlers[2](ref typedMessage);
5103
5922
  if (handlers.Count < 4)
5104
5923
  {
5105
5924
  return;
5106
5925
  }
5107
- handlers[3](typedMessage);
5926
+ handlers[3](ref typedMessage);
5108
5927
  return;
5109
5928
  }
5110
5929
  case 5:
5111
5930
  {
5112
- handlers[0](typedMessage);
5931
+ handlers[0](ref typedMessage);
5113
5932
  if (handlers.Count < 2)
5114
5933
  {
5115
5934
  return;
5116
5935
  }
5117
- handlers[1](typedMessage);
5936
+ handlers[1](ref typedMessage);
5118
5937
  if (handlers.Count < 3)
5119
5938
  {
5120
5939
  return;
5121
5940
  }
5122
- handlers[2](typedMessage);
5941
+ handlers[2](ref typedMessage);
5123
5942
  if (handlers.Count < 4)
5124
5943
  {
5125
5944
  return;
5126
5945
  }
5127
- handlers[3](typedMessage);
5946
+ handlers[3](ref typedMessage);
5128
5947
  if (handlers.Count < 5)
5129
5948
  {
5130
5949
  return;
5131
5950
  }
5132
- handlers[4](typedMessage);
5951
+ handlers[4](ref typedMessage);
5133
5952
  return;
5134
5953
  }
5135
5954
  }
5136
5955
 
5137
5956
  for (int i = 0; i < handlersCount && i < handlers.Count; ++i)
5138
5957
  {
5139
- handlers[i](typedMessage);
5958
+ handlers[i](ref typedMessage);
5140
5959
  }
5141
5960
  }
5142
5961
 
@@ -5162,94 +5981,94 @@ namespace DxMessaging.Core
5162
5981
  return;
5163
5982
  }
5164
5983
 
5165
- List<Action<InstanceId, T>> typedHandlers = GetOrAddNewHandlerStack(
5166
- cache,
5167
- emissionId
5168
- );
5984
+ List<FastHandlerWithContext<T>> typedHandlers = GetOrAddNewFlatInvokerStack<
5985
+ Action<InstanceId, T>,
5986
+ FastHandlerWithContext<T>
5987
+ >(cache, emissionId);
5169
5988
  ref T typedMessage = ref DxUnsafe.As<TMessage, T>(ref message);
5170
5989
  int handlersCount = typedHandlers.Count;
5171
5990
  switch (handlersCount)
5172
5991
  {
5173
5992
  case 1:
5174
5993
  {
5175
- typedHandlers[0](context, typedMessage);
5994
+ typedHandlers[0](ref context, ref typedMessage);
5176
5995
  return;
5177
5996
  }
5178
5997
  case 2:
5179
5998
  {
5180
- typedHandlers[0](context, typedMessage);
5999
+ typedHandlers[0](ref context, ref typedMessage);
5181
6000
  if (typedHandlers.Count < 2)
5182
6001
  {
5183
6002
  return;
5184
6003
  }
5185
- typedHandlers[1](context, typedMessage);
6004
+ typedHandlers[1](ref context, ref typedMessage);
5186
6005
  return;
5187
6006
  }
5188
6007
  case 3:
5189
6008
  {
5190
- typedHandlers[0](context, typedMessage);
6009
+ typedHandlers[0](ref context, ref typedMessage);
5191
6010
  if (typedHandlers.Count < 2)
5192
6011
  {
5193
6012
  return;
5194
6013
  }
5195
- typedHandlers[1](context, typedMessage);
6014
+ typedHandlers[1](ref context, ref typedMessage);
5196
6015
  if (typedHandlers.Count < 3)
5197
6016
  {
5198
6017
  return;
5199
6018
  }
5200
- typedHandlers[2](context, typedMessage);
6019
+ typedHandlers[2](ref context, ref typedMessage);
5201
6020
  return;
5202
6021
  }
5203
6022
  case 4:
5204
6023
  {
5205
- typedHandlers[0](context, typedMessage);
6024
+ typedHandlers[0](ref context, ref typedMessage);
5206
6025
  if (typedHandlers.Count < 2)
5207
6026
  {
5208
6027
  return;
5209
6028
  }
5210
- typedHandlers[1](context, typedMessage);
6029
+ typedHandlers[1](ref context, ref typedMessage);
5211
6030
  if (typedHandlers.Count < 3)
5212
6031
  {
5213
6032
  return;
5214
6033
  }
5215
- typedHandlers[2](context, typedMessage);
6034
+ typedHandlers[2](ref context, ref typedMessage);
5216
6035
  if (typedHandlers.Count < 4)
5217
6036
  {
5218
6037
  return;
5219
6038
  }
5220
- typedHandlers[3](context, typedMessage);
6039
+ typedHandlers[3](ref context, ref typedMessage);
5221
6040
  return;
5222
6041
  }
5223
6042
  case 5:
5224
6043
  {
5225
- typedHandlers[0](context, typedMessage);
6044
+ typedHandlers[0](ref context, ref typedMessage);
5226
6045
  if (typedHandlers.Count < 2)
5227
6046
  {
5228
6047
  return;
5229
6048
  }
5230
- typedHandlers[1](context, typedMessage);
6049
+ typedHandlers[1](ref context, ref typedMessage);
5231
6050
  if (typedHandlers.Count < 3)
5232
6051
  {
5233
6052
  return;
5234
6053
  }
5235
- typedHandlers[2](context, typedMessage);
6054
+ typedHandlers[2](ref context, ref typedMessage);
5236
6055
  if (typedHandlers.Count < 4)
5237
6056
  {
5238
6057
  return;
5239
6058
  }
5240
- typedHandlers[3](context, typedMessage);
6059
+ typedHandlers[3](ref context, ref typedMessage);
5241
6060
  if (typedHandlers.Count < 5)
5242
6061
  {
5243
6062
  return;
5244
6063
  }
5245
- typedHandlers[4](context, typedMessage);
6064
+ typedHandlers[4](ref context, ref typedMessage);
5246
6065
  return;
5247
6066
  }
5248
6067
  }
5249
6068
 
5250
6069
  for (int i = 0; i < handlersCount && i < typedHandlers.Count; ++i)
5251
6070
  {
5252
- typedHandlers[i](context, typedMessage);
6071
+ typedHandlers[i](ref context, ref typedMessage);
5253
6072
  }
5254
6073
  }
5255
6074
 
@@ -5277,94 +6096,94 @@ namespace DxMessaging.Core
5277
6096
  return;
5278
6097
  }
5279
6098
 
5280
- List<Action<InstanceId, T>> typedHandlers = GetOrAddNewHandlerStack(
5281
- cache,
5282
- emissionId
5283
- );
6099
+ List<FastHandlerWithContext<T>> typedHandlers = GetOrAddNewFlatInvokerStack<
6100
+ Action<InstanceId, T>,
6101
+ FastHandlerWithContext<T>
6102
+ >(cache, emissionId);
5284
6103
  ref T typedMessage = ref DxUnsafe.As<TMessage, T>(ref message);
5285
6104
  int handlersCount = typedHandlers.Count;
5286
6105
  switch (handlersCount)
5287
6106
  {
5288
6107
  case 1:
5289
6108
  {
5290
- typedHandlers[0](context, typedMessage);
6109
+ typedHandlers[0](ref context, ref typedMessage);
5291
6110
  return;
5292
6111
  }
5293
6112
  case 2:
5294
6113
  {
5295
- typedHandlers[0](context, typedMessage);
6114
+ typedHandlers[0](ref context, ref typedMessage);
5296
6115
  if (typedHandlers.Count < 2)
5297
6116
  {
5298
6117
  return;
5299
6118
  }
5300
- typedHandlers[1](context, typedMessage);
6119
+ typedHandlers[1](ref context, ref typedMessage);
5301
6120
  return;
5302
6121
  }
5303
6122
  case 3:
5304
6123
  {
5305
- typedHandlers[0](context, typedMessage);
6124
+ typedHandlers[0](ref context, ref typedMessage);
5306
6125
  if (typedHandlers.Count < 2)
5307
6126
  {
5308
6127
  return;
5309
6128
  }
5310
- typedHandlers[1](context, typedMessage);
6129
+ typedHandlers[1](ref context, ref typedMessage);
5311
6130
  if (typedHandlers.Count < 3)
5312
6131
  {
5313
6132
  return;
5314
6133
  }
5315
- typedHandlers[2](context, typedMessage);
6134
+ typedHandlers[2](ref context, ref typedMessage);
5316
6135
  return;
5317
6136
  }
5318
6137
  case 4:
5319
6138
  {
5320
- typedHandlers[0](context, typedMessage);
6139
+ typedHandlers[0](ref context, ref typedMessage);
5321
6140
  if (typedHandlers.Count < 2)
5322
6141
  {
5323
6142
  return;
5324
6143
  }
5325
- typedHandlers[1](context, typedMessage);
6144
+ typedHandlers[1](ref context, ref typedMessage);
5326
6145
  if (typedHandlers.Count < 3)
5327
6146
  {
5328
6147
  return;
5329
6148
  }
5330
- typedHandlers[2](context, typedMessage);
6149
+ typedHandlers[2](ref context, ref typedMessage);
5331
6150
  if (typedHandlers.Count < 4)
5332
6151
  {
5333
6152
  return;
5334
6153
  }
5335
- typedHandlers[3](context, typedMessage);
6154
+ typedHandlers[3](ref context, ref typedMessage);
5336
6155
  return;
5337
6156
  }
5338
6157
  case 5:
5339
6158
  {
5340
- typedHandlers[0](context, typedMessage);
6159
+ typedHandlers[0](ref context, ref typedMessage);
5341
6160
  if (typedHandlers.Count < 2)
5342
6161
  {
5343
6162
  return;
5344
6163
  }
5345
- typedHandlers[1](context, typedMessage);
6164
+ typedHandlers[1](ref context, ref typedMessage);
5346
6165
  if (typedHandlers.Count < 3)
5347
6166
  {
5348
6167
  return;
5349
6168
  }
5350
- typedHandlers[2](context, typedMessage);
6169
+ typedHandlers[2](ref context, ref typedMessage);
5351
6170
  if (typedHandlers.Count < 4)
5352
6171
  {
5353
6172
  return;
5354
6173
  }
5355
- typedHandlers[3](context, typedMessage);
6174
+ typedHandlers[3](ref context, ref typedMessage);
5356
6175
  if (typedHandlers.Count < 5)
5357
6176
  {
5358
6177
  return;
5359
6178
  }
5360
- typedHandlers[4](context, typedMessage);
6179
+ typedHandlers[4](ref context, ref typedMessage);
5361
6180
  return;
5362
6181
  }
5363
6182
  }
5364
6183
 
5365
6184
  for (int i = 0; i < handlersCount && i < typedHandlers.Count; ++i)
5366
6185
  {
5367
- typedHandlers[i](context, typedMessage);
6186
+ typedHandlers[i](ref context, ref typedMessage);
5368
6187
  }
5369
6188
  }
5370
6189
 
@@ -5419,6 +6238,63 @@ namespace DxMessaging.Core
5419
6238
  return actionCache.cache;
5420
6239
  }
5421
6240
 
6241
+ // Default-slot registrations may store the raw user Action as the
6242
+ // identity key while carrying diagnostics in Entry.flatInvoker. The
6243
+ // legacy Handle* path must dispatch that same flat invoker snapshot,
6244
+ // not Entry.handler, so diagnostics semantics match bus-side flat
6245
+ // dispatch if the legacy callback path is used directly.
6246
+ internal static List<TInvoker> GetOrAddNewFlatInvokerStack<TU, TInvoker>(
6247
+ HandlerActionCache<TU> actionCache,
6248
+ long emissionId
6249
+ )
6250
+ where TInvoker : class
6251
+ {
6252
+ DebugAssertInsertionOrderInSync(actionCache);
6253
+ if (actionCache.flatInvokerLastSeenEmissionId != emissionId)
6254
+ {
6255
+ if (actionCache.version != actionCache.flatInvokerLastSeenVersion)
6256
+ {
6257
+ List<TInvoker> list = actionCache.GetOrCreateFlatInvokerCache<TInvoker>();
6258
+ list.Clear();
6259
+ List<TU> orderedHandlers = actionCache.insertionOrder;
6260
+ int orderedCount = orderedHandlers.Count;
6261
+ for (int i = 0; i < orderedCount; ++i)
6262
+ {
6263
+ if (
6264
+ !actionCache.entries.TryGetValue(
6265
+ orderedHandlers[i],
6266
+ out HandlerActionCache<TU>.Entry entry
6267
+ )
6268
+ )
6269
+ {
6270
+ continue;
6271
+ }
6272
+
6273
+ if (entry.flatInvoker is TInvoker invoker)
6274
+ {
6275
+ list.Add(invoker);
6276
+ }
6277
+ else
6278
+ {
6279
+ System.Diagnostics.Debug.Assert(
6280
+ false,
6281
+ "Default registration is missing the flat invoker required "
6282
+ + "by legacy Handle* dispatch. Every default Add* path must "
6283
+ + "store the diagnostics-augmented flat invoker in "
6284
+ + "HandlerActionCache.Entry.flatInvoker."
6285
+ );
6286
+ }
6287
+ }
6288
+
6289
+ actionCache.flatInvokerLastSeenVersion = actionCache.version;
6290
+ }
6291
+
6292
+ actionCache.flatInvokerLastSeenEmissionId = emissionId;
6293
+ }
6294
+
6295
+ return actionCache.GetOrCreateFlatInvokerCache<TInvoker>();
6296
+ }
6297
+
5422
6298
  // Asserts insertionOrder stays in lockstep with the entries
5423
6299
  // dictionary at every dispatch-snapshot read. Drift indicates a
5424
6300
  // mutation site of HandlerActionCache.entries that forgot to
@@ -5810,11 +6686,11 @@ namespace DxMessaging.Core
5810
6686
  // `flatInvoker` carries the pre-resolved flat-dispatch invoker for
5811
6687
  // registrations the bus-side flat snapshot consumes (untargeted
5812
6688
  // handle/post default handlers); see HandlerActionCache.Entry.flatInvoker.
5813
- private Action AddHandlerPreservingPriorityKey<TU>(
6689
+ private HandlerDeregistration AddHandlerPreservingPriorityKey<TU>(
5814
6690
  Dictionary<int, IHandlerActionCache> handlers,
5815
6691
  TU originalHandler,
5816
6692
  TU augmentedHandler,
5817
- Action deregistration,
6693
+ MessageBusRegistration deregistration,
5818
6694
  int priority,
5819
6695
  IMessageBus messageBus,
5820
6696
  object flatInvoker = null
@@ -5871,82 +6747,22 @@ namespace DxMessaging.Core
5871
6747
  slot.liveCount++;
5872
6748
  }
5873
6749
 
5874
- Dictionary<int, IHandlerActionCache> localHandlers = handlers;
5875
- TypedSlot<T> localSlot = slot;
5876
6750
  long localSlotVersion = slot?.version ?? 0;
5877
6751
  long localResetGeneration =
5878
6752
  global::DxMessaging.Core.MessageBus.MessageBus.GetResetGeneration(messageBus);
5879
6753
 
5880
- return () =>
5881
- {
5882
- if (
5883
- !global::DxMessaging.Core.MessageBus.MessageBus.IsResetGenerationCurrent(
5884
- messageBus,
5885
- localResetGeneration
5886
- )
5887
- )
5888
- {
5889
- return;
5890
- }
5891
-
5892
- if (localSlot != null && localSlot.version != localSlotVersion)
5893
- {
5894
- return;
5895
- }
5896
-
5897
- if (
5898
- !localHandlers.TryGetValue(
5899
- priority,
5900
- out IHandlerActionCache localErasedCache
5901
- ) || localErasedCache is not HandlerActionCache<TU> localCache
5902
- )
5903
- {
5904
- return;
5905
- }
5906
-
5907
- if (
5908
- !localCache.entries.TryGetValue(
5909
- originalHandler,
5910
- out HandlerActionCache<TU>.Entry localEntry
5911
- )
5912
- )
5913
- {
5914
- return;
5915
- }
5916
-
5917
- localCache.version++;
5918
-
5919
- deregistration?.Invoke();
5920
- if (localSlot != null)
5921
- {
5922
- localSlot.lastTouchTicks =
5923
- global::DxMessaging.Core.MessageBus.MessageBus.GetCurrentTouchTick(
5924
- messageBus
5925
- );
5926
- }
5927
-
5928
- if (localEntry.count <= 1)
5929
- {
5930
- _ = localCache.entries.Remove(originalHandler);
5931
- _ = localCache.insertionOrder.Remove(originalHandler);
5932
- localCache.version++;
5933
- if (localSlot != null)
5934
- {
5935
- localSlot.liveCount--;
5936
- }
5937
- // Intentionally DO NOT remove the priority key here to preserve
5938
- // the cache handle during an in-flight emission.
5939
- return;
5940
- }
5941
-
5942
- localEntry = new HandlerActionCache<TU>.Entry(
5943
- localEntry.handler,
5944
- localEntry.count - 1,
5945
- localEntry.flatInvoker
5946
- );
5947
-
5948
- localCache.entries[originalHandler] = localEntry;
5949
- };
6754
+ // See the context overload: this re-expresses the old scalar
6755
+ // deregistration closure verbatim as a per-handle object.
6756
+ return new TypedHandlerDeregistration(
6757
+ messageBus,
6758
+ localResetGeneration,
6759
+ slot,
6760
+ localSlotVersion,
6761
+ handlers,
6762
+ originalHandler,
6763
+ priority,
6764
+ in deregistration
6765
+ );
5950
6766
  }
5951
6767
 
5952
6768
  private static Action AddHandlerPreservingPriorityKey<TU>(