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
package/Docs/Overview.md CHANGED
@@ -4,22 +4,89 @@
4
4
 
5
5
  ---
6
6
 
7
- DxMessaging is a highperformance, typesafe messaging system for Unity and .NET that decouples producers and consumers without sprawling events or brittle global hooks. It gives you clear message categories, predictable ordering, and tooling to observe, intercept, and diagnose message flows.
7
+ DxMessaging is a high-performance, type-safe messaging system for Unity that **eliminates the three biggest pain points** of traditional event systems:
8
8
 
9
- What it solves
9
+ 1. **Memory leaks** from forgotten unsubscribes → Automatic lifecycle management
10
+ 1. **Tight coupling** creating refactoring nightmares → Full decoupling with no direct references
11
+ 1. **Debugging black holes** ("what fired when?") → Built-in Inspector diagnostics
10
12
 
11
- - Decoupling without tight references: producers don’t need to know consumers.
12
- - Predictable lifecycle: explicit registration tokens that you enable/disable with Unity components.
13
- - Ergonomics and performance: struct‑friendly APIs, by‑ref handlers, zero‑boxing patterns.
14
- - Observability: interceptors, post‑processors, diagnostics buffers, and registration logs.
15
- - Scalable message taxonomy: Untargeted, Targeted, and Broadcast messages fit most gameplay/UI flows.
13
+ ## What Problems Does It Solve?
16
14
 
17
- When to consider DxMessaging
15
+ ### For Beginners: "I'm calling methods manually everywhere"
18
16
 
19
- - You’re fighting complex webs of C# events/delegates with manual attach/detach and order issues.
20
- - You need to decouple systems that span scenes or don’t share direct references.
21
- - You want to model Gameplay/UI flows as messages with clear ownership (global vs to one entity vs from one entity).
22
- - You need to inspect, validate, and sometimes cancel or normalize emissions.
17
+ #### Your code probably looks like this
18
+
19
+ ```csharp
20
+ public class Player : MonoBehaviour {
21
+ public HealthBar healthBar;
22
+ public AudioManager audio;
23
+ public AchievementSystem achievements;
24
+
25
+ void TakeDamage(int amount) {
26
+ health -= amount;
27
+ healthBar.UpdateHealth(health); // Manual call
28
+ audio.PlayDamageSound(); // Manual call
29
+ achievements.CheckDamage(amount); // Manual call
30
+ // Add analytics? More manual calls...
31
+ }
32
+ }
33
+ ```
34
+
35
+ **Every new system = another SerializeField + another manual call.** It's exhausting and brittle.
36
+
37
+ ##### DxMessaging fixes this
38
+
39
+ ```csharp
40
+ void TakeDamage(int amount) {
41
+ health -= amount;
42
+ new TookDamage(amount).EmitComponentBroadcast(this);
43
+ // Done! Everything else reacts automatically.
44
+ }
45
+ ```
46
+
47
+ ### For Intermediate Devs: "I use C# events but they leak"
48
+
49
+ #### You know this pain
50
+
51
+ ```csharp
52
+ void OnEnable() { GameManager.OnScoreChanged += UpdateUI; }
53
+ void OnDisable() { /* Forgot this? 💀 LEAK! */ }
54
+ ```
55
+
56
+ **DxMessaging makes leaks impossible** - automatic cleanup when components die.
57
+
58
+ ### For Advanced Devs: "I need observability and control"
59
+
60
+ - ✅ See message history in Inspector (timestamps, payloads, call counts)
61
+ - ✅ Priority-based execution (no more race conditions)
62
+ - ✅ Interceptors (validate/normalize before handlers)
63
+ - ✅ Global observers (track ALL instances of a message type)
64
+ - ✅ Local bus islands (isolated testing, zero global state)
65
+
66
+ ## What It Solves (Technical)
67
+
68
+ - **Decoupling without references** - producers/consumers never know about each other
69
+ - **Predictable lifecycle** - explicit tokens tied to Unity component lifecycles
70
+ - **Performance** - struct messages passed by-ref, zero allocations, zero boxing
71
+ - **Observability** - interceptors, post-processors, diagnostics, registration logs
72
+ - **Scalable taxonomy** - three message types (Untargeted/Targeted/Broadcast) cover 99% of use cases
73
+
74
+ ## When to Consider DxMessaging
75
+
76
+ ### Use it when
77
+
78
+ - You have 3+ systems that need to communicate
79
+ - You've debugged memory leaks from forgotten unsubscribes
80
+ - You're tired of UI depending on 15 different gameplay systems
81
+ - You want to see "what fired when" without setting 50 breakpoints
82
+ - You're building for the long term (months/years, not days)
83
+
84
+ #### Skip it when
85
+
86
+ - Game jam prototype (<1 week)
87
+ - Tiny project (<1000 lines)
88
+ - Single-system communication (just call the method directly)
89
+ - You need synchronous return values (DxMessaging is fire-and-forget)
23
90
 
