com.wallstop-studios.dxmessaging 2.0.0-rc27.3.1 → 2.1.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 (185) hide show
  1. package/.github/workflows/format-on-demand.yml +2 -2
  2. package/.github/workflows/json-format-check.yml +1 -1
  3. package/.github/workflows/markdown-json.yml +1 -1
  4. package/.github/workflows/markdownlint.yml +1 -1
  5. package/.github/workflows/npm-publish.yml +1 -1
  6. package/.github/workflows/prettier-autofix.yml +2 -2
  7. package/.github/workflows/yaml-format-lint.yml +1 -1
  8. package/Docs/Advanced.md +26 -1
  9. package/Docs/Comparisons.md +1352 -141
  10. package/Docs/Compatibility.md +27 -0
  11. package/Docs/DesignAndArchitecture.md +41 -34
  12. package/Docs/EmitShorthands.md +34 -0
  13. package/Docs/GettingStarted.md +84 -17
  14. package/Docs/Helpers.md +1 -1
  15. package/Docs/Index.md +28 -25
  16. package/Docs/Install.md +29 -6
  17. package/Docs/Integrations/Reflex.md +292 -0
  18. package/Docs/Integrations/Reflex.md.meta +7 -0
  19. package/Docs/Integrations/VContainer.md +324 -0
  20. package/Docs/Integrations/VContainer.md.meta +7 -0
  21. package/Docs/Integrations/Zenject.md +333 -0
  22. package/Docs/Integrations/Zenject.md.meta +7 -0
  23. package/{Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.pdb.meta → Docs/Integrations.meta} +2 -1
  24. package/Docs/InterceptorsAndOrdering.md +371 -17
  25. package/Docs/ListeningPatterns.md +206 -0
  26. package/Docs/MessageBusProviders.md +496 -0
  27. package/Docs/MessageBusProviders.md.meta +7 -0
  28. package/Docs/MessageTypes.md +27 -0
  29. package/Docs/MigrationGuide.md +45 -0
  30. package/Docs/Overview.md +114 -20
  31. package/Docs/Patterns.md +327 -2
  32. package/Docs/Performance.md +9 -9
  33. package/Docs/QuickReference.md +31 -0
  34. package/Docs/RuntimeConfiguration.md +407 -0
  35. package/Docs/RuntimeConfiguration.md.meta +7 -0
  36. package/Docs/UnityIntegration.md +3 -1
  37. package/Docs/VisualGuide.md +281 -167
  38. package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  39. package/Editor/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +3 -3
  40. package/Editor/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
  41. package/Editor/Analyzers/Microsoft.CodeAnalysis.dll.meta +2 -2
  42. package/Editor/Analyzers/System.Collections.Immutable.dll +0 -0
  43. package/Editor/Analyzers/System.Reflection.Metadata.dll +0 -0
  44. package/Editor/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
  45. package/Editor/CustomEditors/MessagingComponentEditor.cs +15 -6
  46. package/README.md +388 -67
  47. package/Runtime/AssemblyInfo.cs +4 -0
  48. package/Runtime/Core/Extensions/MessageBusExtensions.cs +253 -0
  49. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +12 -0
  50. package/Runtime/Core/Extensions/MessageExtensions.cs +137 -89
  51. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs +23 -0
  52. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +11 -0
  53. package/Runtime/Core/MessageBus/IMessageBusProvider.cs +14 -0
  54. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +11 -0
  55. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +18 -0
  56. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +11 -0
  57. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs +26 -0
  58. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +11 -0
  59. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +383 -0
  60. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +11 -0
  61. package/Runtime/Core/MessageHandler.cs +198 -27
  62. package/Runtime/Core/MessageRegistrationToken.cs +67 -25
  63. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs +31 -0
  64. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +12 -0
  65. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs +38 -0
  66. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +12 -0
  67. package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs +11 -0
  68. package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +3 -0
  69. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +73 -0
  70. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +11 -0
  71. package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef +20 -0
  72. package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef.meta +7 -0
  73. package/Runtime/Unity/Integrations/Reflex.meta +8 -0
  74. package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs +11 -0
  75. package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +3 -0
  76. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +46 -0
  77. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +11 -0
  78. package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef +30 -0
  79. package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef.meta +7 -0
  80. package/Runtime/Unity/Integrations/VContainer.meta +8 -0
  81. package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs +11 -0
  82. package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +3 -0
  83. package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef +30 -0
  84. package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef.meta +7 -0
  85. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +55 -0
  86. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +11 -0
  87. package/Runtime/Unity/Integrations/Zenject.meta +8 -0
  88. package/Runtime/Unity/Integrations.meta +8 -0
  89. package/Runtime/Unity/MessageAwareComponent.cs +102 -0
  90. package/Runtime/Unity/MessageBusProviderHandle.cs +97 -0
  91. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +12 -0
  92. package/Runtime/Unity/MessagingComponent.cs +164 -2
  93. package/Runtime/Unity/MessagingComponentInstaller.cs +120 -0
  94. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +12 -0
  95. package/Runtime/Unity/ScriptableMessageBusProvider.cs +14 -0
  96. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +12 -0
  97. package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab +98 -0
  98. package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab.meta +7 -0
  99. package/Samples~/DI/Prefabs.meta +8 -0
  100. package/Samples~/DI/Providers/GlobalMessageBusProvider.asset +14 -0
  101. package/Samples~/DI/Providers/GlobalMessageBusProvider.asset.meta +8 -0
  102. package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset +14 -0
  103. package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset.meta +8 -0
  104. package/Samples~/DI/Providers.meta +8 -0
  105. package/Samples~/DI/README.md +51 -0
  106. package/Samples~/DI/README.md.meta +7 -0
  107. package/Samples~/DI/Reflex/SampleInstaller.cs +75 -0
  108. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +11 -0
  109. package/Samples~/DI/Reflex.meta +8 -0
  110. package/Samples~/DI/VContainer/SampleLifetimeScope.cs +81 -0
  111. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +11 -0
  112. package/Samples~/DI/VContainer.meta +8 -0
  113. package/Samples~/DI/Zenject/SampleInstaller.cs +67 -0
  114. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +11 -0
  115. package/Samples~/DI/Zenject.meta +8 -0
  116. package/Samples~/DI.meta +8 -0
  117. package/Samples~/Mini Combat/README.md +86 -28
  118. package/Samples~/Mini Combat/Walkthrough.md +41 -25
  119. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs +12 -2
  120. package/Samples~/UI Buttons + Inspector/README.md +55 -12
  121. package/Samples~/UI Buttons + Inspector/README.md.meta +7 -0
  122. package/Tests/Runtime/Benchmarks/BenchmarkSession.cs +444 -0
  123. package/Tests/Runtime/Benchmarks/BenchmarkSession.cs.meta +11 -0
  124. package/Tests/Runtime/Benchmarks/BenchmarkTestBase.cs +94 -0
  125. package/Tests/Runtime/Benchmarks/BenchmarkTestBase.cs.meta +11 -0
  126. package/Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs +395 -0
  127. package/Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs.meta +11 -0
  128. package/Tests/Runtime/Benchmarks/PerformanceTests.cs +77 -429
  129. package/Tests/Runtime/Benchmarks/ProviderResolutionBenchmarks.cs +142 -0
  130. package/Tests/Runtime/Benchmarks/ProviderResolutionBenchmarks.cs.meta +12 -0
  131. package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef +50 -0
  132. package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef.meta +7 -0
  133. package/Tests/Runtime/Core/DefaultBusFallbackTests.cs +333 -0
  134. package/Tests/Runtime/Core/DefaultBusFallbackTests.cs.meta +11 -0
  135. package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs +278 -0
  136. package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs.meta +11 -0
  137. package/Tests/Runtime/Core/Extensions/MessageExtensionsProviderTests.cs +289 -0
  138. package/Tests/Runtime/Core/Extensions/MessageExtensionsProviderTests.cs.meta +11 -0
  139. package/Tests/Runtime/Core/Extensions.meta +8 -0
  140. package/Tests/Runtime/Core/IntegrationShimSmokeTests.cs +57 -0
  141. package/Tests/Runtime/Core/IntegrationShimSmokeTests.cs.meta +11 -0
  142. package/Tests/Runtime/Core/MessageHandlerGlobalBusTests.cs +219 -0
  143. package/Tests/Runtime/Core/MessageHandlerGlobalBusTests.cs.meta +11 -0
  144. package/Tests/Runtime/Core/MessageRegistrationBuilderTests.cs +204 -0
  145. package/Tests/Runtime/Core/MessageRegistrationBuilderTests.cs.meta +11 -0
  146. package/Tests/Runtime/Core/MessagingTestBase.cs +4 -4
  147. package/Tests/Runtime/Core/NominalTests.cs +2 -2
  148. package/Tests/Runtime/Core/SourceGeneratorNestedTests.cs +1 -1
  149. package/Tests/Runtime/Core/TypedShorthandTests.cs +2 -2
  150. package/Tests/Runtime/Core/UntargetedEquivalenceTests.cs +1 -1
  151. package/Tests/Runtime/Core/UntargetedPrefreezeTests.cs +2 -4
  152. package/Tests/Runtime/Integrations/Reflex/ReflexIntegrationTests.cs +162 -0
  153. package/Tests/Runtime/Integrations/Reflex/ReflexIntegrationTests.cs.meta +11 -0
  154. package/Tests/Runtime/Integrations/Reflex/Resources/ReflexSettings.asset +16 -0
  155. package/Tests/Runtime/Integrations/Reflex/Resources/ReflexSettings.asset.meta +8 -0
  156. package/Tests/Runtime/Integrations/Reflex/Resources.meta +8 -0
  157. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef +27 -0
  158. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef.meta +7 -0
  159. package/Tests/Runtime/Integrations/Reflex.meta +8 -0
  160. package/Tests/Runtime/Integrations/VContainer/VContainerIntegrationTests.cs +140 -0
  161. package/Tests/Runtime/Integrations/VContainer/VContainerIntegrationTests.cs.meta +11 -0
  162. package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef +37 -0
  163. package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef.meta +7 -0
  164. package/Tests/Runtime/Integrations/VContainer.meta +8 -0
  165. package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef +37 -0
  166. package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef.meta +7 -0
  167. package/Tests/Runtime/Integrations/Zenject/ZenjectIntegrationTests.cs +140 -0
  168. package/Tests/Runtime/Integrations/Zenject/ZenjectIntegrationTests.cs.meta +11 -0
  169. package/Tests/Runtime/Integrations/Zenject.meta +8 -0
  170. package/Tests/Runtime/Integrations.meta +8 -0
  171. package/Tests/Runtime/Scripts/Components/EmptyMessageAwareComponent.cs +1 -1
  172. package/Tests/Runtime/Scripts/Components/GenericMessageAwareComponent.cs +1 -1
  173. package/Tests/Runtime/Scripts/Components/SimpleMessageAwareComponent.cs +1 -1
  174. package/Tests/Runtime/TestUtilities/UnityFixtureBase.cs +64 -0
  175. package/Tests/Runtime/TestUtilities/UnityFixtureBase.cs.meta +12 -0
  176. package/Tests/Runtime/TestUtilities.meta +9 -0
  177. package/Tests/Runtime/Unity/MessageBusProviderAssetTests.cs +57 -0
  178. package/Tests/Runtime/Unity/MessageBusProviderAssetTests.cs.meta +11 -0
  179. package/Tests/Runtime/Unity/MessageBusProviderHandleTests.cs +107 -0
  180. package/Tests/Runtime/Unity/MessageBusProviderHandleTests.cs.meta +12 -0
  181. package/Tests/Runtime/Unity/MessagingComponentProviderIntegrationTests.cs +210 -0
  182. package/Tests/Runtime/Unity/MessagingComponentProviderIntegrationTests.cs.meta +12 -0
  183. package/Tests/Runtime/Unity.meta +9 -0
  184. package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.asmdef +3 -1
  185. package/package.json +1 -1
