com.wallstop-studios.dxmessaging 2.0.0 → 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.
- package/.github/workflows/format-on-demand.yml +2 -2
- package/.github/workflows/json-format-check.yml +1 -1
- package/.github/workflows/markdown-json.yml +1 -1
- package/.github/workflows/markdownlint.yml +1 -1
- package/.github/workflows/npm-publish.yml +1 -1
- package/.github/workflows/prettier-autofix.yml +2 -2
- package/.github/workflows/yaml-format-lint.yml +1 -1
- package/Docs/Advanced.md +26 -1
- package/Docs/Comparisons.md +1229 -146
- package/Docs/Compatibility.md +27 -0
- package/Docs/DesignAndArchitecture.md +41 -34
- package/Docs/EmitShorthands.md +34 -0
- package/Docs/Helpers.md +1 -1
- package/Docs/Index.md +28 -25
- package/Docs/Install.md +29 -6
- package/Docs/Integrations/Reflex.md +292 -0
- package/Docs/Integrations/Reflex.md.meta +7 -0
- package/Docs/Integrations/VContainer.md +324 -0
- package/Docs/Integrations/VContainer.md.meta +7 -0
- package/Docs/Integrations/Zenject.md +333 -0
- package/Docs/Integrations/Zenject.md.meta +7 -0
- package/{Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.pdb.meta → Docs/Integrations.meta} +2 -1
- package/Docs/InterceptorsAndOrdering.md +371 -17
- package/Docs/ListeningPatterns.md +206 -0
- package/Docs/MessageBusProviders.md +496 -0
- package/Docs/MessageBusProviders.md.meta +7 -0
- package/Docs/MessageTypes.md +27 -0
- package/Docs/MigrationGuide.md +45 -0
- package/Docs/Patterns.md +286 -0
- package/Docs/Performance.md +9 -9
- package/Docs/QuickReference.md +31 -0
- package/Docs/RuntimeConfiguration.md +407 -0
- package/Docs/RuntimeConfiguration.md.meta +7 -0
- package/Docs/UnityIntegration.md +3 -1
- package/Docs/VisualGuide.md +206 -157
- package/Editor/CustomEditors/MessagingComponentEditor.cs +15 -6
- package/README.md +148 -26
- package/Runtime/AssemblyInfo.cs +4 -0
- package/Runtime/Core/Extensions/MessageBusExtensions.cs +253 -0
- package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +12 -0
- package/Runtime/Core/Extensions/MessageExtensions.cs +137 -89
- package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs +23 -0
- package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +11 -0
- package/Runtime/Core/MessageBus/IMessageBusProvider.cs +14 -0
- package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +11 -0
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +18 -0
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +11 -0
- package/Runtime/Core/MessageBus/MessageBusRebindMode.cs +26 -0
- package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +11 -0
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +383 -0
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +11 -0
- package/Runtime/Core/MessageHandler.cs +198 -27
- package/Runtime/Core/MessageRegistrationToken.cs +67 -25
- package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs +31 -0
- package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +12 -0
- package/Runtime/Unity/InitialGlobalMessageBusProvider.cs +38 -0
- package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +12 -0
- package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs +11 -0
- package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +3 -0
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs +73 -0
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +11 -0
- package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef +20 -0
- package/Runtime/Unity/Integrations/Reflex/WallstopStudios.DxMessaging.Reflex.asmdef.meta +7 -0
- package/Runtime/Unity/Integrations/Reflex.meta +8 -0
- package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs +11 -0
- package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +3 -0
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs +46 -0
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +11 -0
- package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef +30 -0
- package/Runtime/Unity/Integrations/VContainer/WallstopStudios.DxMessaging.VContainer.asmdef.meta +7 -0
- package/Runtime/Unity/Integrations/VContainer.meta +8 -0
- package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs +11 -0
- package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +3 -0
- package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef +30 -0
- package/Runtime/Unity/Integrations/Zenject/WallstopStudios.DxMessaging.Zenject.asmdef.meta +7 -0
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs +55 -0
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +11 -0
- package/Runtime/Unity/Integrations/Zenject.meta +8 -0
- package/Runtime/Unity/Integrations.meta +8 -0
- package/Runtime/Unity/MessageAwareComponent.cs +102 -0
- package/Runtime/Unity/MessageBusProviderHandle.cs +97 -0
- package/Runtime/Unity/MessageBusProviderHandle.cs.meta +12 -0
- package/Runtime/Unity/MessagingComponent.cs +164 -2
- package/Runtime/Unity/MessagingComponentInstaller.cs +120 -0
- package/Runtime/Unity/MessagingComponentInstaller.cs.meta +12 -0
- package/Runtime/Unity/ScriptableMessageBusProvider.cs +14 -0
- package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +12 -0
- package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab +98 -0
- package/Samples~/DI/Prefabs/MessagingInstallerSample.prefab.meta +7 -0
- package/Samples~/DI/Prefabs.meta +8 -0
- package/Samples~/DI/Providers/GlobalMessageBusProvider.asset +14 -0
- package/Samples~/DI/Providers/GlobalMessageBusProvider.asset.meta +8 -0
- package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset +14 -0
- package/Samples~/DI/Providers/InitialGlobalMessageBusProvider.asset.meta +8 -0
- package/Samples~/DI/Providers.meta +8 -0
- package/Samples~/DI/README.md +51 -0
- package/Samples~/DI/README.md.meta +7 -0
- package/Samples~/DI/Reflex/SampleInstaller.cs +75 -0
- package/Samples~/DI/Reflex/SampleInstaller.cs.meta +11 -0
- package/Samples~/DI/Reflex.meta +8 -0
- package/Samples~/DI/VContainer/SampleLifetimeScope.cs +81 -0
- package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +11 -0
- package/Samples~/DI/VContainer.meta +8 -0
- package/Samples~/DI/Zenject/SampleInstaller.cs +67 -0
- package/Samples~/DI/Zenject/SampleInstaller.cs.meta +11 -0
- package/Samples~/DI/Zenject.meta +8 -0
- package/Samples~/DI.meta +8 -0
- package/Samples~/Mini Combat/README.md +5 -7
- package/Samples~/Mini Combat/Walkthrough.md +18 -24
- package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs +12 -2
- package/Samples~/UI Buttons + Inspector/README.md.meta +7 -0
- package/Tests/Runtime/Benchmarks/BenchmarkSession.cs +444 -0
- package/Tests/Runtime/Benchmarks/BenchmarkSession.cs.meta +11 -0
- package/Tests/Runtime/Benchmarks/BenchmarkTestBase.cs +94 -0
- package/Tests/Runtime/Benchmarks/BenchmarkTestBase.cs.meta +11 -0
- package/Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs +395 -0
- package/Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs.meta +11 -0
- package/Tests/Runtime/Benchmarks/PerformanceTests.cs +77 -429
- package/Tests/Runtime/Benchmarks/ProviderResolutionBenchmarks.cs +142 -0
- package/Tests/Runtime/Benchmarks/ProviderResolutionBenchmarks.cs.meta +12 -0
- package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef +50 -0
- package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef.meta +7 -0
- package/Tests/Runtime/Core/DefaultBusFallbackTests.cs +333 -0
- package/Tests/Runtime/Core/DefaultBusFallbackTests.cs.meta +11 -0
- package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs +278 -0
- package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs.meta +11 -0
- package/Tests/Runtime/Core/Extensions/MessageExtensionsProviderTests.cs +289 -0
- package/Tests/Runtime/Core/Extensions/MessageExtensionsProviderTests.cs.meta +11 -0
- package/Tests/Runtime/Core/Extensions.meta +8 -0
- package/Tests/Runtime/Core/IntegrationShimSmokeTests.cs +57 -0
- package/Tests/Runtime/Core/IntegrationShimSmokeTests.cs.meta +11 -0
- package/Tests/Runtime/Core/MessageHandlerGlobalBusTests.cs +219 -0
- package/Tests/Runtime/Core/MessageHandlerGlobalBusTests.cs.meta +11 -0
- package/Tests/Runtime/Core/MessageRegistrationBuilderTests.cs +204 -0
- package/Tests/Runtime/Core/MessageRegistrationBuilderTests.cs.meta +11 -0
- package/Tests/Runtime/Core/MessagingTestBase.cs +4 -4
- package/Tests/Runtime/Core/NominalTests.cs +2 -2
- package/Tests/Runtime/Core/TypedShorthandTests.cs +2 -2
- package/Tests/Runtime/Core/UntargetedEquivalenceTests.cs +1 -1
- package/Tests/Runtime/Core/UntargetedPrefreezeTests.cs +2 -4
- package/Tests/Runtime/Integrations/Reflex/ReflexIntegrationTests.cs +162 -0
- package/Tests/Runtime/Integrations/Reflex/ReflexIntegrationTests.cs.meta +11 -0
- package/Tests/Runtime/Integrations/Reflex/Resources/ReflexSettings.asset +16 -0
- package/Tests/Runtime/Integrations/Reflex/Resources/ReflexSettings.asset.meta +8 -0
- package/Tests/Runtime/Integrations/Reflex/Resources.meta +8 -0
- package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef +27 -0
- package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef.meta +7 -0
- package/Tests/Runtime/Integrations/Reflex.meta +8 -0
- package/Tests/Runtime/Integrations/VContainer/VContainerIntegrationTests.cs +140 -0
- package/Tests/Runtime/Integrations/VContainer/VContainerIntegrationTests.cs.meta +11 -0
- package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef +37 -0
- package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef.meta +7 -0
- package/Tests/Runtime/Integrations/VContainer.meta +8 -0
- package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef +37 -0
- package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef.meta +7 -0
- package/Tests/Runtime/Integrations/Zenject/ZenjectIntegrationTests.cs +140 -0
- package/Tests/Runtime/Integrations/Zenject/ZenjectIntegrationTests.cs.meta +11 -0
- package/Tests/Runtime/Integrations/Zenject.meta +8 -0
- package/Tests/Runtime/Integrations.meta +8 -0
- package/Tests/Runtime/Scripts/Components/EmptyMessageAwareComponent.cs +1 -1
- package/Tests/Runtime/Scripts/Components/GenericMessageAwareComponent.cs +1 -1
- package/Tests/Runtime/Scripts/Components/SimpleMessageAwareComponent.cs +1 -1
- package/Tests/Runtime/TestUtilities/UnityFixtureBase.cs +64 -0
- package/Tests/Runtime/TestUtilities/UnityFixtureBase.cs.meta +12 -0
- package/Tests/Runtime/TestUtilities.meta +9 -0
- package/Tests/Runtime/Unity/MessageBusProviderAssetTests.cs +57 -0
- package/Tests/Runtime/Unity/MessageBusProviderAssetTests.cs.meta +11 -0
- package/Tests/Runtime/Unity/MessageBusProviderHandleTests.cs +107 -0
- package/Tests/Runtime/Unity/MessageBusProviderHandleTests.cs.meta +12 -0
- package/Tests/Runtime/Unity/MessagingComponentProviderIntegrationTests.cs +210 -0
- package/Tests/Runtime/Unity/MessagingComponentProviderIntegrationTests.cs.meta +12 -0
- package/Tests/Runtime/Unity.meta +9 -0
- package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.asmdef +3 -1
- package/package.json +1 -1
|
@@ -82,28 +82,52 @@ Notes on handler groups
|
|
|
82
82
|
Visual overview
|
|
83
83
|
|
|
84
84
|
```mermaid
|
|
85
|
-
flowchart
|
|
86
|
-
subgraph Untargeted
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
flowchart LR
|
|
86
|
+
subgraph Untargeted["<b>Untargeted Messages</b>"]
|
|
87
|
+
direction TB
|
|
88
|
+
U1["Interceptors<T>"] --> U2[Global Accept‑All Untargeted]
|
|
89
|
+
U2 --> U3["Handlers<T>"]
|
|
90
|
+
U3 --> U4["Post‑Processors<T>"]
|
|
90
91
|
end
|
|
91
92
|
|
|
92
|
-
subgraph Targeted
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
subgraph Targeted["<b>Targeted Messages</b>"]
|
|
94
|
+
direction TB
|
|
95
|
+
T1["Interceptors<T>"] --> T2[Global Accept‑All Targeted]
|
|
96
|
+
T2 --> T3["Handlers<T> @ target"]
|
|
97
|
+
T3 --> T4["Handlers<T> (All Targets)"]
|
|
98
|
+
T4 --> T5["Post‑Processors<T> @ target"]
|
|
99
|
+
T5 --> T6["Post‑Processors<T> (All Targets)"]
|
|
98
100
|
end
|
|
99
101
|
|
|
100
|
-
subgraph Broadcast
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
subgraph Broadcast["<b>Broadcast Messages</b>"]
|
|
103
|
+
direction TB
|
|
104
|
+
B1["Interceptors<T>"] --> B2[Global Accept‑All Broadcast]
|
|
105
|
+
B2 --> B3["Handlers<T> @ source"]
|
|
106
|
+
B3 --> B4["Handlers<T> (All Sources)"]
|
|
107
|
+
B4 --> B5["Post‑Processors<T> @ source"]
|
|
108
|
+
B5 --> B6["Post‑Processors<T> (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.
|