24
91
  Core ideas
25
92
 
@@ -31,15 +98,42 @@ Core ideas
31
98
  - Pipeline: Interceptors → Handlers → Post‑Processors, with diagnostics optionally enabled.
32
99
  - Unity integration: `MessagingComponent` and `MessageAwareComponent` manage lifecycles cleanly.
33
100
 
34
- Killer features
101
+ ## Killer Features (What Makes It Special)
102
+
103
+ ### 🚀 Global Observers: The Unique Advantage
104
+
105
+ #### Traditional event systems force you to do this
106
+
107
+ ```csharp
108
+ // Subscribe to each entity type separately
109
+ PlayerEvents.OnDamaged += TrackPlayerDamage;
110
+ EnemyEvents.OnDamaged += TrackEnemyDamage;
111
+ NPCEvents.OnDamaged += TrackNPCDamage;
112
+ // Add a new entity type? More subscriptions...
113
+ ```
114
+
115
+ ##### DxMessaging lets you do this
116
+
117
+ ```csharp
118
+ // Subscribe ONCE to ALL damage, regardless of source
119
+ _ = Token.RegisterBroadcastWithoutSource<TookDamage>(
120
+ (InstanceId source, TookDamage msg) => {
121
+ Analytics.Log($"{source} took {msg.amount} damage");
122
+ }
123
+ );
124
+ ```
125
+
126
+ **Real-world impact:** Build achievement systems, combat logs, and analytics that work with ANY entity - even ones that don't exist yet.
127
+
128
+ ### Other Killer Features
35
129
 
36
- - **🚀 Global Observers: Listen to ALL messages** — Subscribe to all targeted/broadcast messages of a type without knowing specific targets/sources. **Unlike traditional event buses** where you need separate subscriptions per entity type (PlayerDamaged, EnemyDamaged, etc.), DxMessaging lets you subscribe ONCE to ALL damage events and get the source/target as a parameter. Perfect for analytics, debugging, achievements, and combat logs.
37
- - **Priority ordering** and explicit pipeline stages (interceptors, handlers, post‑processors).
38
- - **Local bus islands** for test isolation and modular subsystems.
39
- - **Struct‑friendly, byref handlers** to avoid boxing and copies.
40
- - **Attributes + source generation** (`DxAutoConstructor`) reduce boilerplate while keeping strong typing.
41
- - **Unityfirst helpers** (GameObject/Component emit) and a powerful MessagingComponent inspector.
42
- - **Global accept‑all** for building inspectors and profilers.
130
+ - **Priority-based ordering** - eliminate race conditions, control execution flow explicitly
131
+ - **Interceptor pipeline** - validate/normalize messages BEFORE handlers run (one validation, all handlers protected)
132
+ - **Local bus islands** - isolated testing with zero global state contamination
133
+ - **Zero-allocation design** - struct messages passed by-ref, no boxing, no GC spikes
134
+ - **Auto-constructor generation** - `[DxAutoConstructor]` eliminates boilerplate while keeping type safety
135
+ - **Unity-first helpers** - `EmitGameObjectTargeted()`, `EmitComponentBroadcast()` feel natural
136
+ - **Inspector diagnostics** - see message history, registrations, call counts in real-time
43
137
 