@@ -82,28 +82,52 @@ Notes on handler groups
82
82
  Visual overview
83
83
 
84
84
  ```mermaid
85
- flowchart TD
86
- subgraph Untargeted
87
- U1[Interceptors<T>] --> U2[Global Accept‑All Untargeted]
88
- U2 --> U3[Handlers<T>]
89
- U3 --> U4[Post‑Processors<T>]
85
+ flowchart LR
86
+ subgraph Untargeted["<b>Untargeted Messages</b>"]
87
+ direction TB
88
+ U1["Interceptors&lt;T&gt;"] --> U2[Global Accept‑All Untargeted]
89
+ U2 --> U3["Handlers&lt;T&gt;"]
90
+ U3 --> U4["Post‑Processors&lt;T&gt;"]
90
91
  end
91
92
 
92
- subgraph Targeted
93
- T1[Interceptors<T>] --> T2[Global Accept‑All Targeted]
94
- T2 --> T3[Handlers<T> @ target]
95
- T3 --> T4[Handlers<T> (All Targets)]
96
- T4 --> T5[Post‑Processors<T> @ target]
97
- T5 --> T6[Post‑Processors<T> (All Targets)]
93
+ subgraph Targeted["<b>Targeted Messages</b>"]
94
+ direction TB
95
+ T1["Interceptors&lt;T&gt;"] --> T2[Global Accept‑All Targeted]
96
+ T2 --> T3["Handlers&lt;T&gt; @ target"]
97
+ T3 --> T4["Handlers&lt;T&gt; (All Targets)"]
98
+ T4 --> T5["Post‑Processors&lt;T&gt; @ target"]
99
+ T5 --> T6["Post‑Processors&lt;T&gt; (All Targets)"]
98
100
  end
99
101
 
100
- subgraph Broadcast
101
- B1[Interceptors<T>] --> B2[Global Accept‑All Broadcast]
102
- B2 --> B3[Handlers<T> @ source]
103
- B3 --> B4[Handlers<T> (All Sources)]
104
- B4 --> B5[Post‑Processors<T> @ source]
105
- B5 --> B6[Post‑Processors<T> (All Sources)]
102
+ subgraph Broadcast["<b>Broadcast Messages</b>"]
103
+ direction TB
104
+ B1["Interceptors&lt;T&gt;"] --> B2[Global Accept‑All Broadcast]
105
+ B2 --> B3["Handlers&lt;T&gt; @ source"]
106
+ B3 --> B4["Handlers&lt;T&gt; (All Sources)"]
107
+ B4 --> B5["Post‑Processors&lt;T&gt; @ source"]
108
+ B5 --> B6["Post‑Processors&lt;T&gt; (All Sources)"]
106
109
  end
110
+
111
+ style Untargeted fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
112
+ style Targeted fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
113
+ style Broadcast fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
114
+
115
+ style U1 fill:#ffe7ba,stroke:#d48806,stroke-width:2px,color:#000
116
+ style U2 fill:#d3adf7,stroke:#531dab,stroke-width:2px,color:#000
117
+ style U3 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
118
+ style U4 fill:#95de64,stroke:#237804,stroke-width:2px,color:#000
119
+ style T1 fill:#ffe7ba,stroke:#d48806,stroke-width:2px,color:#000
120
+ style T2 fill:#d3adf7,stroke:#531dab,stroke-width:2px,color:#000
121
+ style T3 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
122
+ style T4 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
123
+ style T5 fill:#95de64,stroke:#237804,stroke-width:2px,color:#000
124
+ style T6 fill:#95de64,stroke:#237804,stroke-width:2px,color:#000
125
+ style B1 fill:#ffe7ba,stroke:#d48806,stroke-width:2px,color:#000
126
+ style B2 fill:#d3adf7,stroke:#531dab,stroke-width:2px,color:#000
127
+ style B3 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
128
+ style B4 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
129
+ style B5 fill:#95de64,stroke:#237804,stroke-width:2px,color:#000
130
+ style B6 fill:#95de64,stroke:#237804,stroke-width:2px,color:#000
107
131
  ```