44
138
  ---
45
139
 
package/Docs/Patterns.md CHANGED
@@ -1,10 +1,50 @@
1
- # DxMessaging Patterns
1
+ # DxMessaging Patterns: Real-World Solutions
2
2
 
3
3
  [← Back to Index](Index.md) | [Getting Started](GettingStarted.md) | [Message Types](MessageTypes.md) | [Samples](../Samples~/)
4
4
 
5
5
  ---
6
6
 
7
- This document captures practical patterns for building systems with DxMessaging. It complements the README by focusing on composition, structure, and problem-solving techniques.
7
+ **You're here because:** You understand DxMessaging basics, now you want to see "How do I actually build X?"
8
+
9
+ ## What you'll find
10
+
11
+ - **Basic Patterns** - Fundamental building blocks (scene transitions, commands, observability)
12
+ - **Advanced Patterns** - Power user techniques (diagnostics, testing, legacy integration)
13
+ - **Scale Patterns** - Production-ready examples (100+ entities, cross-scene systems, large UI)
14
+
15
+ ### Reading guide
16
+
17
+ - **New to DxMessaging?** Start with Basic Patterns 1-8
18
+ - **Intermediate user?** Jump to Advanced Patterns 9-12
19
+ - **Building at scale?** Go straight to Real-World Scale Patterns
20
+ - **Specific problem?** Use Ctrl+F / Cmd+F to search
21
+
22
+ **Philosophy:** These aren't toy examples. They're patterns from real games with real problems.
23
+
24
+ ---
25
+
26
+ ## Quick Links: "I Want To..."
27
+
28
+ ### Find your use case, jump to the pattern
29
+
30
+ | I want to... | Go to |
31
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
32
+ | Make UI react to gameplay | [Pattern 2: Directed Commands](#2-directed-commands-targeted) |
33
+ | Coordinate scene transitions | [Pattern 1: Scene-wide Events](#1-scene-wide-events-untargeted) |
34
+ | Build an achievement system | [Pattern 3: Observability](#3-observability-broadcast) + [Global Accept-All](#10-global-accept-all-handlers) |
35
+ | Validate input/damage before it happens | [Pattern 4: Interceptors](#4-validation-and-normalization-interceptors) |
36
+ | Add analytics without touching gameplay | [Pattern 5: Post-Processors](#5-analyticslogging-post-processors) |
37
+ | Track ALL damage from ANY entity | [Pattern: Managing 100+ Entities](#pattern-managing-100-combat-entities) |
38
+ | Build a large UI system (20+ panels) | [Pattern: Large-Scale UI](#pattern-large-scale-ui-system-20-panels) |
39
+ | Make systems run in a specific order | [Pattern: Priority Ordering](#pattern-priority-ordered-execution-for-complex-systems) |
40
+ | Test in isolation | [Pattern 6: Local Bus Islands](#6-local-bus-islands) |
41
+ | Migrate from C# events | [Pattern 9: Bridging Legacy](#9-bridging-legacy-unity-messaging) |
42
+ | Handle persistent systems across scene loads | [Pattern: Cross-Scene Persistent](#pattern-cross-scene-persistent-systems) |
43
+ | See what's happening (debug message flow) | [Pattern 11: Diagnostics](#11-diagnostics-and-tuning) |
44
+ | Build a battle royale / large multiplayer | [Pattern: Battle Royale Example](#real-world-production-example-battle-royale-game) |
45
+ | Use with Scriptable Object Architecture | [Pattern 14: SOA Compatibility](#14-compatibility-with-scriptable-object-architecture-soa) |
46
+
47
+ ---
8
48
 
9
49
  ## Table of Contents
10
50
 
@@ -25,6 +65,7 @@ This document captures practical patterns for building systems with DxMessaging.
25
65
  - [Global Accept-All Handlers](#10-global-accept-all-handlers)
26
66
  - [Diagnostics and Tuning](#11-diagnostics-and-tuning)
27
67
  - [Testing](#12-testing)
68
+ - [Compatibility with Scriptable Object Architecture (SOA)](#14-compatibility-with-scriptable-object-architecture-soa)
28
69
 
29
70
  ### Real-World Scale Patterns
30
71
 
@@ -729,6 +770,290 @@ public class MatchStats : MessageAwareComponent {
729
770
 
730
771
  ---
731
772
 
773
+ ## 14) Compatibility with Scriptable Object Architecture (SOA)
774
+
775
+ **Disclaimer:** Scriptable Object Architecture (SOA) is a debated pattern in the Unity community. While it has proponents, there are documented criticisms regarding its scalability and maintainability. See [Anti-ScriptableObject Architecture](https://github.com/cathei/AntiScriptableObjectArchitecture) for a detailed critique. **SOA is not a recommended pattern** for most projects, unless you need designers wiring events. Consider alternatives like dependency injection (Zenject, VContainer), reactive systems (UniRx), or messaging systems (DxMessaging, MessagePipe), as they often provide better long-term maintainability.
776
+
777
+ That said, if your project uses or requires SOA, DxMessaging can work alongside it.
778
+
779
+ ### What is Scriptable Object Architecture?
780
+
781
+ **SOA Background:** Popularized by Ryan Hipple's [Unite Austin 2017 talk](https://www.youtube.com/watch?v=raQ3iHhE_Kk), SOA uses ScriptableObject assets as:
782
+
783
+ 1. **Shared Variables** - `FloatVariable`, `IntVariable`, etc. (ScriptableObject assets that hold runtime state)
784
+ 1. **Event Channels** - `GameEvent` + `GameEventListener` pattern (designer-created events)
785
+ 1. **Runtime Sets** - Collections of active game objects (e.g., all enemies)
786
+
787
+ **Core idea:** Systems communicate through serialized SO assets instead of direct references.
788
+
789
+ #### Key resources
790
+
791
+ - [Unite 2017 Talk by Ryan Hipple](https://www.youtube.com/watch?v=raQ3iHhE_Kk)
792
+ - [Official Unity Guide](https://unity.com/how-to/architect-game-code-scriptable-objects)
793
+ - [Reference Implementation](https://github.com/roboryantron/Unite2017)
794
+ - [Community Package](https://github.com/DanielEverland/ScriptableObject-Architecture)
795
+
796
+ ### Why SOA is Controversial
797
+
798
+ From [Anti-ScriptableObject Architecture](https://github.com/cathei/AntiScriptableObjectArchitecture), key criticisms include:
799
+
800
+ 1. **Wrong Purpose** - ScriptableObjects are designed for immutable design data, not runtime mutable state
801
+ 1. **Redundant Complexity** - Standard C# objects achieve the same goals without SO restrictions
802
+ 1. **Inspector Dependency** - Binds architecture to Unity's GUI, complicating debugging and maintenance
803
+ 1. **Limited Scalability** - Runtime-created variables undermine the pattern; managing numerous assets becomes unwieldy
804
+ 1. **Domain Reload Issues** - Disabled domain reloading causes ScriptableObjects to retain values unpredictably
805
+ 1. **Testability Concerns** - SO assets persist between tests, requiring manual cleanup
806
+
807
+ #### Recommended alternatives
808
+
809
+ - **Dependency Injection** - Zenject, VContainer, Reflex (see [DxMessaging DI Integrations](Integrations/))
810
+ - **Reactive Systems** - UniRx, UniTask
811
+ - **Messaging** - DxMessaging (this framework), MessagePipe
812
+ - **Configuration Data** - Spreadsheet-based solutions (e.g., BakingSheet)
813
+
814
+ Use ScriptableObjects for their intended purpose: **immutable design-time data** (configs, balance tables, prefab references).
815
+
816
+ ### Can DxMessaging Work with SOA?
817
+
818
+ **Yes, but with caveats.** DxMessaging and SOA solve similar problems (decoupling, communication) with different philosophies:
819
+
820
+ | Aspect | SOA | DxMessaging |
821
+ | -------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------- |
822
+ | **Paradigm** | Asset-based, persistent state | Runtime message passing, transient |
823
+ | **Designer-Centric** | ✅ High (create events in Inspector) | ❌ Low (code-driven) |
824
+ | **Type Safety** | ⚠️ Mixed (SO refs typed, but UnityEvent inspector wiring loses compile-time safety) | ✅ Strong (compile-time validation) |
825
+ | **Lifecycle** | ⚠️ Manual (SO assets persist) | ✅ Automatic (tokens clean up) |
826
+ | **Debugging** | ⚠️ Inspector-dependent | ✅ Built-in diagnostics |
827
+ | **Performance** | ⚠️ List iteration, UnityAction overhead | ✅ Zero-allocation structs |
828
+ | **Use Case** | Shared state, designer-driven configs | Event-driven communication, runtime logic |
829
+ | **Testability** | ⚠️ Requires SO asset cleanup | ✅ Isolated buses per test |
830
+
831
+ **Bottom line:** If you're starting fresh, prefer DxMessaging or DI. If you have legacy SOA code, the patterns below show coexistence strategies.
832
+
833
+ ### Pattern Overview
834
+
835
+ | Pattern | What it shows | When to use | SOA involvement |
836
+ | ------- | ------------------------------------------------------ | ----------------------------------------------------- | ---------------------------- |
837
+ | **A** | SOA Events (GameEvent) forwarding to DxMessaging | Designer-created event assets, modern code downstream | ✅ Yes - SOA Event pattern |
838
+ | **B** | ScriptableObjects for configs + DxMessaging for events | New projects / best practice | ❌ No - proper SO usage only |
839
+
840
+ ### Pattern A: SOA → DxMessaging (Event Forwarding)
841
+
842
+ **Use case:** Designer-created SOA events, but you want DxMessaging benefits downstream.
843
+
844
+ **Strategy:** SOA GameEventListener forwards to DxMessaging.
845
+
846
+ > **This uses the SOA GameEvent pattern:** If you're NOT using designer-created GameEvent assets
847
+ > with `Raise()`/listener management, you don't need this pattern. For immutable config data,
848
+ > see Pattern B instead.
849
+
850
+ #### Example: Scene Transitions
851
+
852
+ ```csharp
853
+ using DxMessaging.Core.Messages;
854
+ using DxMessaging.Core.Attributes;
855
+ using DxMessaging.Core.Extensions;
856
+ using UnityEngine;
857
+ using UnityEngine.Events;
858
+
859
+ // Traditional SOA event
860
+ [CreateAssetMenu(menuName = "Events/Game Event")]
861
+ public class GameEvent : ScriptableObject
862
+ {
863
+ private readonly List<UnityAction> listeners = new();
864
+
865
+ public void Raise()
866
+ {
867
+ for (int i = listeners.Count - 1; i >= 0; i--)
868
+ listeners[i]?.Invoke();
869
+ }
870
+
871
+ public void RegisterListener(UnityAction listener) => listeners.Add(listener);
872
+ public void UnregisterListener(UnityAction listener) => listeners.Remove(listener);
873
+ }
874
+
875
+ // DxMessaging message (modern, type-safe)
876
+ [DxUntargetedMessage]
877
+ public readonly struct SceneTransitionRequested { }
878
+
879
+ // Bridge: SOA Event → DxMessaging
880
+ public class SOAEventBridge : MonoBehaviour
881
+ {
882
+ [SerializeField] private GameEvent onSceneTransitionSO; // Designer-created asset
883
+
884
+ void OnEnable()
885
+ {
886
+ onSceneTransitionSO.RegisterListener(OnSOAEvent);
887
+ }
888
+
889
+ void OnDisable()
890
+ {
891
+ onSceneTransitionSO.UnregisterListener(OnSOAEvent);
892
+ }
893
+
894
+ void OnSOAEvent()
895
+ {
896
+ // Forward to DxMessaging
897
+ var message = new SceneTransitionRequested();
898
+ message.Emit();
899
+ }
900
+ }
901
+
902
+ // Modern DxMessaging listeners (no SO dependency)
903
+ public class AudioSystem : MessageAwareComponent
904
+ {
905
+ protected override void RegisterMessageHandlers()
906
+ {
907
+ base.RegisterMessageHandlers();
908
+ _ = Token.RegisterUntargeted<SceneTransitionRequested>(OnTransition);
909
+ }
910
+
911
+ void OnTransition(ref SceneTransitionRequested msg)
912
+ {
913
+ FadeOutMusic(); // Type-safe, debuggable via DxMessaging Inspector
914
+ }
915
+ }
916
+ ```
917
+
918
+ ##### Benefits
919
+
920
+ - ✅ Designers create events in Inspector (SOA workflow preserved)
921
+ - ✅ Code uses DxMessaging (type-safe, lifecycle-safe)
922
+
923
+ ###### Drawbacks
924
+
925
+ - ⚠️ Bridge boilerplate for each SOA event
926
+ - ⚠️ Double registration (SOA listener + DxMessaging handler)
927
+
928
+ ### Pattern B: Proper ScriptableObject Usage (Recommended)
929
+
930
+ **Use case:** ScriptableObjects for immutable config data, DxMessaging for runtime events.
931
+
932
+ **Strategy:** Use each tool for its intended purpose; avoid bridging.
933
+
934
+ > **Important:** This pattern uses ScriptableObjects CORRECTLY (immutable design data),
935
+ > NOT as SOA (mutable runtime state). This is standard Unity best practice, not SOA.
936
+ > If you're only using SOs for config data like this, you don't need SOA patterns at all.
937
+
938
+ #### Example: Combat with Designer-Tunable Data
939
+
940
+ ```csharp
941
+ using DxMessaging.Core.Messages;
942
+ using DxMessaging.Core.Attributes;
943
+ using DxMessaging.Unity;
944
+ using UnityEngine;
945
+
946
+ // ScriptableObject: Designer-tunable balance data (immutable at runtime)
947
+ // This is CORRECT SO usage, NOT SOA
948
+ [CreateAssetMenu(menuName = "Config/Weapon Stats")]
949
+ public class WeaponStats : ScriptableObject
950
+ {
951
+ public int baseDamage = 10; // Designer tweaks in Inspector
952
+ public float critMultiplier = 2f;
953
+ }
954
+
955
+ // DxMessaging: Runtime event (code-driven)
956
+ [DxBroadcastMessage]
957
+ [DxAutoConstructor]
958
+ public readonly partial struct DamageDealt
959
+ {
960
+ public readonly int amount;
961
+ public readonly bool wasCrit;
962
+ }
963
+
964
+ // Combat system: Reads immutable SO data, emits DxMessaging events
965
+ public class Weapon : MessageAwareComponent
966
+ {
967
+ [SerializeField] private WeaponStats stats; // Immutable config (correct SO usage)
968
+
969
+ public void Fire()
970
+ {
971
+ bool crit = Random.value < 0.1f;
972
+ int damage = Mathf.RoundToInt(stats.baseDamage * (crit ? stats.critMultiplier : 1f));
973
+
974
+ var msg = new DamageDealt(damage, crit);
975
+ msg.EmitComponentBroadcast(this); // Runtime event via DxMessaging
976
+ }
977
+ }
978
+
979
+ // Analytics: Pure DxMessaging (no SOA dependency)
980
+ public class CombatAnalytics : MessageAwareComponent
981
+ {
982
+ protected override void RegisterMessageHandlers()
983
+ {
984
+ base.RegisterMessageHandlers();
985
+ _ = Token.RegisterBroadcastWithoutSource<DamageDealt>(OnDamage);
986
+ }
987
+
988
+ void OnDamage(InstanceId source, DamageDealt msg)
989
+ {
990
+ Debug.Log($"{source} dealt {msg.amount} damage (crit: {msg.wasCrit})");
991
+ }
992
+ }
993
+ ```
994
+
995
+ ##### Benefits
996
+
997
+ - ✅ **Best of both worlds** - ScriptableObjects for static configs, DxMessaging for runtime events
998
+ - ✅ No bridging overhead
999
+ - ✅ Uses each system correctly: SOs for their intended purpose (immutable design data), messaging for runtime communication
1000
+ - ✅ This is NOT SOA - it's proper Unity architecture
1001
+
1002
+ ###### This is the recommended pattern for all projects
1003
+
1004
+ ### When to Use Each Pattern
1005
+
1006
+ | Pattern | Use When | Complexity | Performance |
1007
+ | --------------------------- | ------------------------------------------------------- | ---------- | ----------- |
1008
+ | **A: SOA → DxMessaging** | Designers create SOA events, modern code uses messaging | Medium | ⚠️ Medium |
1009
+ | **B: Proper SO Usage** | Immutable configs only, messaging for events | Low | ✅ Good |
1010
+ | **None (Pure DxMessaging)** | Greenfield project or full SOA migration | Lowest | ✅ Best |
1011
+
1012
+ ### Migration Path: SOA → DxMessaging
1013
+
1014
+ If you're moving away from SOA:
1015
+
1016
+ 1. **Phase 1:** Identify SOA event usage (GameEvent/GameEventListener patterns)
1017
+ 1. **Phase 2:** Create equivalent DxMessaging messages (Untargeted/Broadcast)
1018
+ 1. **Phase 3:** Add bridges (Pattern A or B) to maintain compatibility
1019
+ 1. **Phase 4:** Migrate listeners to DxMessaging incrementally
1020
+ 1. **Phase 5:** Remove bridges and SOA assets once all references gone
1021
+
1022
+ For SOA variables:
1023
+
1024
+ 1. Convert read-only SO configs → Keep as-is (correct SO usage) or move to JSON/ScriptableObjects for data
1025
+ 1. Convert mutable SO variables → DxMessaging messages or DI-injected services
1026
+ 1. Convert RuntimeSets → DxMessaging global observers (`RegisterBroadcastWithoutSource`)
1027
+
1028
+ ### Final Recommendations
1029
+
1030
+ #### If you're using SOA
1031
+
1032
+ - ✅ **Do:** Use Pattern B (Proper SO Usage) - SOs for immutable configs ONLY, DxMessaging for runtime events
1033
+ - ✅ **Do:** Use Pattern A to bridge existing SOA GameEvent assets to DxMessaging during migration
1034
+ - ✅ **Do:** Read [Anti-ScriptableObject Architecture](https://github.com/cathei/AntiScriptableObjectArchitecture) to understand risks
1035
+ - ✅ **Do:** Consider gradual migration to DxMessaging or DI frameworks
1036
+ - ❌ **Don't:** Use SOs for mutable runtime state (health, scores, etc.)
1037
+ - ❌ **Don't:** Create new SOA event assets—use DxMessaging messages instead
1038
+
1039
+ ##### If you're starting fresh
1040
+
1041
+ - ✅ **Do:** Use DxMessaging for all messaging/events
1042
+ - ✅ **Do:** Use ScriptableObjects ONLY for immutable design data (weapon stats, level configs)
1043
+ - ✅ **Do:** Consider DI frameworks (Zenject/VContainer) for service dependencies
1044
+ - ❌ **Don't:** Adopt SOA's GameEvent/Variable patterns—they're superseded by better tools
1045
+
1046
+ ###### Resources
1047
+
1048
+ - [Anti-ScriptableObject Architecture](https://github.com/cathei/AntiScriptableObjectArchitecture) - Detailed critique
1049
+ - [Ryan Hipple Unite 2017 Talk](https://www.youtube.com/watch?v=raQ3iHhE_Kk) - Original SOA presentation
1050
+ - [Unity Official Guide](https://unity.com/how-to/architect-game-code-scriptable-objects) - Unity's perspective
1051
+ - [DxMessaging DI Integrations](Integrations/) - Better alternatives for dependency management
1052
+ - [Zenject](https://github.com/modesttree/Zenject) - Recommended DI framework
1053
+ - [VContainer](https://github.com/hadashiA/VContainer) - Lightweight DI alternative
1054
+
1055
+ ---
1056
+
732
1057
  ## Related Documentation
733
1058
 
734
1059
  ### Learn the Basics First?
@@ -14,15 +14,15 @@ See also: `Docs/DesignAndArchitecture.md#performance-optimizations` for design d
14
14
 
15
15
  | Message Tech | Operations / Second | Allocations? |
16
16
  | ---------------------------------- | ------------------- | ------------ |
17
- | Unity | 2,328,200 | Yes |
18
- | DxMessaging (GameObject) - Normal | 8,288,600 | No |
19
- | DxMessaging (Component) - Normal | 8,298,200 | No |
20
- | DxMessaging (GameObject) - No-Copy | 9,329,200 | No |
21
- | DxMessaging (Component) - No-Copy | 9,350,800 | No |
22
- | DxMessaging (Untargeted) - No-Copy | 14,705,000 | No |
23
- | Reflexive (One Argument) | 2,806,800 | No |
24
- | Reflexive (Two Arguments) | 2,325,800 | No |
25
- | Reflexive (Three Arguments) | 2,322,200 | No |
17
+ | Unity | 2,490,000 | Yes |
18
+ | DxMessaging (GameObject) - Normal | 8,520,000 | No |
19
+ | DxMessaging (Component) - Normal | 8,542,000 | No |
20
+ | DxMessaging (GameObject) - No-Copy | 9,426,000 | No |
21
+ | DxMessaging (Component) - No-Copy | 9,552,000 | No |
22
+ | DxMessaging (Untargeted) - No-Copy | 14,900,000 | No |
23
+ | Reflexive (One Argument) | 2,832,000 | No |
24
+ | Reflexive (Two Arguments) | 2,348,000 | No |
25
+ | Reflexive (Three Arguments) | 2,364,000 | No |
26
26
 
27
27
  ## macOS
28
28
 
@@ -9,6 +9,7 @@ Do’s
9
9
  - Use GameObject/Component emit helpers (no manual `InstanceId`).
10
10
  - Register once; enable/disable with component state.
11
11
  - Prefer named handler methods over inline lambdas for reuse and clarity.
12
+ - When using DI, inject `IMessageRegistrationBuilder` instead of newing `MessageHandler`s manually.
12
13
 
13
14
  Don’ts
14
15
 
@@ -77,6 +78,36 @@ _ = token.RegisterBroadcastWithoutSource<TookDamage>(OnAnyDamage);
77
78
  void OnAnyDamage(ref InstanceId src, ref TookDamage m) { /* ... */ }
78
79
  ```
79
80
 
81
+ Register (DI / services)
82
+
83
+ ```csharp
84
+ using DxMessaging.Core.MessageBus;
85
+
86
+ public sealed class DamageSystem : IStartable, IDisposable
87
+ {
88
+ private readonly MessageRegistrationLease lease;
89
+
90
+ public DamageSystem(IMessageRegistrationBuilder registrationBuilder)
91
+ {
92
+ lease = registrationBuilder.Build(new MessageRegistrationBuildOptions
93
+ {
94
+ Configure = token =>
95
+ {
96
+ _ = token.RegisterUntargeted<TookDamage>(OnDamage);
97
+ }
98
+ });
99
+ }
100
+
101
+ public void Start() => lease.Activate();
102
+
103
+ public void Dispose() => lease.Dispose();
104
+
105
+ private static void OnDamage(ref TookDamage message) { /* respond */ }
106
+ }
107
+ ```
108
+
109
+ Tip: Define `ZENJECT_PRESENT`, `VCONTAINER_PRESENT`, or `REFLEX_PRESENT` to enable the optional shims under `Runtime/Unity/Integrations/` that bind the builder automatically for those containers.
110
+
80
111
  Interceptors and post‑processors
81
112
 
82
113
  ```csharp