108
132
 
109
133
  Example sequence
@@ -145,6 +169,336 @@ _ = bus.RegisterTargetedInterceptor<TookDamage>(
145
169
  );
146
170
  ```
147
171
 
172
+ Real‑World Use Cases
173
+
174
+ ###### State‑Based Message Cancellation
175
+
176
+ Prevent UI messages from being processed based on current UI state:
177
+
178
+ ```csharp
179
+ using DxMessaging.Core;
180
+ using DxMessaging.Core.MessageBus;
181
+
182
+ [DxUntargetedMessage]
183
+ [DxAutoConstructor]
184
+ public readonly partial struct OpenMenu
185
+ {
186
+ public readonly string menuName;
187
+ }
188
+
189
+ [DxUntargetedMessage]
190
+ [DxAutoConstructor]
191
+ public readonly partial struct ShowDialog
192
+ {
193
+ public readonly string dialogText;
194
+ }
195
+
196
+ public class UIStateManager
197
+ {
198
+ private bool _isInCutscene;
199
+ private bool _isPaused;
200
+ private bool _isLoading;
201
+
202
+ public void RegisterInterceptors()
203
+ {
204
+ var bus = MessageHandler.MessageBus;
205
+
206
+ // Block all UI interactions during cutscenes, loading, or when paused
207
+ _ = bus.RegisterUntargetedInterceptor<OpenMenu>(
208
+ (ref OpenMenu m) => !_isInCutscene && !_isLoading,
209
+ priority: -100 // Run early
210
+ );
211
+
212
+ _ = bus.RegisterUntargetedInterceptor<ShowDialog>(
213
+ (ref ShowDialog m) => !_isInCutscene && !_isPaused,
214
+ priority: -100
215
+ );
216
+ }
217
+
218
+ public void EnterCutscene() => _isInCutscene = true;
219
+ public void ExitCutscene() => _isInCutscene = false;
220
+ }
221
+
222
+ // Usage: UI messages automatically blocked during cutscenes
223
+ var uiManager = new UIStateManager();
224
+ uiManager.RegisterInterceptors();
225
+ uiManager.EnterCutscene();
226
+
227
+ new OpenMenu("inventory").Emit(); // Cancelled by interceptor
228
+ new ShowDialog("Hello!").Emit(); // Cancelled by interceptor
229
+ ```
230
+
231
+ ###### Value Clamping and Normalization
232
+
233
+ Ensure message data stays within valid ranges:
234
+
235
+ ```csharp
236
+ using DxMessaging.Core;
237
+ using DxMessaging.Core.MessageBus;
238
+ using UnityEngine;
239
+
240
+ [DxUntargetedMessage]
241
+ [DxAutoConstructor]
242
+ public readonly partial struct MovementInput
243
+ {
244
+ public readonly Vector2 direction;
245
+ public readonly float speed;
246
+ }
247
+
248
+ public class InputNormalizer
249
+ {
250
+ public void RegisterInterceptors()
251
+ {
252
+ var bus = MessageHandler.MessageBus;
253
+
254
+ // Normalize and clamp movement input
255
+ _ = bus.RegisterUntargetedInterceptor<MovementInput>(
256
+ (ref MovementInput m) =>
257
+ {
258
+ // Normalize direction vector
259
+ var normalized = m.direction.normalized;
260
+
261
+ // Clamp speed to valid range
262
+ var clampedSpeed = Mathf.Clamp(m.speed, 0f, 10f);
263
+
264
+ // Mutate the message with cleaned values
265
+ m = new MovementInput(normalized, clampedSpeed);
266
+ return true;
267
+ },
268
+ priority: 0
269
+ );
270
+ }
271
+ }
272
+
273
+ // Usage: all movement input is automatically normalized
274
+ var normalizer = new InputNormalizer();
275
+ normalizer.RegisterInterceptors();
276
+
277
+ // Even invalid input gets cleaned
278
+ new MovementInput(new Vector2(100, 200), 9999f).Emit();
279
+ // Handlers receive: direction=(0.45, 0.89), speed=10.0
280
+ ```
281
+
282
+ ###### Permission and Authorization Checks
283
+
284
+ Block messages that violate game rules or permissions:
285
+
286
+ ```csharp
287
+ using DxMessaging.Core;
288
+ using DxMessaging.Core.MessageBus;
289
+
290
+ [DxTargetedMessage]
291
+ [DxAutoConstructor]
292
+ public readonly partial struct SpendCurrency
293
+ {
294
+ public readonly int amount;
295
+ }
296
+
297
+ [DxTargetedMessage]
298
+ [DxAutoConstructor]
299
+ public readonly partial struct UnlockAchievement
300
+ {
301
+ public readonly string achievementId;
302
+ }
303
+
304
+ public class PermissionSystem
305
+ {
306
+ private readonly IPlayerDataService _playerData;
307
+
308
+ public PermissionSystem(IPlayerDataService playerData)
309
+ {
310
+ _playerData = playerData;
311
+ }
312
+
313
+ public void RegisterInterceptors()
314
+ {
315
+ var bus = MessageHandler.MessageBus;
316
+
317
+ // Block currency spending if player doesn't have enough
318
+ _ = bus.RegisterTargetedInterceptor<SpendCurrency>(
319
+ (ref InstanceId playerId, ref SpendCurrency m) =>
320
+ {
321
+ var balance = _playerData.GetCurrencyBalance(playerId);
322
+ if (balance < m.amount)
323
+ {
324
+ Debug.LogWarning($"Player {playerId} attempted to spend {m.amount} but only has {balance}");
325
+ return false; // Cancel the message
326
+ }
327
+ return true;
328
+ },
329
+ priority: -50
330
+ );
331
+
332
+ // Block achievement unlocks if already unlocked (idempotency)
333
+ _ = bus.RegisterTargetedInterceptor<UnlockAchievement>(
334
+ (ref InstanceId playerId, ref UnlockAchievement m) =>
335
+ {
336
+ if (_playerData.HasAchievement(playerId, m.achievementId))
337
+ {
338
+ return false; // Already unlocked, skip
339
+ }
340
+ return true;
341
+ },
342
+ priority: -50
343
+ );
344
+ }
345
+ }
346
+
347
+ // Usage: invalid operations automatically blocked
348
+ var permissions = new PermissionSystem(playerDataService);
349
+ permissions.RegisterInterceptors();
350
+
351
+ // This will be blocked if player doesn't have 100 currency
352
+ new SpendCurrency(100).EmitTargeted(playerId);
353
+
354
+ // This will be blocked if achievement is already unlocked
355
+ new UnlockAchievement("first_boss").EmitTargeted(playerId);
356
+ ```
357
+
358
+ ###### Message Enrichment and Context Addition
359
+
360
+ Add contextual data to messages as they flow through the system:
361
+
362
+ ```csharp
363
+ using DxMessaging.Core;
364
+ using DxMessaging.Core.MessageBus;
365
+ using System;
366
+
367
+ [DxTargetedMessage]
368
+ [DxAutoConstructor]
369
+ public readonly partial struct PlayerAction
370
+ {
371
+ public readonly string actionType;
372
+ [DxOptionalParameter]
373
+ public readonly long timestamp; // Added by interceptor
374
+ [DxOptionalParameter]
375
+ public readonly string sessionId; // Added by interceptor
376
+ }
377
+
378
+ public class TelemetryEnricher
379
+ {
380
+ private readonly string _currentSessionId;
381
+
382
+ public TelemetryEnricher(string sessionId)
383
+ {
384
+ _currentSessionId = sessionId;
385
+ }
386
+
387
+ public void RegisterInterceptors()
388
+ {
389
+ var bus = MessageHandler.MessageBus;
390
+
391
+ // Enrich player actions with timestamp and session context
392
+ _ = bus.RegisterTargetedInterceptor<PlayerAction>(
393
+ (ref InstanceId playerId, ref PlayerAction m) =>
394
+ {
395
+ // Add timestamp and session ID to every action
396
+ m = new PlayerAction(
397
+ m.actionType,
398
+ DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
399
+ _currentSessionId
400
+ );
401
+ return true;
402
+ },
403
+ priority: -100 // Run very early to enrich before other interceptors
404
+ );
405
+ }
406
+ }
407
+
408
+ // Usage: messages automatically enriched with context
409
+ var enricher = new TelemetryEnricher(Guid.NewGuid().ToString());
410
+ enricher.RegisterInterceptors();
411
+
412
+ // Emit without timestamp/session - interceptor adds them
413
+ new PlayerAction("jump").EmitTargeted(playerId);
414
+ // Handlers receive fully enriched message with timestamp and sessionId
415
+ ```
416
+
417
+ ###### Cooldown and Rate Limiting
418
+
419
+ Prevent message spam by enforcing cooldowns:
420
+
421
+ ```csharp
422
+ using DxMessaging.Core;
423
+ using DxMessaging.Core.MessageBus;
424
+ using System;
425
+ using System.Collections.Generic;
426
+
427
+ [DxTargetedMessage]
428
+ [DxAutoConstructor]
429
+ public readonly partial struct CastSpell
430
+ {
431
+ public readonly string spellName;
432
+ }
433
+
434
+ public class CooldownManager
435
+ {
436
+ private readonly Dictionary<(InstanceId, string), DateTime> _lastCastTimes = new();
437
+ private readonly TimeSpan _globalCooldown = TimeSpan.FromSeconds(1.5);
438
+
439
+ public void RegisterInterceptors()
440
+ {
441
+ var bus = MessageHandler.MessageBus;
442
+
443
+ _ = bus.RegisterTargetedInterceptor<CastSpell>(
444
+ (ref InstanceId casterId, ref CastSpell m) =>
445
+ {
446
+ var key = (casterId, m.spellName);
447
+ var now = DateTime.UtcNow;
448
+
449
+ if (_lastCastTimes.TryGetValue(key, out var lastCast))
450
+ {
451
+ if (now - lastCast < _globalCooldown)
452
+ {
453
+ // Still on cooldown
454
+ return false;
455
+ }
456
+ }
457
+
458
+ _lastCastTimes[key] = now;
459
+ return true;
460
+ },
461
+ priority: -10
462
+ );
463
+ }
464
+ }
465
+
466
+ // Usage: rapid spell casts automatically throttled
467
+ var cooldowns = new CooldownManager();
468
+ cooldowns.RegisterInterceptors();
469
+
470
+ new CastSpell("fireball").EmitTargeted(playerId); // ✓ Allowed
471
+ new CastSpell("fireball").EmitTargeted(playerId); // ✗ Blocked (too soon)
472
+ // ... wait 1.5s ...
473
+ new CastSpell("fireball").EmitTargeted(playerId); // ✓ Allowed
474
+ ```
475
+
476
+ When to Use Interceptors
477
+
478
+ ✅ **Good use cases:**
479
+
480
+ - Input validation and sanitization
481
+ - Value clamping and normalization
482
+ - Permission and authorization checks
483
+ - State‑based message filtering
484
+ - Rate limiting and cooldown enforcement
485
+ - Message enrichment (adding timestamps, session IDs, etc.)
486
+ - Early exit for duplicate or redundant messages
487
+ - Logging suspicious or invalid message attempts
488
+
489
+ ⚠️ **Key principles:**
490
+
491
+ - **Run before handlers**: Interceptors execute before any type‑specific handlers, making them perfect for preprocessing
492
+ - **Can mutate**: Unlike post‑processors, interceptors can modify message data
493
+ - **Can cancel**: Return `false` to prevent the message from reaching handlers
494
+ - **Priority matters**: Lower priority values run first (use negative priorities for early interceptors)
495
+
496
+ ❌ **Avoid for:**
497
+
498
+ - Read‑only observation (use handlers or post‑processors instead)
499
+ - Actions that should run after message processing (use post‑processors)
500
+ - Heavy computation that doesn't need to block the message
501
+
148
502
  Post‑processors
149
503
 
150
504
  - Observe after handlers. Great for logging, analytics, or follow‑up emission.
@@ -47,6 +47,212 @@ var dereg = bus.RegisterGlobalAcceptAll(handler);
47
47
  // implement handler callbacks for generic categories on your MessageHandler
48
48
  ```
49
49
 
50
+ Real‑World Use Cases
51
+
52
+ ## Development Debug Dump
53
+
54
+ Capture all messages during development for debugging and diagnostics:
55
+
56
+ ```csharp
57
+ using DxMessaging.Core;
58
+ using DxMessaging.Core.Messages;
59
+ using UnityEngine;
60
+
61
+ public class DebugMessageLogger : MessageHandler
62
+ {
63
+ public DebugMessageLogger() : base(new InstanceId(999)) { }
64
+
65
+ public override void Handle(ref IUntargetedMessage message)
66
+ {
67
+ Debug.Log($"[Untargeted] {message.GetType().Name}: {message}");
68
+ }
69
+
70
+ public override void Handle(ref InstanceId target, ref ITargetedMessage message)
71
+ {
72
+ Debug.Log($"[Targeted → {target}] {message.GetType().Name}: {message}");
73
+ }
74
+
75
+ public override void Handle(ref InstanceId source, ref IBroadcastMessage message)
76
+ {
77
+ Debug.Log($"[Broadcast ← {source}] {message.GetType().Name}: {message}");
78
+ }
79
+ }
80
+
81
+ // Register in development builds only
82
+ #if DEVELOPMENT_BUILD || UNITY_EDITOR
83
+ var logger = new DebugMessageLogger { active = true };
84
+ _ = MessageHandler.MessageBus.RegisterGlobalAcceptAll(logger);
85
+ #endif
86
+ ```
87
+
88
+ ### Attribute‑Based Network Replication
89
+
90
+ Automatically replicate messages marked with custom attributes across the network:
91
+
92
+ ```csharp
93
+ using System;
94
+ using System.Reflection;
95
+ using System.Collections.Generic;
96
+ using DxMessaging.Core;
97
+ using DxMessaging.Core.Messages;
98
+
99
+ // Mark messages that should be replicated
100
+ [AttributeUsage(AttributeTargets.Struct)]
101
+ public class NetworkedAttribute : Attribute { }
102
+
103
+ [Networked]
104
+ [DxUntargetedMessage]
105
+ [DxAutoConstructor]
106
+ public readonly partial struct PlayerMoved
107
+ {
108
+ public readonly Vector3 position;
109
+ }
110
+
111
+ [Networked]
112
+ [DxTargetedMessage]
113
+ [DxAutoConstructor]
114
+ public readonly partial struct DealDamage
115
+ {
116
+ public readonly float amount;
117
+ }
118
+
119
+ // Network replication handler
120
+ public class NetworkReplicator : MessageHandler
121
+ {
122
+ private readonly INetworkManager _network;
123
+ private readonly HashSet<Type> _networkedTypes = new();
124
+
125
+ public NetworkReplicator(INetworkManager network) : base(new InstanceId(1000))
126
+ {
127
+ _network = network;
128
+ CacheNetworkedTypes();
129
+ }
130
+
131
+ private void CacheNetworkedTypes()
132
+ {
133
+ // Find all message types with [Networked] attribute
134
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
135
+ {
136
+ foreach (var type in assembly.GetTypes())
137
+ {
138
+ if (type.GetCustomAttribute<NetworkedAttribute>() != null)
139
+ {
140
+ _networkedTypes.Add(type);
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ public override void Handle(ref IUntargetedMessage message)
147
+ {
148
+ if (_networkedTypes.Contains(message.GetType()))
149
+ {
150
+ _network.Send(message); // Serialize and send
151
+ }
152
+ }
153
+
154
+ public override void Handle(ref InstanceId target, ref ITargetedMessage message)
155
+ {
156
+ if (_networkedTypes.Contains(message.GetType()))
157
+ {
158
+ _network.Send(target, message);
159
+ }
160
+ }
161
+
162
+ public override void Handle(ref InstanceId source, ref IBroadcastMessage message)
163
+ {
164
+ if (_networkedTypes.Contains(message.GetType()))
165
+ {
166
+ _network.Send(source, message);
167
+ }
168
+ }
169
+ }
170
+
171
+ // Usage: any message with [Networked] automatically replicates
172
+ var replicator = new NetworkReplicator(networkManager) { active = true };
173
+ _ = MessageHandler.MessageBus.RegisterGlobalAcceptAll(replicator);
174
+
175
+ // These messages now "just work" across the network
176
+ new PlayerMoved(playerPos).Emit();
177
+ new DealDamage(50f).EmitTargeted(enemyId);
178
+ ```
179
+
180
+ #### Message Analytics and Metrics
181
+
182
+ Track message frequency and performance across your entire game:
183
+
184
+ ```csharp
185
+ using System;
186
+ using System.Collections.Generic;
187
+ using System.Diagnostics;
188
+ using DxMessaging.Core;
189
+ using DxMessaging.Core.Messages;
190
+
191
+ public class MessageAnalytics : MessageHandler
192
+ {
193
+ private readonly Dictionary<Type, (int count, long totalMs)> _stats = new();
194
+ private readonly Stopwatch _stopwatch = new();
195
+
196
+ public MessageAnalytics() : base(new InstanceId(1001)) { }
197
+
198
+ public override void Handle(ref IUntargetedMessage message)
199
+ {
200
+ TrackMessage(message.GetType());
201
+ }
202
+
203
+ public override void Handle(ref InstanceId target, ref ITargetedMessage message)
204
+ {
205
+ TrackMessage(message.GetType());
206
+ }
207
+
208
+ public override void Handle(ref InstanceId source, ref IBroadcastMessage message)
209
+ {
210
+ TrackMessage(message.GetType());
211
+ }
212
+
213
+ private void TrackMessage(Type messageType)
214
+ {
215
+ _stopwatch.Restart();
216
+ // Message processing happens here
217
+ _stopwatch.Stop();
218
+
219
+ if (!_stats.TryGetValue(messageType, out var stat))
220
+ {
221
+ stat = (0, 0);
222
+ }
223
+ _stats[messageType] = (stat.count + 1, stat.totalMs + _stopwatch.ElapsedMilliseconds);
224
+ }
225
+
226
+ public void PrintStats()
227
+ {
228
+ foreach (var kvp in _stats)
229
+ {
230
+ var avg = kvp.Value.totalMs / (double)kvp.Value.count;
231
+ UnityEngine.Debug.Log($"{kvp.Key.Name}: {kvp.Value.count} messages, avg {avg:F2}ms");
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ When to Use Global Accept‑All
238
+
239
+ ✅ **Good use cases:**
240
+
241
+ - Development‑time debugging and logging
242
+ - Cross‑cutting concerns (analytics, telemetry, metrics)
243
+ - Attribute‑based systems (networking, serialization, persistence)
244
+ - Testing and diagnostics tools
245
+ - Message replay/recording systems
246
+
247
+ ⚠️ **Performance consideration:**
248
+ Global Accept‑All handlers are invoked for **every** message of **every** type. For high‑performance gameplay logic, prefer type‑specific registrations which use O(1) lookup instead of O(N) iteration.
249
+
250
+ ❌ **Avoid for:**
251
+
252
+ - Core gameplay logic that only needs specific message types
253
+ - Hot paths with thousands of messages per frame
254
+ - Production code that can use specific type registrations instead
255
+
50
256
  Tips
51
257
 
52
258
  - Use across‑all listeners for diagnostics, analytics, or cross‑cutting observers.