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
@@ -1,205 +1,1270 @@
1
- # Comparisons: Events, Unity Events, and Unity Messages
1
+ # Comparisons: DxMessaging vs. Everything Else
2
2
 
3
- This guide shows common pain points in standard approaches and how DxMessaging addresses them with clearer ownership, ordering, and observability.
3
+ **TL;DR:** If you've used C# events, UnityEvents, or static event buses and thought "there has to be a better way," you're right. DxMessaging fixes the pain points while keeping the benefits.
4
4
 
5
- Table of contents
5
+ ## This guide shows
6
6
 
7
- - C# events/delegates
8
- - UnityEvents (inspector wiring)
9
- - Unity SendMessage
10
- - Global “Event Bus” singletons
11
- - How DxMessaging addresses each
12
- - When to use which
7
+ - What's wrong with each approach (with real code examples)
8
+ - How DxMessaging solves it (with real code examples)
9
+ - Honest trade-offs (what you give up, what you gain)
10
+ - When to actually use each approach (no BS)
13
11
 
14
- Standard C# Events/Actions
12
+ ### Table of Contents
15
13
 
16
- Problems
14
+ - [Performance Benchmarks](#performance-benchmarks)
15
+ - [Unity Messaging Frameworks](#unity-messaging-frameworks)
16
+ - [UniRx](#unirx-reactive-extensions-for-unity)
17
+ - [MessagePipe](#messagepipe-high-performance-messaging)
18
+ - [Zenject Signals](#zenject-signals-di-based-messaging)
19
+ - [Scriptable Object Architecture (SOA)](#scriptable-object-architecture-soa)
20
+ - [Traditional Approaches](#traditional-approaches)
21
+ - [C# Events/Delegates](#standard-c-eventsactions)
22
+ - [UnityEvents](#unityevents-inspector-wiring)
23
+ - [Unity SendMessage](#unity-sendmessage)
24
+ - [Static Event Buses](#global-event-bus-singletons)
25
+ - [Trade-offs](#honest-trade-offs-what-you-give-up-what-you-gain)
26
+ - [Feature Matrix](#feature-by-feature-comparison-matrix)
27
+ - [Decision Guide](#when-each-approach-actually-wins)
17
28
 
18
- - Manual attach/detach: easy to forget unsubscribe; memory leaks and callbacks on destroyed objects.
19
- - Tight coupling: consumers reference producers (or vice‑versa), hurting modularity and tests.
20
- - Ordering is implicit: hard to coordinate A before B across systems.
21
- - Hard to observe globally: no built‑in way to inspect “what fired recently”.
29
+ ## Performance Benchmarks
22
30
 
23
- Typical code
31
+ These sections are auto-updated by the PlayMode comparison benchmarks in `Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs`. Run the suite locally to refresh the tables.
32
+
33
+ ### Comparisons (Windows)
34
+
35
+ | Message Tech | Operations / Second | Allocations? |
36
+ | ---------------------------------- | ------------------- | ------------ |
37
+ | DxMessaging (Untargeted) - No-Copy | 14,902,000 | No |
38
+ | UniRx MessageBroker | 18,026,000 | No |
39
+ | MessagePipe (Global) | 96,978,000 | No |
40
+ | Zenject SignalBus | 2,520,000 | Yes |
41
+
42
+ ### Comparisons (macOS)
43
+
44
+ Run the PlayMode comparison benchmarks on macOS to populate this section.
45
+
46
+ ### Comparisons (Linux)
47
+
48
+ Run the PlayMode comparison benchmarks on Linux to populate this section.
49
+
50
+ ---
51
+
52
+ ## Unity Messaging Frameworks
53
+
54
+ This section compares DxMessaging with other popular Unity messaging/eventing libraries. Each offers different approaches to solving communication and decoupling problems in Unity.
55
+
56
+ ### Quick Summary: Which Framework to Choose?
57
+
58
+ #### TL;DR Decision Tree
59
+
60
+ ```text
61
+ Need absolute simplest pub/sub setup (zero boilerplate)?
62
+ → Use UniRx MessageBroker (publish/receive in 2 lines)
63
+
64
+ Need complex event stream transformations (debounce, throttle, combine)?
65
+ → Use UniRx (reactive programming paradigm)
66
+
67
+ Already using Dependency Injection (Zenject, VContainer, Reflex)?
68
+ → Use MessagePipe (DI-first, best performance) or Zenject Signals (if on Zenject)
69
+ → Or DxMessaging (integrates with DI, see Integrations guides for Zenject/VContainer/Reflex)
70
+
71
+ Need Unity-specific features (GameObject targeting, Inspector debugging, global observers)?
72
+ → Use DxMessaging (Unity-first design)
73
+
74
+ Want plug-and-play with zero dependencies?
75
+ → Use DxMessaging (no setup required)
76
+
77
+ Maximum raw throughput is THE priority?
78
+ → Use MessagePipe (highest ops/sec in benchmarks)
79
+
80
+ Need message validation, interception, or ordered execution?
81
+ → Use DxMessaging (interceptor pipeline, priority-based ordering)
82
+
83
+ Simple pub/sub with automatic lifecycle management and debugging?
84
+ → Use DxMessaging (automatic cleanup, priorities, validation, Inspector)
85
+ ```
86
+
87
+ ##### One-Line Summary for Each
88
+
89
+ - **DxMessaging:** Unity-first pub/sub with automatic lifecycle, global observers, interceptors, priorities, and Inspector debugging (works standalone OR with DI)
90
+ - **UniRx:** Reactive programming with LINQ-style stream operators for complex event transformations
91
+ - **MessagePipe:** DI-first, highest throughput for high-frequency messaging in DI architectures
92
+ - **Zenject Signals:** Decoupled messaging integrated with Zenject dependency injection
93
+
94
+ > **💡 Note:** DxMessaging works both standalone (zero dependencies) AND with DI frameworks. See [Integration Guides](../Integrations/) for Zenject, VContainer, and Reflex.
95
+
96
+ ---
97
+
98
+ ### UniRx (Reactive Extensions for Unity)
99
+
100
+ **What It Is:** A reactive programming library that treats events as observable streams. Based on .NET Reactive Extensions (Rx), reimplemented for Unity with IL2CPP compatibility.
101
+
102
+ **Core Philosophy:** Everything is a stream that can be observed, filtered, combined, and transformed using LINQ-style operators.
103
+
104
+ #### Key Features
105
+
106
+ - **Stream-based programming:** Transform events into observable sequences
107
+ - **LINQ operators:** `Where`, `Select`, `Merge`, `CombineLatest`, `Buffer`, etc.
108
+ - **Async operations:** Convert coroutines to observables with cancellation support
109
+ - **Multithreading:** Thread-safe operations with main thread synchronization
110
+ - **Time operators:** Frame-based and time-based event handling
111
+ - **UI integration:** Observable extensions for Unity UI events
112
+
113
+ #### Code Example
114
+
115
+ ##### Simple MessageBroker Setup (Pub/Sub)
24
116
 
25
117
  ```csharp
26
- public sealed class Spawner
118
+ using UniRx;
119
+ using UnityEngine;
120
+
121
+ public struct EnemySpawned
27
122
  {
28
- public event Action Spawned;
29
- public void Spawn()
123
+ public int EnemyId;
124
+ public Vector3 Position;
125
+ }
126
+
127
+ // Publisher - extremely simple, no setup required
128
+ public class EnemySpawner : MonoBehaviour
129
+ {
130
+ void SpawnEnemy(int id)
30
131
  {
31
- // ...
32
- Spawned?.Invoke();
132
+ MessageBroker.Default.Publish(new EnemySpawned
133
+ {
134
+ EnemyId = id,
135
+ Position = transform.position
136
+ });
33
137
  }
34
138
  }
35
139
 
36
- public sealed class UI
140
+ // Subscriber - also extremely simple
141
+ public class AchievementSystem : MonoBehaviour
37
142
  {
38
- private readonly Spawner _spawner;
39
- public UI(Spawner spawner)
143
+ void Start()
40
144
  {
41
- _spawner = spawner;
42
- _spawner.Spawned += OnSpawned; // must remember to unsubscribe
145
+ MessageBroker.Default.Receive<EnemySpawned>()
146
+ .Subscribe(msg => Debug.Log($"Enemy {msg.EnemyId} spawned!"))
147
+ .AddTo(this); // Automatic cleanup on destroy
43
148
  }
44
- private void OnSpawned() => Refresh();
45
149
  }
46
150
  ```
47
151
 
48
- DxMessaging
152
+ ###### Advanced Stream Transformations (Reactive Programming)
49
153
 
50
- - No direct coupling; the UI listens for a message instead of referencing a specific Spawner instance.
51
- - Lifecycle managed by a token; enable/disable tied to a component.
52
- - Ordering via priority; global inspection via diagnostics and the custom inspector.
154
+ ```csharp
155
+ // Double-click detection using reactive operators
156
+ Observable.EveryUpdate()
157
+ .Where(_ => Input.GetMouseButtonDown(0))
158
+ .Buffer(Observable.Timer(TimeSpan.FromMilliseconds(250)))
159
+ .Where(xs => xs.Count >= 2)
160
+ .Subscribe(_ => Debug.Log("Double Click!"));
161
+
162
+ // Combine multiple input streams
163
+ var leftClick = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0));
164
+ var rightClick = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(1));
165
+ leftClick.Merge(rightClick).Subscribe(_ => Debug.Log("Any click!"));
166
+ ```
167
+
168
+ #### What Problems It Solves
169
+
170
+ - ✅ **Complex event streams:** Chain, filter, combine, and transform events elegantly
171
+ - ✅ **Async operations:** Better async/await alternative with cancellation
172
+ - ✅ **Temporal logic:** Time-based operations (throttle, debounce, sample)
173
+ - ✅ **UI reactivity:** Bind UI elements to data streams reactively
174
+ - ✅ **Memory management:** Disposable subscriptions prevent leaks
175
+
176
+ #### What Problems It Doesn't Solve Well
177
+
178
+ - ⚠️ **Simple pub/sub:** MessageBroker handles this well, but using reactive operators for simple scenarios is overkill
179
+ - ❌ **Execution order control:** No built-in priority system for handler ordering
180
+ - ❌ **Message validation/interception:** No pre-processing pipeline to validate or transform messages before handlers
181
+ - ❌ **Unity Inspector debugging:** No Inspector integration to visualize message flow
182
+ - ❌ **GameObject/Component targeting:** Not designed for Unity-specific targeting patterns
183
+ - ❌ **Global message observation:** Cannot easily listen to all instances of a message type across different sources
184
+
185
+ #### Performance Characteristics
186
+
187
+ - **Allocations:** Can allocate on subscription/disposal; stream operators may allocate
188
+ - **Overhead:** Higher than simple events due to observable infrastructure
189
+ - **Use case:** Best for complex event transformations; overhead justified by functionality
190
+
191
+ #### Learning Curve
192
+
193
+ - **Simple MessageBroker (basic pub/sub):** Very easy - just `Publish()` and `Receive()`, similar to events
194
+ - **Advanced stream operators:** Steep - requires understanding reactive programming paradigm
195
+ - **Mental model shift:** For complex features, must think in streams, not events
196
+ - **Documentation:** Extensive examples, but reactive concepts take time to master
197
+ - **Estimated learning time:** 15 minutes for MessageBroker; 1-2 weeks for reactive stream mastery
198
+
199
+ #### Ease of Understanding
200
+
201
+ - ⭐⭐⭐⭐⭐ (Very easy) - MessageBroker pub/sub is intuitive and straightforward
202
+ - ⭐⭐⭐ (Moderate to difficult) - Advanced reactive operators require learning
203
+ - Stream operator code is concise but requires understanding of reactive patterns
204
+ - Hard to debug complex observable chains without Rx knowledge
205
+ - For advanced features: Team buy-in essential; not intuitive for traditional event-driven developers
206
+
207
+ #### When UniRx Wins
208
+
209
+ - ✅ Simple pub/sub with minimal setup (MessageBroker is extremely easy)
210
+ - ✅ Complex event transformations (e.g., double-click, gesture detection)
211
+ - ✅ Combining multiple input sources
212
+ - ✅ Time-based logic (debounce, throttle, sample)
213
+ - ✅ UI data binding with reactive updates
214
+ - ✅ Teams familiar with reactive programming
215
+
216
+ #### When DxMessaging Wins
217
+
218
+ - ✅ Need Unity-specific features (GameObject targeting, lifecycle management)
219
+ - ✅ Execution order matters (priority-based ordering)
220
+ - ✅ Message validation/interception needed (interceptor pipeline)
221
+ - ✅ Inspector debugging required (message history, registration view)
222
+ - ✅ Direct GameObject/Component targeting
223
+ - ✅ Global message observation (listen to all instances of a message type)
224
+ - ✅ Late-stage processing (post-processors after all handlers)
225
+ - ✅ Automatic lifecycle management (zero memory leaks)
226
+ - ✅ Teams unfamiliar with reactive programming (and don't need reactive features)
227
+
228
+ #### Direct Comparison
229
+
230
+ | Aspect | UniRx | DxMessaging |
231
+ | ------------------------ | ---------------------- | ------------------------ |
232
+ | **Primary Use Case** | Stream transformations | Pub/sub messaging |
233
+ | **Unity Compatibility** | ✅ Built for Unity | ✅ Built for Unity |
234
+ | **Dependencies** | ✅ Standalone | ✅ Standalone |
235
+ | **Performance** | 18M ops/sec | 14M ops/sec |
236
+ | **Allocations** | ⚠️ Can allocate | ✅ Zero (structs) |
237
+ | **Learning Curve** | ⭐ Steep (Rx paradigm) | ⭐⭐⭐ Moderate |
238
+ | **Setup Complexity** | ⭐⭐⭐⭐⭐ Low | ⭐⭐⭐⭐⭐ Plug-and-play |
239
+ | **DI Integration** | ⚠️ Optional | ⚠️ Optional |
240
+ | **Async/Await** | ✅ Observables | ⚠️ Manual |
241
+ | **Type Safety** | ✅ Strong | ✅ Strong |
242
+ | **Lifecycle Management** | ⚠️ Manual dispose | ✅ Automatic |
243
+ | **Execution Order** | ❌ Not built-in | ✅ Priority-based |
244
+ | **GameObject Targeting** | ❌ Not designed for | ✅ Built-in |
245
+ | **Unity Integration** | ⭐⭐⭐⭐ Good (UI) | ⭐⭐⭐⭐⭐ Deep |
246
+ | **Inspector Debugging** | ❌ No | ✅ History + stats |
247
+ | **Interceptors** | ❌ Not built-in | ✅ Full pipeline |
248
+ | **Global Observers** | ❌ Not built-in | ✅ Listen to all |
249
+ | **Post-Processing** | ❌ Not built-in | ✅ Dedicated stage |
250
+ | **Testability** | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ Excellent |
251
+ | **Decoupling** | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐⭐ Excellent |
252
+ | **Temporal Operators** | ✅ Extensive (Rx) | ❌ Not built-in |
253
+ | **Complex Stream Logic** | ✅ LINQ-style | ❌ Not designed for |
254
+
255
+ **Bottom Line:** UniRx excels at complex event stream transformations and reactive programming patterns, with MessageBroker providing extremely simple pub/sub setup. DxMessaging excels at straightforward pub/sub communication with control, validation, debugging, and Unity-specific features. Use UniRx when you need stream operators or simple zero-setup pub/sub; use DxMessaging when you need Unity integration with execution control and debugging.
256
+
257
+ ---
258
+
259
+ ### MessagePipe (High-Performance Messaging)
260
+
261
+ **What It Is:** A high-performance, DI-first messaging library by Cysharp (creators of UniTask). Designed for in-memory and distributed messaging with zero-allocation focus.
262
+
263
+ **Core Philosophy:** Maximum performance with dependency injection integration. Support all messaging patterns with a unified, generic interface.
264
+
265
+ #### Key Features
266
+
267
+ - **Multiple patterns:** Pub/Sub, Request/Response, Mediator patterns
268
+ - **Sync and async:** Full async/await support with configurable strategies (sequential/parallel)
269
+ - **Keyed messaging:** Type-based or key-based message routing
270
+ - **DI-first design:** Deep integration with dependency injection containers
271
+ - **Filters:** Pre/post execution customization (similar to interceptors)
272
+ - **Zero allocation:** Struct messages with zero GC per publish
273
+ - **Roslyn analyzer:** Detects subscription leaks at compile-time
274
+ - **Global and scoped:** Support for global message bus or scoped instances
275
+
276
+ #### Code Example
53
277
 
54
278
  ```csharp
55
- using DxMessaging.Core.Attributes;
56
- using DxMessaging.Core.Extensions;
57
- using DxMessaging.Core.Messages;
279
+ // Using MessagePipe with DI
280
+ public class GameManager : MonoBehaviour
281
+ {
282
+ private IPublisher<EnemySpawned> _publisher;
283
+ private IDisposable _subscription;
58
284
 
59
- [DxUntargetedMessage]
60
- [DxAutoConstructor]
61
- public readonly partial struct Spawned { }
285
+ void Start()
286
+ {
287
+ // Injected via DI container
288
+ _publisher = GlobalMessagePipe.GetPublisher<EnemySpawned>();
289
+ var subscriber = GlobalMessagePipe.GetSubscriber<EnemySpawned>();
290
+
291
+ _subscription = subscriber.Subscribe(msg =>
292
+ {
293
+ Debug.Log($"Enemy spawned: {msg.EnemyId}");
294
+ });
295
+ }
62
296
 
63
- // Producer
64
- var evt = new Spawned();
65
- evt.Emit();
297
+ void SpawnEnemy(int id)
298
+ {
299
+ _publisher.Publish(new EnemySpawned { EnemyId = id });
300
+ }
301
+
302
+ void OnDestroy() => _subscription?.Dispose();
303
+ }
304
+
305
+ // Async handler with filters
306
+ public class AchievementSystem
307
+ {
308
+ public AchievementSystem(IAsyncSubscriber<EnemyKilled> subscriber)
309
+ {
310
+ subscriber.Subscribe(async (msg, cancellationToken) =>
311
+ {
312
+ await SaveAchievementAsync(msg.EnemyType);
313
+ });
314
+ }
315
+ }
316
+ ```
317
+
318
+ #### What Problems It Solves
319
+
320
+ - ✅ **Performance:** 78x faster than Prism EventAggregator, zero allocations
321
+ - ✅ **DI integration:** First-class support for dependency injection
322
+ - ✅ **Async messaging:** Native async/await without blocking
323
+ - ✅ **Leak detection:** Analyzer catches forgotten subscriptions at compile-time
324
+ - ✅ **Flexibility:** Keyed, keyless, buffered, request/response patterns
325
+ - ✅ **Cross-platform:** Works in Unity, .NET, Blazor, etc.
326
+
327
+ #### What Problems It Doesn't Solve Well
328
+
329
+ - ❌ **Unity-specific integration:** No built-in Unity MonoBehaviour lifecycle management or GameObject targeting
330
+ - ❌ **Inspector debugging:** No visual debugging or message history in Unity Inspector
331
+ - ❌ **Execution order control:** No priority system (handlers execute in subscription order)
332
+ - ❌ **Setup complexity:** Requires DI container configuration (VContainer/Zenject setup needed)
333
+ - ❌ **Global message observation:** No built-in way to listen to all instances of a message across different keys/sources
334
+ - ❌ **Standalone use:** Designed for DI-first architecture (less suitable for non-DI projects)
335
+
336
+ #### Performance Characteristics
337
+
338
+ - **Best-in-class:** Claims 78x faster than Prism, faster than C# events in some scenarios
339
+ - **Zero allocation:** Struct-based messages with no GC per publish
340
+ - **Benchmark data:** See performance section above for actual numbers
341
+ - **Use case:** Optimized for high-frequency messaging (thousands/frame)
342
+
343
+ #### Learning Curve
344
+
345
+ - **Moderate:** Requires understanding of dependency injection
346
+ - **DI knowledge:** Must be comfortable with service provider pattern
347
+ - **Generic interfaces:** Multiple generic types can be confusing initially
348
+ - **Estimated learning time:** 2-3 days with DI experience; 1 week without
349
+
350
+ #### Ease of Understanding
351
+
352
+ - ⭐⭐⭐⭐ (Moderate)
353
+ - Clean, generic interfaces once you understand DI
354
+ - Code is straightforward for developers familiar with DI patterns
355
+ - Harder for teams without DI experience
356
+
357
+ #### When MessagePipe Wins
358
+
359
+ - ✅ Performance-critical applications (high message throughput)
360
+ - ✅ Projects already using DI (VContainer, Zenject, etc.)
361
+ - ✅ Cross-platform .NET projects (not Unity-only)
362
+ - ✅ Need async messaging with cancellation
363
+ - ✅ Large-scale projects with DI architecture
364
+ - ✅ Teams experienced with DI patterns
365
+
366
+ #### When DxMessaging Wins
367
+
368
+ - ✅ Unity-first projects (not cross-platform .NET)
369
+ - ✅ Unity lifecycle management needed (automatic MonoBehaviour cleanup)
370
+ - ✅ Inspector debugging essential (message history visualization)
371
+ - ✅ Execution order control needed (priority-based handlers)
372
+ - ✅ Message validation/interception required (interceptor pipeline)
373
+ - ✅ Global message observation needed (listen to all message instances)
374
+ - ✅ Post-processing stage needed (analytics, logging after handlers)
375
+ - ✅ Teams without DI experience or projects not using DI
376
+ - ✅ Plug-and-play simplicity preferred over DI configuration
377
+
378
+ #### Direct Comparison
379
+
380
+ | Aspect | MessagePipe | DxMessaging |
381
+ | ------------------------ | --------------------------- | ------------------------ |
382
+ | **Primary Use Case** | High-perf DI messaging | Pub/sub messaging |
383
+ | **Unity Compatibility** | ✅ Built for Unity | ✅ Built for Unity |
384
+ | **Dependencies** | ⚠️ DI container required | ✅ Standalone |
385
+ | **Performance** | 97M ops/sec | 14M ops/sec |
386
+ | **Allocations** | ✅ Zero (structs) | ✅ Zero (structs) |
387
+ | **Learning Curve** | ⭐⭐⭐⭐ Moderate (DI) | ⭐⭐⭐ Moderate |
388
+ | **Setup Complexity** | ⭐⭐⭐ DI setup required | ⭐⭐⭐⭐⭐ Plug-and-play |
389
+ | **DI Integration** | ✅ First-class | ⚠️ Optional |
390
+ | **Async/Await** | ✅ Native | ⚠️ Manual |
391
+ | **Type Safety** | ✅ Strong | ✅ Strong |
392
+ | **Lifecycle Management** | ⚠️ Manual dispose | ✅ Automatic |
393
+ | **Execution Order** | ❌ Subscription order | ✅ Priority-based |
394
+ | **GameObject Targeting** | ❌ Not built-in | ✅ Built-in |
395
+ | **Unity Integration** | ⭐⭐⭐ Basic (no lifecycle) | ⭐⭐⭐⭐⭐ Deep |
396
+ | **Inspector Debugging** | ❌ No | ✅ History + stats |
397
+ | **Interceptors** | ⚠️ Filters | ✅ Full pipeline |
398
+ | **Global Observers** | ❌ Not built-in | ✅ Listen to all |
399
+ | **Post-Processing** | ⚠️ Via filters | ✅ Dedicated stage |
400
+ | **Testability** | ⭐⭐⭐⭐⭐ DI mocking | ⭐⭐⭐⭐⭐ Local buses |
401
+ | **Decoupling** | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐⭐ Excellent |
402
+ | **Leak Detection** | ✅ Roslyn analyzer | ✅ Automatic lifecycle |
403
+
404
+ **Bottom Line:** MessagePipe is the performance king with DI-first design. DxMessaging is Unity-first with lifecycle awareness and debugging. Use MessagePipe if you have DI infrastructure and need maximum performance. Use DxMessaging if you want Unity-native messaging with automatic lifecycle management.
405
+
406
+ > **💡 Want both?** DxMessaging integrates with DI frameworks! See [DI Integration Guides](../Integrations/) for Zenject, VContainer, and Reflex. Use DI for service construction, DxMessaging for event communication.
407
+
408
+ ---
409
+
410
+ ### Zenject Signals (DI-Based Messaging)
411
+
412
+ **What It Is:** The built-in messaging system for Zenject (Extenject), a dependency injection framework for Unity. Signals are an optional extension that provides decoupled communication.
413
+
414
+ **Core Philosophy:** Loosely coupled messaging integrated with dependency injection. Reduce direct dependencies between classes while maintaining testability.
415
+
416
+ #### Key Features
417
+
418
+ - **DI-integrated:** Signals declared and resolved via Zenject container
419
+ - **Typed signals:** Strongly-typed signal classes with parameters
420
+ - **Synchronous and async:** Sync (RunSync) and async (RunAsync) execution modes
421
+ - **Subscription modes:** Require, optional, or optional-with-warning subscribers
422
+ - **Installer-based setup:** Declare signals in installers for container binding
423
+ - **Multiple subscription methods:** Direct binding, SignalBus subscription, stream-based (with UniRx)
424
+ - **Testable:** Easy to mock and test with dependency injection
425
+
426
+ #### Code Example
427
+
428
+ ```csharp
429
+ // 1. Define signal
430
+ public class EnemyKilledSignal
431
+ {
432
+ public string EnemyType;
433
+ public int Score;
434
+ }
435
+
436
+ // 2. Install and declare in installer
437
+ public class GameInstaller : MonoInstaller
438
+ {
439
+ public override void InstallBindings()
440
+ {
441
+ SignalBusInstaller.Install(Container);
442
+ Container.DeclareSignal<EnemyKilledSignal>();
443
+ Container.BindSignal<EnemyKilledSignal>()
444
+ .ToMethod<AchievementSystem>(x => x.OnEnemyKilled)
445
+ .FromResolve();
446
+ }
447
+ }
448
+
449
+ // 3. Fire signal
450
+ public class Enemy : MonoBehaviour
451
+ {
452
+ [Inject] private SignalBus _signalBus;
453
+
454
+ void Die()
455
+ {
456
+ _signalBus.Fire(new EnemyKilledSignal
457
+ {
458
+ EnemyType = "Orc",
459
+ Score = 100
460
+ });
461
+ }
462
+ }
463
+
464
+ // 4. Subscribe to signal
465
+ public class AchievementSystem
466
+ {
467
+ [Inject] private SignalBus _signalBus;
468
+
469
+ public void Initialize()
470
+ {
471
+ _signalBus.Subscribe<EnemyKilledSignal>(OnEnemyKilled);
472
+ }
473
+
474
+ void OnEnemyKilled(EnemyKilledSignal signal)
475
+ {
476
+ Debug.Log($"Killed {signal.EnemyType} for {signal.Score} points!");
477
+ }
478
+ }
479
+ ```
480
+
481
+ #### What Problems It Solves
482
+
483
+ - ✅ **Decoupling:** Classes communicate without direct references
484
+ - ✅ **DI integration:** Seamless with Zenject dependency injection
485
+ - ✅ **Testability:** Easy to mock SignalBus in tests
486
+ - ✅ **Type safety:** Strongly-typed signal classes
487
+ - ✅ **Subscriber validation:** Can enforce required subscribers
488
+ - ✅ **Async support:** Fire signals synchronously or asynchronously
489
+
490
+ #### What Problems It Doesn't Solve Well
491
+
492
+ - ❌ **Zenject dependency:** Must use Zenject/Extenject framework; not standalone
493
+ - ❌ **Performance overhead:** Higher than lightweight messaging (DI resolution cost)
494
+ - ❌ **Execution order control:** No priority system for handler ordering
495
+ - ❌ **Inspector debugging:** No visual message history or flow visualization
496
+ - ❌ **Allocations:** Signal parameters can cause allocations depending on usage
497
+ - ❌ **Validation pipeline:** No built-in interceptor or pre-processing stage
498
+ - ❌ **Global observation:** Cannot easily listen to all signal fires across the system
499
+ - ❌ **Post-processing:** No dedicated after-handler stage for analytics/logging
500
+
501
+ #### Performance Characteristics
502
+
503
+ - **Overhead:** Higher than lightweight messaging (DI resolution + boxing)
504
+ - **Allocations:** Signal parameters can cause allocations (depends on implementation)
505
+ - **Benchmark data:** See performance section above for actual numbers
506
+ - **Use case:** Performance trade-off for testability and DI benefits
507
+
508
+ #### Learning Curve
509
+
510
+ - **Moderate to steep:** Requires understanding Zenject dependency injection
511
+ - **Zenject knowledge:** Must learn Zenject before signals
512
+ - **Setup overhead:** Installers, bindings, container configuration
513
+ - **Estimated learning time:** 1 week for Zenject + signals together
514
+
515
+ #### Ease of Understanding
516
+
517
+ - ⭐⭐⭐ (Moderate)
518
+ - Clear once you understand Zenject
519
+ - Signal concept is straightforward
520
+ - Setup (installers, bindings) adds complexity
521
+
522
+ #### When Zenject Signals Win
523
+
524
+ - ✅ Already using Zenject for dependency injection
525
+ - ✅ Testability is critical (DI makes mocking easy)
526
+ - ✅ Need subscriber validation (ensure handlers exist)
527
+ - ✅ Team experienced with Zenject
528
+ - ✅ Want DI-managed lifecycle
529
+
530
+ #### When DxMessaging Wins
531
+
532
+ - ✅ Not using Zenject/Extenject (or prefer standalone solution)
533
+ - ✅ Performance critical (lower overhead than DI-based signals)
534
+ - ✅ Execution order control needed (priority-based handlers)
535
+ - ✅ Inspector debugging required (message history visualization)
536
+ - ✅ Message validation/interception needed (interceptor pipeline)
537
+ - ✅ Global message observation needed (listen to all signal fires)
538
+ - ✅ Post-processing stage needed (analytics after handlers)
539
+ - ✅ Zero-allocation messaging essential (struct-based)
540
+ - ✅ GameObject/Component targeting needed (Unity-specific patterns)
541
+ - ✅ Plug-and-play simplicity preferred over DI setup
542
+
543
+ #### Direct Comparison
544
+
545
+ | Aspect | Zenject Signals | DxMessaging |
546
+ | ------------------------ | ---------------------------- | ------------------------ |
547
+ | **Primary Use Case** | DI-integrated messaging | Pub/sub messaging |
548
+ | **Unity Compatibility** | ✅ Built for Unity | ✅ Built for Unity |
549
+ | **Dependencies** | ❌ Zenject required | ✅ Standalone |
550
+ | **Performance** | 2.5M ops/sec | 14M ops/sec |
551
+ | **Allocations** | ⚠️ Can allocate | ✅ Zero (structs) |
552
+ | **Learning Curve** | ⭐⭐ Steep (Zenject+Signals) | ⭐⭐⭐ Moderate |
553
+ | **Setup Complexity** | ⭐⭐ Installers required | ⭐⭐⭐⭐⭐ Plug-and-play |
554
+ | **DI Integration** | ✅ Required (Zenject) | ⚠️ Optional |
555
+ | **Async/Await** | ✅ RunAsync support | ⚠️ Manual |
556
+ | **Type Safety** | ✅ Strong | ✅ Strong |
557
+ | **Lifecycle Management** | ⚠️ DI-managed | ✅ Automatic |
558
+ | **Execution Order** | ❌ Not built-in | ✅ Priority-based |
559
+ | **GameObject Targeting** | ❌ Not built-in | ✅ Built-in |
560
+ | **Unity Integration** | ⭐⭐⭐⭐ DI-managed | ⭐⭐⭐⭐⭐ Deep |
561
+ | **Inspector Debugging** | ❌ No | ✅ History + stats |
562
+ | **Interceptors** | ⚠️ Subscriber validation | ✅ Full pipeline |
563
+ | **Global Observers** | ❌ Not built-in | ✅ Listen to all |
564
+ | **Post-Processing** | ❌ Not built-in | ✅ Dedicated stage |
565
+ | **Testability** | ⭐⭐⭐⭐⭐ DI mocking | ⭐⭐⭐⭐⭐ Local buses |
566
+ | **Decoupling** | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐⭐ Excellent |
567
+
568
+ **Bottom Line:** Zenject Signals are great if you're already invested in Zenject and value testability through DI. DxMessaging is better if you want standalone messaging without DI overhead, with better performance and Unity integration.
569
+
570
+ > **💡 Using Zenject?** DxMessaging integrates with Zenject! See [DxMessaging + Zenject Integration Guide](../Integrations/Zenject.md) for step-by-step setup. Get DxMessaging's features (priorities, interceptors, Inspector debugging) with Zenject's DI.
571
+
572
+ ---
573
+
574
+ ## Scriptable Object Architecture (SOA)
575
+
576
+ **What It Is:** A Unity-specific pattern popularized by Ryan Hipple's [Unite 2017 talk](https://www.youtube.com/watch?v=raQ3iHhE_Kk) that uses ScriptableObject assets for runtime communication (GameEvent, FloatVariable, etc.).
577
+
578
+ **Core Philosophy:** Designer-driven, asset-based communication where systems communicate through serialized SO assets instead of direct references.
579
+
580
+ **⚠️ Controversial Pattern:** SOA has significant criticisms regarding scalability and maintainability. See [Anti-ScriptableObject Architecture](https://github.com/cathei/AntiScriptableObjectArchitecture) for detailed critique. Unity recommends ScriptableObjects for **immutable design data**, not mutable runtime state.
581
+
582
+ ### Quick Comparison
583
+
584
+ | Aspect | SOA (GameEvent/Variables) | DxMessaging |
585
+ | -------------------- | ------------------------------------------------------------------------- | ----------------------------------- |
586
+ | **Designer Control** | ✅ High (create events in Inspector) | ❌ Low (code-driven) |
587
+ | **Type Safety** | ⚠️ Mixed (SO refs typed, but UnityEvent wiring loses compile-time safety) | ✅ Strong (compile-time validation) |
588
+ | **Lifecycle** | ⚠️ Manual (assets persist) | ✅ Automatic (tokens clean up) |
589
+ | **Performance** | ⚠️ List iteration, UnityAction overhead | ✅ Zero-allocation structs |
590
+ | **Testability** | ⚠️ Requires SO asset cleanup | ✅ Isolated buses per test |
591
+
592
+ ### When to Use Each
593
+
594
+ #### Choose SOA when
595
+
596
+ - Designers need to create and wire events in the Inspector without code
597
+ - Your team is already deeply invested in SOA with existing assets
598
+ - Designer empowerment is more important than code maintainability
599
+
600
+ ##### Choose DxMessaging when
601
+
602
+ - You need type-safe, code-driven messaging
603
+ - Performance and zero-allocation are priorities
604
+ - You want automatic lifecycle management
605
+ - You need interceptors, priorities, or global observers
606
+
607
+ ###### Use Both when
608
+
609
+ - ScriptableObjects for **immutable config data** (weapon stats, level configs)
610
+ - DxMessaging for **runtime events and communication**
611
+ - This is the recommended approach - use each tool correctly
612
+
613
+ ### Full Comparison Guide
614
+
615
+ For detailed migration patterns, interoperability strategies, and code examples, see:
616
+
617
+ #### → [SOA Compatibility Guide](Patterns.md#14-compatibility-with-scriptable-object-architecture-soa)
618
+
619
+ Includes:
620
+
621
+ - Pattern A: Bridging SOA GameEvents to DxMessaging
622
+ - Pattern B: Proper ScriptableObject usage (configs + messaging)
623
+ - Migration path from SOA to DxMessaging
624
+ - When to keep using ScriptableObjects
625
+
626
+ ##### Resources
627
+
628
+ - [Unite 2017 Talk](https://www.youtube.com/watch?v=raQ3iHhE_Kk) - Original SOA presentation
629
+ - [Anti-SOA Critique](https://github.com/cathei/AntiScriptableObjectArchitecture) - Detailed criticisms
630
+ - [Unity Official Guide](https://unity.com/how-to/architect-game-code-scriptable-objects) - Unity's perspective
631
+
632
+ ---
633
+
634
+ ## Traditional Approaches
635
+
636
+ ### Standard C# Events/Actions
637
+
638
+ **What It Is:** C#'s built-in event and delegate system. The default way to handle callbacks and notifications in .NET and Unity.
639
+
640
+ **Core Philosophy:** Direct, type-safe callbacks between objects. Simple, familiar, and built into the language.
641
+
642
+ #### Key Features
643
+
644
+ - **Language-native:** Built into C#, no dependencies
645
+ - **Type-safe:** Compile-time checking of event signatures
646
+ - **Return values:** Events can return values and use `out` parameters
647
+ - **Inline lambdas:** Subscribe with anonymous functions
648
+ - **Multicast:** Multiple subscribers per event
649
+ - **Fast:** Direct method invocation with minimal overhead
650
+
651
+ #### Code Example
652
+
653
+ ```csharp
654
+ // Define and use C# events
655
+ public class GameManager : MonoBehaviour
656
+ {
657
+ public event Action<int> OnScoreChanged;
658
+
659
+ public void AddScore(int points)
660
+ {
661
+ OnScoreChanged?.Invoke(points);
662
+ }
663
+ }
664
+
665
+ public class UI : MonoBehaviour
666
+ {
667
+ [SerializeField] private GameManager gameManager;
668
+
669
+ void OnEnable()
670
+ {
671
+ gameManager.OnScoreChanged += UpdateScore;
672
+ }
673
+
674
+ void OnDisable()
675
+ {
676
+ gameManager.OnScoreChanged -= UpdateScore;
677
+ }
66
678
 
67
- // Consumer (Unity)
68
- _ = token.RegisterUntargeted<Spawned>(OnSpawned);
69
- void OnSpawned(ref Spawned m) => Refresh();
679
+ void UpdateScore(int points)
680
+ {
681
+ Debug.Log($"Score: {points}");
682
+ }
683
+ }
70
684
  ```
71
685
 
72
- UnityEvents (inspector wiring)
686
+ #### What Problems It Solves
687
+
688
+ - ✅ **Simple callbacks:** Straightforward notification pattern
689
+ - ✅ **Type safety:** Compile-time checking prevents errors
690
+ - ✅ **Return values:** Can get feedback from event handlers
691
+ - ✅ **Performance:** Minimal overhead, direct invocation
692
+ - ✅ **Familiarity:** Every C# developer knows events
693
+ - ✅ **No dependencies:** Built into the language
73
694
 
74
- Problems
695
+ #### What Problems It Doesn't Solve Well
75
696
 
76
- - Great for small demos, but brittle at scale (hidden references, order issues, refactors break wiring).
77
- - No interception or post‑processing stages to validate/normalize.
697
+ - **Memory leaks:** Forgetting to unsubscribe causes leaks
698
+ - **Tight coupling:** Subscribers need direct references to event sources
699
+ - ❌ **Execution order:** Undefined handler invocation order
700
+ - ❌ **Lifecycle management:** Manual subscribe/unsubscribe in OnEnable/OnDisable
701
+ - ❌ **Debugging:** No visibility into who's subscribed or when events fire
702
+ - ❌ **Validation/interception:** No pipeline to modify or validate before handlers
703
+ - ❌ **Global observation:** Cannot listen to all events across the system
78
704
 
79
- Typical code
705
+ #### Performance Characteristics
706
+
707
+ - **Fastest option:** Direct method invocation (~50ns per call)
708
+ - **Zero allocation:** No GC pressure for basic events
709
+ - **Inline-able:** JIT can optimize simple event calls
710
+ - **Use case:** Best raw performance for simple notifications
711
+
712
+ #### Learning Curve
713
+
714
+ - **Zero for C# developers:** Standard language feature
715
+ - **Immediate productivity:** No new concepts to learn
716
+ - **Estimated learning time:** Already know it
717
+
718
+ #### Ease of Understanding
719
+
720
+ - ⭐⭐⭐⭐⭐ (Very easy)
721
+ - Familiar to all C# developers
722
+ - Straightforward mental model
723
+ - Easy to debug with breakpoints
724
+
725
+ #### When C# Events Win
726
+
727
+ - ✅ Small, stable scope (5-10 events max)
728
+ - ✅ Need return values or `out` parameters
729
+ - ✅ Writing a library (DxMessaging is Unity-specific)
730
+ - ✅ Simple, local communication within a class or module
731
+ - ✅ Team is C# experts, Unity beginners
732
+ - ✅ Performance is absolutely critical (lowest overhead)
733
+ - ✅ Quick prototypes or game jams
734
+
735
+ #### When DxMessaging Wins
736
+
737
+ - ✅ Memory leaks are a problem (automatic lifecycle management)
738
+ - ✅ Need decoupling (systems don't reference each other)
739
+ - ✅ Execution order matters (priority-based handlers)
740
+ - ✅ Debugging "what fired when" (Inspector message history)
741
+ - ✅ Message validation/interception needed (interceptor pipeline)
742
+ - ✅ Global observation needed (listen to all message instances)
743
+ - ✅ Cross-system communication (10+ systems)
744
+ - ✅ Long-term maintenance (months/years)
745
+ - ✅ GameObject/Component targeting needed
746
+ - ✅ Post-processing stage needed (analytics after handlers)
747
+
748
+ #### Direct Comparison
749
+
750
+ | Aspect | C# Events | DxMessaging |
751
+ | ------------------------ | --------------------- | ---------------------- |
752
+ | **Primary Use Case** | Simple callbacks | Pub/sub messaging |
753
+ | **Unity Compatibility** | ✅ Built into C# | ✅ Built for Unity |
754
+ | **Dependencies** | ✅ None (language) | ✅ Standalone |
755
+ | **Performance** | ~50ns/call (fastest) | ~60ns/call |
756
+ | **Allocations** | ✅ Zero (basic) | ✅ Zero (structs) |
757
+ | **Learning Curve** | ⭐⭐⭐⭐⭐ None | ⭐⭐⭐ Moderate |
758
+ | **Setup Complexity** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐ Moderate |
759
+ | **DI Integration** | ⚠️ Manual | ⚠️ Optional |
760
+ | **Async/Await** | ⚠️ Manual | ⚠️ Manual |
761
+ | **Type Safety** | ✅ Strong | ✅ Strong |
762
+ | **Lifecycle Management** | ❌ Manual unsubscribe | ✅ Automatic |
763
+ | **Execution Order** | ❌ Undefined | ✅ Priority-based |
764
+ | **GameObject Targeting** | ❌ Not built-in | ✅ Built-in |
765
+ | **Unity Integration** | ⭐ None | ⭐⭐⭐⭐⭐ Deep |
766
+ | **Inspector Debugging** | ❌ No | ✅ History + stats |
767
+ | **Interceptors** | ❌ Not built-in | ✅ Full pipeline |
768
+ | **Global Observers** | ❌ Not built-in | ✅ Listen to all |
769
+ | **Post-Processing** | ❌ Not built-in | ✅ Dedicated stage |
770
+ | **Testability** | ⭐⭐ Hard to isolate | ⭐⭐⭐⭐⭐ Local buses |
771
+ | **Decoupling** | ⭐ Tight coupling | ⭐⭐⭐⭐⭐ Excellent |
772
+ | **Return Values** | ✅ Yes | ❌ Fire-and-forget |
773
+
774
+ **Bottom Line:** C# events are the fastest and simplest for basic callbacks. DxMessaging is better for complex, decoupled systems where lifecycle management, debugging, and execution control matter.
775
+
776
+ ---
777
+
778
+ ### UnityEvents (Inspector Wiring)
779
+
780
+ **What It Is:** Unity's serializable event system that allows wiring callbacks in the Inspector. Designed for designer-friendly event hookups without code.
781
+
782
+ **Core Philosophy:** Visual, Inspector-based event connections. Enable non-programmers to wire game logic through the editor.
783
+
784
+ #### Key Features
785
+
786
+ - **Inspector wiring:** Drag-and-drop connections in Unity Inspector
787
+ - **Serializable:** Events saved with scenes and prefabs
788
+ - **Designer-friendly:** Non-programmers can wire logic
789
+ - **Persistent references:** Connections survive across sessions
790
+ - **Dynamic parameters:** Pass values from Inspector to callbacks
791
+ - **No code required:** Can wire entire behaviors without scripting
792
+
793
+ #### Code Example
80
794
 
81
795
  ```csharp
82
796
  using UnityEngine;
83
797
  using UnityEngine.Events;
84
798
 
85
- public sealed class Button : MonoBehaviour
799
+ public class Button : MonoBehaviour
86
800
  {
87
- public UnityEvent onClicked; // wired in Inspector
88
- public void Click() => onClicked?.Invoke();
801
+ public UnityEvent onClick;
802
+
803
+ void OnMouseDown()
804
+ {
805
+ onClick?.Invoke();
806
+ }
89
807
  }
90
808
 
91
- public sealed class UI : MonoBehaviour
809
+ public class UI : MonoBehaviour
92
810
  {
93
- public void Refresh() { /* ... */ }
811
+ public void ShowMenu()
812
+ {
813
+ Debug.Log("Menu shown");
814
+ }
815
+
816
+ public void HideMenu()
817
+ {
818
+ Debug.Log("Menu hidden");
819
+ }
94
820
  }
821
+
822
+ // In Inspector: Drag UI component to Button's onClick event
823
+ // Select ShowMenu from dropdown
824
+ // No additional code needed
95
825
  ```
96
826
 
97
- DxMessaging
827
+ #### What Problems It Solves
828
+
829
+ - ✅ **Visual wiring:** See connections in Inspector
830
+ - ✅ **No code required:** Designers can hook up events
831
+ - ✅ **Persistence:** Connections saved with scenes/prefabs
832
+ - ✅ **Rapid prototyping:** Quick iteration without scripting
833
+ - ✅ **Prefab workflows:** Events work across prefab instances
834
+
835
+ #### What Problems It Doesn't Solve Well
98
836
 
99
- - Strongly‑typed registrations in code; explicit priorities and stages.
100
- - Inspect and page through emissions/registrations from MessagingComponent inspector.
837
+ - **Hidden dependencies:** Connections invisible in code, hard to find during refactoring
838
+ - **Brittle at scale:** Renaming methods breaks wiring, no compile-time safety
839
+ - ❌ **Execution order:** Undefined call order for multiple subscribers
840
+ - ❌ **No validation:** No way to validate or intercept before invocation
841
+ - ❌ **Performance:** Slower than C# events due to reflection and boxing
842
+ - ❌ **Debugging:** Hard to trace "who called what" at runtime
843
+ - ❌ **Merge conflicts:** Inspector changes cause git conflicts
844
+ - ❌ **Refactoring pain:** Renaming/moving methods silently breaks connections
101
845
 
102
- Unity SendMessage
846
+ #### Performance Characteristics
103
847
 
104
- Problems
848
+ - **Slow compared to alternatives:** Reflection overhead, boxing for value types
849
+ - **Allocations:** Parameters boxed as objects, causes GC pressure
850
+ - **Use case:** Acceptable for UI and low-frequency events, avoid for high-frequency gameplay
105
851
 
106
- - String‑based; no compile‑time checking. 0/1 parameter only; boxing costs.
107
- - Hard to reason about who handles what; debugging is difficult.
852
+ #### Learning Curve
108
853
 
109
- DxMessaging
854
+ - **Very easy:** Point-and-click in Inspector
855
+ - **No coding knowledge needed:** Accessible to designers
856
+ - **Estimated learning time:** 5-10 minutes
110
857
 
111
- - Use `ReflexiveMessage` to bridge legacy SendMessage behavior into the bus pipeline (optional).
112
- - Prefer typed messages for new code; multiple parameters via fields, by‑ref handlers avoid boxing.
858
+ #### Ease of Understanding
859
+
860
+ - ⭐⭐⭐⭐ (Easy for wiring, hard for debugging)
861
+ - Simple to connect in Inspector
862
+ - Difficult to understand flow when reading code
863
+ - Hard to track down at scale (where is this method called from?)
864
+
865
+ #### When UnityEvents Win
866
+
867
+ - ✅ Designers need to wire logic without code
868
+ - ✅ Rapid prototyping with prefabs
869
+ - ✅ Very simple games (mobile casual, hyper-casual)
870
+ - ✅ UI interactions with minimal logic
871
+ - ✅ Small projects (<5 scripts)
872
+ - ✅ One-off connections that rarely change
873
+
874
+ #### When DxMessaging Wins
875
+
876
+ - ✅ Code-first development (programmers prefer code visibility)
877
+ - ✅ Refactoring frequently (compile-time safety)
878
+ - ✅ Execution order matters (priority-based handlers)
879
+ - ✅ Need validation/interception (interceptor pipeline)
880
+ - ✅ Performance-sensitive (zero allocation required)
881
+ - ✅ Debugging observability (message history)
882
+ - ✅ Cross-system communication (10+ components)
883
+ - ✅ Team collaboration (merge-friendly code over Inspector)
884
+ - ✅ Long-term maintenance (find usages, refactor safely)
885
+
886
+ #### Direct Comparison
887
+
888
+ | Aspect | UnityEvents | DxMessaging |
889
+ | ------------------------ | ---------------------- | ---------------------- |
890
+ | **Primary Use Case** | Inspector wiring | Pub/sub messaging |
891
+ | **Unity Compatibility** | ✅ Built into Unity | ✅ Built for Unity |
892
+ | **Dependencies** | ✅ None (Unity) | ✅ Standalone |
893
+ | **Performance** | Slow (serialization) | ~60ns/call |
894
+ | **Allocations** | ❌ Boxing | ✅ Zero (structs) |
895
+ | **Learning Curve** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐ Moderate |
896
+ | **Setup Complexity** | ⭐⭐⭐⭐⭐ Inspector | ⭐⭐⭐ Code-based |
897
+ | **DI Integration** | ❌ No | ⚠️ Optional |
898
+ | **Async/Await** | ❌ No | ⚠️ Manual |
899
+ | **Type Safety** | ⭐⭐ Weak (serialized) | ✅ Strong |
900
+ | **Lifecycle Management** | ⚠️ Unity-managed | ✅ Automatic |
901
+ | **Execution Order** | ❌ Undefined | ✅ Priority-based |
902
+ | **GameObject Targeting** | ⚠️ Manual references | ✅ Built-in |
903
+ | **Unity Integration** | ⭐⭐⭐ Inspector-based | ⭐⭐⭐⭐⭐ Deep |
904
+ | **Inspector Debugging** | ⭐⭐ Connections only | ✅ History + stats |
905
+ | **Interceptors** | ❌ Not built-in | ✅ Full pipeline |
906
+ | **Global Observers** | ❌ Not possible | ✅ Listen to all |
907
+ | **Post-Processing** | ❌ Not built-in | ✅ Dedicated stage |
908
+ | **Testability** | ⭐⭐ Scene setup | ⭐⭐⭐⭐⭐ Local buses |
909
+ | **Decoupling** | ⭐⭐ Hidden refs | ⭐⭐⭐⭐⭐ Excellent |
910
+ | **Refactoring Safety** | ❌ Silent breakage | ✅ Compile-time errors |
911
+ | **Code Visibility** | ❌ Hidden in Inspector | ✅ Explicit in code |
912
+
913
+ **Bottom Line:** UnityEvents are great for simple Inspector-based wiring and designer workflows. DxMessaging is better for code-first development, refactoring safety, and complex messaging needs.
914
+
915
+ ---
916
+
917
+ ### Unity SendMessage
918
+
919
+ **What It Is:** Unity's legacy reflection-based message system. Calls methods by name on GameObjects and their components.
920
+
921
+ **Core Philosophy:** String-based, reflection-driven communication. Designed for simplicity and GameObject hierarchy traversal.
922
+
923
+ #### Key Features
924
+
925
+ - **String-based:** Call methods by name without references
926
+ - **Hierarchy traversal:** SendMessageUpwards, BroadcastMessage for parent/child searching
927
+ - **No dependencies:** Built into Unity GameObject
928
+ - **Simple API:** One-line method calls
929
+ - **GameObject-centric:** Works with Unity's component model
930
+ - **Optional receivers:** Methods don't need to exist (SendMessageOptions.DontRequireReceiver)
931
+
932
+ #### Code Example
933
+
934
+ ```csharp
935
+ using UnityEngine;
936
+
937
+ public class Enemy : MonoBehaviour
938
+ {
939
+ void TakeDamage(int amount)
940
+ {
941
+ Debug.Log($"Took {amount} damage");
942
+ }
943
+ }
944
+
945
+ public class Weapon : MonoBehaviour
946
+ {
947
+ void Attack(GameObject target)
948
+ {
949
+ // Call TakeDamage on target GameObject
950
+ target.SendMessage("TakeDamage", 10);
951
+ }
952
+
953
+ void AttackUpwards()
954
+ {
955
+ // Call on this GameObject and all parents
956
+ SendMessageUpwards("TakeDamage", 5, SendMessageOptions.DontRequireReceiver);
957
+ }
958
+
959
+ void AttackChildren()
960
+ {
961
+ // Call on this GameObject and all children
962
+ BroadcastMessage("TakeDamage", 3);
963
+ }
964
+ }
965
+ ```
966
+
967
+ #### What Problems It Solves
968
+
969
+ - ✅ **No references needed:** Call methods without GetComponent
970
+ - ✅ **Hierarchy traversal:** Easy parent/child communication
971
+ - ✅ **Simple API:** One-line method invocation
972
+ - ✅ **Optional receivers:** Can call non-existent methods safely
973
+ - ✅ **Built-in:** No setup or dependencies
974
+
975
+ #### What Problems It Doesn't Solve Well
976
+
977
+ - ❌ **No type safety:** String-based, typos cause silent failures
978
+ - ❌ **Slow performance:** Reflection overhead on every call
979
+ - ❌ **Limited parameters:** Only 0 or 1 parameter supported
980
+ - ❌ **Boxing allocations:** Value types boxed to object, causes GC
981
+ - ❌ **Hard to debug:** No compile-time checking, no IDE "Find Usages"
982
+ - ❌ **Refactoring nightmare:** Renaming methods breaks string references
983
+ - ❌ **No validation:** No way to validate or intercept messages
984
+ - ❌ **Execution order:** Undefined call order for multiple receivers
985
+
986
+ #### Performance Characteristics
987
+
988
+ - **Very slow:** Reflection overhead much worse than events or messaging systems
989
+ - **Allocations:** Boxing value type parameters causes GC pressure
990
+ - **Use case:** Legacy code only; avoid for new development
991
+
992
+ #### Learning Curve
993
+
994
+ - **Very easy:** Simple one-line API
995
+ - **Immediate productivity:** No setup required
996
+ - **Estimated learning time:** 5 minutes
997
+
998
+ #### Ease of Understanding
999
+
1000
+ - ⭐⭐⭐ (Simple to use, hard to maintain)
1001
+ - Easy to write initially
1002
+ - Difficult to track method calls (no Find Usages)
1003
+ - Refactoring breaks string references silently
1004
+
1005
+ #### When Unity SendMessage Wins
1006
+
1007
+ - ✅ Legacy code that already uses it
1008
+ - ✅ Quick prototypes (throwaway code)
1009
+ - ✅ Simple tutorials or learning examples
1010
+ - ✅ Calling optional methods that may not exist
1011
+
1012
+ #### When DxMessaging Wins
1013
+
1014
+ - ✅ Type safety required (compile-time checking)
1015
+ - ✅ Performance matters (zero allocation, no reflection)
1016
+ - ✅ Multiple parameters needed (struct fields)
1017
+ - ✅ Refactoring frequently (find usages, rename safely)
1018
+ - ✅ Debugging observability (message history)
1019
+ - ✅ Execution order control (priority-based handlers)
1020
+ - ✅ Message validation/interception (interceptor pipeline)
1021
+ - ✅ Production code (maintainability over simplicity)
1022
+ - ✅ Modern projects (avoid legacy patterns)
1023
+
1024
+ #### Direct Comparison
1025
+
1026
+ | Aspect | Unity SendMessage | DxMessaging |
1027
+ | ------------------------ | ------------------------- | ---------------------------- |
1028
+ | **Primary Use Case** | Legacy GameObject calls | Pub/sub messaging |
1029
+ | **Unity Compatibility** | ✅ Built into Unity | ✅ Built for Unity |
1030
+ | **Dependencies** | ✅ None (Unity) | ✅ Standalone |
1031
+ | **Performance** | Very slow (reflection) | ~60ns/call |
1032
+ | **Allocations** | ❌ Heavy boxing | ✅ Zero (structs) |
1033
+ | **Learning Curve** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐ Moderate |
1034
+ | **Setup Complexity** | ⭐⭐⭐⭐⭐ None | ⭐⭐⭐ Moderate |
1035
+ | **DI Integration** | ❌ No | ⚠️ Optional |
1036
+ | **Async/Await** | ❌ No | ⚠️ Manual |
1037
+ | **Type Safety** | ❌ String-based | ✅ Strong |
1038
+ | **Lifecycle Management** | ❌ None | ✅ Automatic |
1039
+ | **Execution Order** | ❌ Undefined | ✅ Priority-based |
1040
+ | **GameObject Targeting** | ✅ Hierarchy traversal | ✅ Built-in (ID-based) |
1041
+ | **Unity Integration** | ⭐⭐ Legacy API | ⭐⭐⭐⭐⭐ Deep |
1042
+ | **Inspector Debugging** | ❌ No | ✅ History + stats |
1043
+ | **Interceptors** | ❌ Not built-in | ✅ Full pipeline |
1044
+ | **Global Observers** | ❌ Not possible | ✅ Listen to all |
1045
+ | **Post-Processing** | ❌ Not built-in | ✅ Dedicated stage |
1046
+ | **Testability** | ⭐⭐ Requires GameObjects | ⭐⭐⭐⭐⭐ Local buses |
1047
+ | **Decoupling** | ⭐⭐ String-based | ⭐⭐⭐⭐⭐ Excellent |
1048
+ | **Refactoring Safety** | ❌ Silent breakage | ✅ Compile-time errors |
1049
+ | **Parameters** | ⚠️ 0 or 1 only | ✅ Unlimited (struct fields) |
1050
+
1051
+ **Bottom Line:** SendMessage is legacy Unity API. Use only for maintaining old code. DxMessaging provides all the same capabilities with type safety, performance, and modern tooling.
1052
+
1053
+ **Migration Path:** DxMessaging provides `ReflexiveMessage` to bridge legacy SendMessage behavior:
113
1054
 
114
1055
  ```csharp
115
1056
  using DxMessaging.Core;
116
1057
  using DxMessaging.Core.Messages;
117
1058
 
1059
+ // Legacy SendMessage equivalent
118
1060
  InstanceId target = gameObject;
119
1061
  var msg = new ReflexiveMessage("OnHit", ReflexiveSendMode.Upwards, 10);
120
1062
  MessageHandler.MessageBus.TargetedBroadcast(ref target, ref msg);
1063
+
1064
+ // Prefer typed messages for new code:
1065
+ // - Multiple parameters via struct fields
1066
+ // - By-ref handlers avoid boxing
1067
+ // - Compile-time safety
121
1068
  ```
122
1069
 
123
- Global “Event Bus” singletons
1070
+ ---
1071
+
1072
+ ### Global Event Bus Singletons
124
1073
 
125
- Problems
1074
+ **What It Is:** A static/singleton class that centralizes all events in one global location. Common pattern for decoupling without dependency injection.
126
1075
 
127
- - Often devolves to one giant bag of static events; naming, ownership, and routing semantics get muddy.
128
- - Still manual lifecycle and ordering issues.
1076
+ **Core Philosophy:** Central event hub accessible from anywhere. Simplify communication through a single global entry point.
129
1077
 
130
- Typical code
1078
+ #### Key Features
1079
+
1080
+ - **Global access:** Static class available everywhere
1081
+ - **No references needed:** No GetComponent or serialized fields
1082
+ - **Simple pattern:** Easy to understand and implement
1083
+ - **Decoupling:** Publishers and subscribers don't know about each other
1084
+ - **Flexibility:** Can add events without changing existing code
1085
+
1086
+ #### Code Example
131
1087
 
132
1088
  ```csharp
1089
+ using System;
1090
+ using UnityEngine;
1091
+
133
1092
  public static class EventHub
134
1093
  {
135
- public static event Action<int> Damage;
136
- public static void RaiseDamage(int amount) => Damage?.Invoke(amount);
1094
+ public static event Action<int> OnDamage;
1095
+ public static event Action<string> OnEnemyKilled;
1096
+ public static event Action OnGameOver;
1097
+
1098
+ public static void RaiseDamage(int amount) => OnDamage?.Invoke(amount);
1099
+ public static void RaiseEnemyKilled(string enemyType) => OnEnemyKilled?.Invoke(enemyType);
1100
+ public static void RaiseGameOver() => OnGameOver?.Invoke();
137
1101
  }
138
1102
 
139
1103
  // Producer
140
- EventHub.RaiseDamage(5);
1104
+ public class Enemy : MonoBehaviour
1105
+ {
1106
+ void Die()
1107
+ {
1108
+ EventHub.RaiseEnemyKilled("Orc");
1109
+ }
1110
+ }
141
1111
 
142
1112
  // Consumer
143
- EventHub.Damage += amount => Log(amount);
1113
+ public class UI : MonoBehaviour
1114
+ {
1115
+ void OnEnable()
1116
+ {
1117
+ EventHub.OnEnemyKilled += HandleEnemyKilled;
1118
+ }
1119
+
1120
+ void OnDisable()
1121
+ {
1122
+ EventHub.OnEnemyKilled -= HandleEnemyKilled;
1123
+ }
1124
+
1125
+ void HandleEnemyKilled(string enemyType)
1126
+ {
1127
+ Debug.Log($"Enemy killed: {enemyType}");
1128
+ }
1129
+ }
144
1130
  ```
145
1131
 
146
- Problems: everything is global, ownership unclear, no interceptors, no context (who sent/received), hard to test.
1132
+ #### What Problems It Solves
147
1133
 
148
- DxMessaging
1134
+ - ✅ **Global decoupling:** No direct references between systems
1135
+ - ✅ **Easy to add events:** Just add to static class
1136
+ - ✅ **Simple pattern:** Straightforward to implement and understand
1137
+ - ✅ **No setup:** No DI container or framework needed
149
1138
 
150
- - A single `MessageBus` with clear categories: Untargeted (global), Targeted (to one), Broadcast (from one).
151
- - Interceptors and post‑processors provide a structured pipeline.
152
- - You can create isolated buses for sub‑systems or tests (local islands), and keep a global default (`MessageHandler.MessageBus`).
1139
+ #### What Problems It Doesn't Solve Well
153
1140
 
154
- ```csharp
155
- using DxMessaging.Core;
156
- using DxMessaging.Core.MessageBus;
157
- using DxMessaging.Core.Messages;
158
- using DxMessaging.Core.Attributes;
1141
+ - ❌ **Memory leaks:** Still manual subscribe/unsubscribe (same as C# events)
1142
+ - ❌ **Global state:** Everything in one bag, hard to organize at scale
1143
+ - ❌ **Execution order:** Undefined handler invocation order
1144
+ - ❌ **Testing difficulty:** Global state makes unit testing hard
1145
+ - ❌ **Naming conflicts:** All events in same namespace, naming gets messy
1146
+ - ❌ **No validation:** No way to intercept or validate messages
1147
+ - ❌ **No observability:** Can't see who's subscribed or message history
1148
+ - ❌ **Ownership unclear:** Who manages what events?
1149
+ - ❌ **Lifecycle management:** Manual subscribe/unsubscribe required
159
1150
 
160
- [DxBroadcastMessage]
161
- [DxAutoConstructor]
162
- public readonly partial struct TookDamage { public readonly int amount; }
1151
+ #### Performance Characteristics
163
1152
 
164
- // Local bus for combat system
165
- var bus = new MessageBus();
166
- var handler = new MessageHandler(new InstanceId(1)) { active = true };
167
- var token = MessageRegistrationToken.Create(handler, bus);
1153
+ - **Good performance:** Similar to C# events (static overhead is minimal)
1154
+ - **Zero allocation:** No GC pressure for basic events
1155
+ - **Use case:** Acceptable for most scenarios
168
1156
 
169
- _ = token.RegisterBroadcastWithoutSource<TookDamage>(OnAnyDamage);
170
- void OnAnyDamage(ref InstanceId src, ref TookDamage m) => Log(src, m.amount);
1157
+ #### Learning Curve
171
1158
 
172
- // Emit within subsystem
173
- var hit = new TookDamage(5);
174
- hit.EmitGameObjectTargeted(enemyGO, bus);
175
- ```
1159
+ - **Very easy:** Just a static class with events
1160
+ - **Immediate productivity:** No new concepts
1161
+ - **Estimated learning time:** 10 minutes
1162
+
1163
+ #### Ease of Understanding
176
1164
 
177
- When to use which
1165
+ - ⭐⭐⭐⭐ (Easy initially, hard at scale)
1166
+ - Simple pattern to grasp
1167
+ - Becomes messy with 20+ events
1168
+ - Hard to track ownership and responsibilities
178
1169
 
179
- - C# events: simple, local wiring within a class or small module.
180
- - UnityEvents: quick prototypes/small scenes; prefer code for maintainability.
181
- - Unity SendMessage: legacy only; prefer `ReflexiveMessage` if you must bridge.
182
- - DxMessaging: decoupled, cross‑system flows where ordering, observability, and lifecycle safety matter.
1170
+ #### When Static Event Bus Wins
1171
+
1172
+ - You've already built one and it works
1173
+ - Very simple use cases (just need globals)
1174
+ - ✅ Small projects (<10 events)
1175
+ - ✅ No framework dependencies desired
1176
+ - ✅ Quick prototypes
1177
+
1178
+ #### When DxMessaging Wins
1179
+
1180
+ - ✅ More than 10-15 events (organization becomes important)
1181
+ - ✅ Memory leaks are a concern (automatic lifecycle management)
1182
+ - ✅ Execution order matters (priority-based handlers)
1183
+ - ✅ Need message validation/interception (interceptor pipeline)
1184
+ - ✅ Testing is important (local buses for isolation)
1185
+ - ✅ Observability needed (Inspector debugging, message history)
1186
+ - ✅ Multiple subsystems (namespacing and organization)
1187
+ - ✅ GameObject/Component targeting needed
1188
+ - ✅ Global observation needed (listen to all message instances)
1189
+ - ✅ Post-processing needed (analytics after handlers)
1190
+ - ✅ Long-term maintenance (structure prevents chaos)
1191
+
1192
+ #### Direct Comparison
1193
+
1194
+ | Aspect | Static Event Bus | DxMessaging |
1195
+ | ------------------------ | --------------------- | ---------------------- |
1196
+ | **Primary Use Case** | Global event hub | Pub/sub messaging |
1197
+ | **Unity Compatibility** | ✅ Works in Unity | ✅ Built for Unity |
1198
+ | **Dependencies** | ✅ None (custom) | ✅ Standalone |
1199
+ | **Performance** | ~50ns/call (fast) | ~60ns/call |
1200
+ | **Allocations** | ✅ Zero (basic) | ✅ Zero (structs) |
1201
+ | **Learning Curve** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐ Moderate |
1202
+ | **Setup Complexity** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐ Moderate |
1203
+ | **DI Integration** | ⚠️ Manual | ⚠️ Optional |
1204
+ | **Async/Await** | ⚠️ Manual | ⚠️ Manual |
1205
+ | **Type Safety** | ✅ Strong | ✅ Strong |
1206
+ | **Lifecycle Management** | ❌ Manual unsubscribe | ✅ Automatic |
1207
+ | **Execution Order** | ❌ Undefined | ✅ Priority-based |
1208
+ | **GameObject Targeting** | ❌ Not built-in | ✅ Built-in |
1209
+ | **Unity Integration** | ⭐ None | ⭐⭐⭐⭐⭐ Deep |
1210
+ | **Inspector Debugging** | ❌ No | ✅ History + stats |
1211
+ | **Interceptors** | ❌ Not built-in | ✅ Full pipeline |
1212
+ | **Global Observers** | ❌ Not built-in | ✅ Listen to all |
1213
+ | **Post-Processing** | ❌ Not built-in | ✅ Dedicated stage |
1214
+ | **Testability** | ⭐ Hard (global) | ⭐⭐⭐⭐⭐ Local buses |
1215
+ | **Decoupling** | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ Excellent |
1216
+ | **Organization** | ⭐⭐ One big class | ⭐⭐⭐⭐⭐ Structured |
1217
+
1218
+ **Bottom Line:** Static event buses solve global access but inherit all the problems of C# events (leaks, undefined order, no observability). DxMessaging provides the same global access with lifecycle safety, structure, and debugging tools.
1219
+
1220
+ **Migration Path:** DxMessaging can replace static event buses gradually:
1221
+
1222
+ ```csharp
1223
+ // Old static event bus
1224
+ EventHub.RaiseDamage(5);
1225
+
1226
+ // DxMessaging equivalent (global bus)
1227
+ new TookDamage(5).Emit();
1228
+
1229
+ // Or use local buses for subsystems
1230
+ var combatBus = new MessageBus();
1231
+ new TookDamage(5).Emit(combatBus);
1232
+ ```
1233
+
1234
+ ---
183
1235
 
184
1236
  ## Honest Trade-offs: What You Give Up, What You Gain
185
1237
 
186
- No solution is perfect. Here's what you sacrifice and gain with DxMessaging compared to alternatives.
1238
+ **Let's be real:** DxMessaging isn't free magic. You trade some things for others. Here's the unfiltered truth about what you gain and what you sacrifice.
1239
+
1240
+ **Bottom line first:** For game jam prototypes, C# events are faster to write. For anything you'll maintain for months, DxMessaging saves you time and sanity.
187
1241
 
188
1242
  ### Learning Curve
189
1243
 
190
1244
  #### What You Give Up
191
1245
 
192
- - ❌ Immediate productivity - There's a 1-2 day learning curve
193
- - ❌ Familiarity - Team needs to understand message types, tokens, and lifecycle
194
- - ❌ "Just works" - Requires upfront design (which message type? what priority?)
1246
+ - ❌ **Immediate productivity** - ~1-2 days to feel comfortable (reading docs, trying examples)
1247
+ - ❌ **Familiarity** - Your team knows C# events already; DxMessaging is new
1248
+ - ❌ **"Just works" intuition** - You need to think: "Which message type? What priority?"
1249
+
1250
+ **Real talk:** Your first message will take 15 minutes. By the 10th message, you'll be faster than with events.
195
1251
 
196
1252
  #### What You Gain
197
1253
 
198
- - ✅ Long-term velocity - Once learned, adding features is faster
199
- - ✅ Reduced debugging time - Clear message flow, Inspector diagnostics
200
- - ✅ Onboarding clarity - New devs see explicit message contracts instead of hidden event chains
1254
+ - ✅ **Long-term velocity** - Adding new features doesn't require touching 5 existing systems
1255
+ - ✅ **Debugging is 10x faster** - Inspector shows "what fired when" instantly
1256
+ - ✅ **Onboarding is easier** - New devs see explicit message contracts, not hidden event chains
1257
+
1258
+ **Example:** Junior dev asks "How does damage work?"
1259
+
1260
+ - **C# events:** "Uh, Player has an OnDamaged event, and HealthBar subscribes in line 47, and..."
1261
+ - **DxMessaging:** "Search for `TookDamage` message, see who emits it and who listens."
201
1262
 
202
- **Verdict:** If you're building a game jam prototype, the learning curve isn't worth it. For multi-month projects, it pays off quickly.
1263
+ ##### Verdict
1264
+
1265
+ - Game jam (1 week project): Learning curve not worth it → Stick with C# events
1266
+ - Mid-size game (1+ month): Pays off by week 2
1267
+ - Large game (6+ months): Essential for sanity
203
1268
 
204
1269
  ### Boilerplate
205
1270
 
@@ -378,31 +1443,114 @@ public void TestAchievementSystem() {
378
1443
 
379
1444
  ## Feature-by-Feature Comparison Matrix
380
1445
 
381
- | Aspect | C# Events | UnityEvents | Static Bus | DxMessaging |
382
- | -------------------- | ------------------ | -------------------- | --------------- | ------------------------- |
383
- | **Setup Complexity** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐⭐ Simple | ⭐⭐⭐ Moderate | ⭐⭐⭐ Moderate |
384
- | **Boilerplate** | ⭐⭐⭐⭐⭐ Low | ⭐⭐⭐⭐⭐ Low | ⭐⭐⭐ Medium | ⭐⭐⭐ Medium |
385
- | **Performance** | ⭐⭐⭐⭐⭐ Fastest | ⭐⭐ Slow (boxing) | ⭐⭐⭐⭐ Fast | ⭐⭐⭐⭐ Fast |
386
- | **Decoupling** | Tight | ⭐⭐ Hidden | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ Excellent |
387
- | **Lifecycle Safety** | Manual | ⭐⭐⭐ Unity-managed | Manual | ⭐⭐⭐⭐⭐ Automatic |
388
- | **Observability** | None | None | None | ⭐⭐⭐⭐⭐ Built-in |
389
- | **Execution Order** | Undefined | Undefined | Undefined | ⭐⭐⭐⭐⭐ Priority-based |
390
- | **Type Safety** | ⭐⭐⭐⭐⭐ Strong | ⭐⭐ Weak | ⭐⭐⭐ Varies | ⭐⭐⭐⭐⭐ Strong |
391
- | **Testability** | ⭐⭐ Hard | ⭐⭐ Hard | Very Hard | ⭐⭐⭐⭐⭐ Easy |
392
- | **Learning Curve** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐⭐ Low | ⭐⭐⭐ Moderate |
393
- | **Memory Safety** | Leak-prone | ⭐⭐⭐ Unity-managed | Leak-prone | ⭐⭐⭐⭐⭐ Leak-free |
394
- | **Debugging** | ⭐⭐ Hard at scale | ⭐⭐ Hard at scale | Very Hard | ⭐⭐⭐⭐⭐ Excellent |
1446
+ ### Unity Messaging Frameworks Comparison
1447
+
1448
+ | Aspect | DxMessaging | UniRx | MessagePipe | Zenject Signals |
1449
+ | ------------------------ | ---------------------------- | ------------------------ | --------------------------- | -------------------------- |
1450
+ | **Primary Use Case** | Pub/sub messaging | Stream transformations | High-perf DI messaging | DI-integrated messaging |
1451
+ | **Unity Compatibility** | Built for Unity | Built for Unity | Built for Unity | Built for Unity |
1452
+ | **Performance** | ⭐⭐⭐⭐ Good (14M) | ⭐⭐⭐⭐ Good (18M) | ⭐⭐⭐⭐⭐ Best (97M) | ⭐⭐ Moderate (2.5M) |
1453
+ | **Zero Allocations** | Yes (structs) | ⚠️ Can allocate | Yes (structs) | ⚠️ Can allocate |
1454
+ | **Unity Integration** | ⭐⭐⭐⭐⭐ Deep (lifecycle) | ⭐⭐⭐⭐ Good (UI/async) | ⭐⭐⭐ Basic (no lifecycle) | ⭐⭐⭐⭐ Good (DI-managed) |
1455
+ | **Inspector Debugging** | Yes (history + stats) | No | No | No |
1456
+ | **Execution Order** | Priority-based | Not built-in | Subscription order | Not built-in |
1457
+ | **Lifecycle Management** | Automatic (MonoBehaviour) | ⚠️ Manual dispose | ⚠️ Manual dispose | ⚠️ DI-managed |
1458
+ | **Learning Curve** | ⭐⭐⭐ Moderate | ⭐⭐ Steep (Rx paradigm) | ⭐⭐⭐⭐ Moderate (DI) | ⭐⭐ Steep (DI+Signals) |
1459
+ | **Setup Complexity** | ⭐⭐⭐⭐⭐ Plug-and-play | ⭐⭐⭐⭐⭐ Low | ⭐⭐⭐ DI setup required | ⭐⭐ Installers required |
1460
+ | **DI Integration** | ⚠️ Optional | ⚠️ Optional | ✅ First-class | ✅ Required (Zenject) |
1461
+ | **Async/Await** | ⚠️ Manual | ✅ Native (observables) | ✅ Native | ✅ Yes |
1462
+ | **Message Validation** | ✅ Interceptor pipeline | ❌ Not built-in | ⚠️ Filters (middleware) | ❌ Not built-in |
1463
+ | **GameObject Targeting** | ✅ Built-in | ❌ Not designed for | ❌ Not built-in | ❌ Not built-in |
1464
+ | **Global Observers** | ✅ Listen to all sources | ❌ Not built-in | ❌ Not built-in | ❌ Not built-in |
1465
+ | **Post-Processing** | ✅ Dedicated stage | ❌ Not built-in | ⚠️ Via filters | ❌ Not built-in |
1466
+ | **Stream Operators** | ❌ Not built-in | ✅ Extensive (LINQ) | ❌ Not built-in | ⚠️ With UniRx |
1467
+ | **Testability** | ⭐⭐⭐⭐⭐ Local buses | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ DI mocking | ⭐⭐⭐⭐⭐ DI mocking |
1468
+ | **Decoupling** | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐⭐ Excellent |
1469
+ | **Type Safety** | ⭐⭐⭐⭐⭐ Strong | ⭐⭐⭐⭐⭐ Strong | ⭐⭐⭐⭐⭐ Strong | ⭐⭐⭐⭐⭐ Strong |
1470
+ | **Dependencies** | ✅ None | ✅ None | ⚠️ MessagePipe package | ❌ Zenject required |
1471
+
1472
+ ### Traditional Approaches Comparison
1473
+
1474
+ | Aspect | C# Events | UnityEvents | SOA (GameEvent) | Static Bus | DxMessaging |
1475
+ | -------------------- | ------------------ | -------------------- | ------------------- | --------------- | ------------------------- |
1476
+ | **Setup Complexity** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐⭐ Simple | ⭐⭐ Asset creation | ⭐⭐⭐ Moderate | ⭐⭐⭐ Moderate |
1477
+ | **Boilerplate** | ⭐⭐⭐⭐⭐ Low | ⭐⭐⭐⭐⭐ Low | ⭐⭐ High | ⭐⭐⭐ Medium | ⭐⭐⭐ Medium |
1478
+ | **Performance** | ⭐⭐⭐⭐⭐ Fastest | ⭐⭐ Slow (boxing) | ⭐⭐⭐ Moderate | ⭐⭐⭐⭐ Fast | ⭐⭐⭐⭐ Fast |
1479
+ | **Decoupling** | ⭐ Tight | ⭐⭐ Hidden | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐ Good | ⭐⭐⭐⭐⭐ Excellent |
1480
+ | **Designer Control** | ⭐ None | ⭐⭐⭐⭐⭐ High | ⭐⭐⭐⭐⭐ High | ⭐ None | ⭐ None |
1481
+ | **Lifecycle Safety** | ⭐ Manual | ⭐⭐⭐ Unity-managed | ⭐⭐ Manual persist | ⭐ Manual | ⭐⭐⭐⭐⭐ Automatic |
1482
+ | **Observability** | ⭐ None | ⭐ None | ⭐ Inspector only | ⭐ None | ⭐⭐⭐⭐⭐ Built-in |
1483
+ | **Execution Order** | ⭐ Undefined | ⭐ Undefined | ⭐ Undefined | ⭐ Undefined | ⭐⭐⭐⭐⭐ Priority-based |
1484
+ | **Type Safety** | ⭐⭐⭐⭐⭐ Strong | ⭐⭐ Weak | ⭐⭐⭐ Mixed | ⭐⭐⭐ Varies | ⭐⭐⭐⭐⭐ Strong |
1485
+ | **Testability** | ⭐⭐ Hard | ⭐⭐ Hard | ⭐ Very Hard | ⭐ Very Hard | ⭐⭐⭐⭐⭐ Easy |
1486
+ | **Learning Curve** | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐⭐⭐ Minimal | ⭐⭐⭐ Moderate | ⭐⭐⭐⭐ Low | ⭐⭐⭐ Moderate |
1487
+ | **Memory Safety** | ⭐ Leak-prone | ⭐⭐⭐ Unity-managed | ⭐⭐ Asset persist | ⭐ Leak-prone | ⭐⭐⭐⭐⭐ Leak-free |
1488
+ | **Debugging** | ⭐⭐ Hard at scale | ⭐⭐ Hard at scale | ⭐⭐ Inspector-only | ⭐ Very Hard | ⭐⭐⭐⭐⭐ Excellent |
395
1489
 
396
1490
  ### Overall Verdict by Use Case
397
1491
 
398
1492
  - **Small prototype/jam:** C# Events or UnityEvents win (simplicity > all)
399
- - **Mid-size game (5-20k lines):** DxMessaging starts paying off
1493
+ - **Mid-size game (5-20k lines):** DxMessaging starts paying off (decoupling, debugging)
400
1494
  - **Large game (20k+ lines):** DxMessaging essential for maintainability
401
- - **Performance-critical (ECS, physics):** Raw delegates/native code
402
- - **UI-heavy:** DxMessaging excels (decoupled updates)
1495
+ - **Designer-driven workflow:** SOA has value (Inspector wiring) but consider maintenance costs
1496
+ - **Legacy SOA project:** Use Pattern B (keep SOs for configs, migrate events to DxMessaging)
1497
+ - **Performance-critical (millions of messages/frame):** MessagePipe wins (highest throughput)
1498
+ - **Performance-critical (Unity-specific):** DxMessaging (excellent perf + Unity integration)
1499
+ - **UI-heavy:** DxMessaging excels (decoupled updates, global observers for UI state)
1500
+ - **Complex event transformations:** UniRx wins (reactive stream operators)
1501
+ - **DI-first architecture:** MessagePipe or Zenject Signals win (DI integration)
1502
+ - **Analytics/diagnostics heavy:** DxMessaging wins (global observers, post-processors, Inspector)
1503
+ - **Need execution control:** DxMessaging wins (priorities, interceptors, ordered stages)
403
1504
 
404
1505
  ## When Each Approach ACTUALLY Wins
405
1506
 
1507
+ ### DxMessaging Wins When
1508
+
1509
+ - ✅ Unity-first projects (MonoBehaviour lifecycle integration)
1510
+ - ✅ 10+ systems that communicate (pub/sub decoupling)
1511
+ - ✅ Observability essential (Inspector debugging, message history)
1512
+ - ✅ Memory leaks are a pain point (automatic lifecycle management)
1513
+ - ✅ Cross-team development (clear message contracts)
1514
+ - ✅ Long-term maintenance (years, not weeks)
1515
+ - ✅ GameObject/Component targeting needed (Unity-specific patterns)
1516
+ - ✅ Execution order control essential (priority-based handlers)
1517
+ - ✅ Message validation/transformation needed (interceptor pipeline)
1518
+ - ✅ Global observation needed (listen to all message instances)
1519
+ - ✅ Post-processing needed (analytics, logging after handlers)
1520
+ - ✅ Late update semantics needed (timing-specific processing)
1521
+ - ✅ Teams without DI experience (no framework dependencies)
1522
+ - ✅ Want plug-and-play solution (zero dependencies, immediate use)
1523
+
1524
+ ### UniRx Wins When
1525
+
1526
+ - ✅ Simple pub/sub with minimal setup (MessageBroker is extremely easy)
1527
+ - ✅ Complex event stream transformations needed
1528
+ - ✅ Time-based operations (throttle, debounce, buffer)
1529
+ - ✅ Combining multiple input sources
1530
+ - ✅ Reactive UI data binding
1531
+ - ✅ Team familiar with reactive programming
1532
+ - ✅ Need LINQ-style query operators on events
1533
+ - ✅ Async operations with cancellation and composition
1534
+
1535
+ ### MessagePipe Wins When
1536
+
1537
+ - ✅ Performance is THE priority (highest throughput)
1538
+ - ✅ Already using DI (VContainer, Zenject, etc.)
1539
+ - ✅ Cross-platform .NET projects (not Unity-only)
1540
+ - ✅ Need native async/await support
1541
+ - ✅ Large-scale projects with DI architecture
1542
+ - ✅ Want compile-time leak detection (Roslyn analyzer)
1543
+ - ✅ High message frequency (thousands/frame)
1544
+
1545
+ ### Zenject Signals Win When
1546
+
1547
+ - ✅ Already using Zenject for dependency injection
1548
+ - ✅ Testability through DI is critical
1549
+ - ✅ Need subscriber validation (ensure handlers exist)
1550
+ - ✅ Team experienced with Zenject
1551
+ - ✅ Want DI-managed lifecycle
1552
+ - ✅ Integration with existing Zenject architecture
1553
+
406
1554
  ### C# Events Win When
407
1555
 
408
1556
  - ✅ You need return values or out parameters
@@ -416,19 +1564,19 @@ public void TestAchievementSystem() {
416
1564
  - ✅ Rapid prototyping with prefabs
417
1565
  - ✅ Very simple games (mobile casual, hyper-casual)
418
1566
 
1567
+ ### SOA (GameEvent/Variables) Wins When
1568
+
1569
+ - ✅ Designers must create and wire events without touching code
1570
+ - ✅ Team is already heavily invested in SOA with many existing assets
1571
+ - ✅ Designer empowerment is the absolute top priority
1572
+ - ⚠️ **BUT:** Consider migration costs and maintainability issues (see [Anti-SOA critique](https://github.com/cathei/AntiScriptableObjectArchitecture))
1573
+ - ⚠️ **Alternative:** Use ScriptableObjects for configs only + DxMessaging for events (Pattern B in [SOA Guide](Patterns.md#14-compatibility-with-scriptable-object-architecture-soa))
1574
+
419
1575
  ### Static Event Bus Wins When
420
1576
 
421
1577
  - ✅ You've already built one and it works
422
1578
  - ✅ Very simple use cases (just need globals)
423
1579
 
424
- ### DxMessaging Wins When
425
-
426
- - ✅ 10+ systems that communicate
427
- - ✅ You need observability (debugging complex flows)
428
- - ✅ Memory leaks are a pain point
429
- - ✅ Cross-team development (clear contracts)
430
- - ✅ Long-term maintenance (years, not weeks)
431
-
432
1580
  ## Cost-Benefit Summary
433
1581
 
434
1582
  ### Costs
@@ -449,17 +1597,80 @@ public void TestAchievementSystem() {
449
1597
 
450
1598
  **Break-even point:** Usually around 10-20 hours into a project, when event management becomes painful.
451
1599
 
452
- ## Making the Decision
1600
+ ## Making the Decision (Be Honest With Yourself)
1601
+
1602
+ ### Answer these questions honestly
1603
+
1604
+ ### 1. Project Lifespan?
1605
+
1606
+ - **<1 week (game jam):** Skip DxMessaging → Use C# events or direct calls
1607
+ - **1-4 weeks (prototype):** Maybe → If you plan to continue, use DxMessaging
1608
+ - **1+ months (real project):** Yes → DxMessaging will save you time
1609
+ - **6+ months (production):** Absolutely → You'll thank yourself later
1610
+
1611
+ ### 2. Team Size?
1612
+
1613
+ - **Solo dev:** Optional → Depends on project complexity
1614
+ - **2-3 devs:** Valuable → Reduces communication overhead
1615
+ - **4+ devs:** Highly recommended → Clear contracts between systems
1616
+ - **Remote/distributed team:** Essential → Explicit message contracts prevent miscommunication
1617
+
1618
+ ### 3. Codebase Size?
1619
+
1620
+ - **<1k lines:** Skip it → Direct method calls are fine
1621
+ - **1k-5k lines:** Consider it → If you're growing fast
1622
+ - **5k-20k lines:** Recommended → Coupling becomes painful
1623
+ - **20k+ lines:** Absolutely → Refactoring without it is a nightmare
1624
+
1625
+ ### 4. How Many Systems Need to Communicate?
1626
+
1627
+ - **1-2 systems:** Skip → Just call methods directly
1628
+ - **3-5 systems:** Consider → If they don't share references
1629
+ - **6-10 systems:** Recommended → Coupling becomes unmanageable
1630
+ - **10+ systems:** Essential → You're drowning in SerializeFields
1631
+
1632
+ ### 5. Have You Had Memory Leaks From Forgotten Unsubscribes?
1633
+
1634
+ - **Never:** Lucky you! Optional
1635
+ - **Once or twice:** Consider it → Prevention is cheaper than debugging
1636
+ - **Multiple times:** Absolutely → Stop wasting time on this
1637
+ - **Currently debugging one:** Drop everything and adopt DxMessaging now
1638
+
1639
+ ### 6. How Often Do You Debug "What Fired When?"
1640
+
1641
+ - **Never:** You're either lying or working on tiny projects
1642
+ - **Rarely:** Optional, but would help
1643
+ - **Monthly:** Recommended → Inspector diagnostics will save hours
1644
+ - **Weekly:** Absolutely → You're wasting too much time
1645
+
1646
+ ### Quick Decision Matrix
1647
+
1648
+ ```text
1649
+ Game Jam → C# Events (speed over safety)
1650
+ Prototype → DxMessaging IF continuing, else C# Events
1651
+ Production → DxMessaging (unless <1k lines)
1652
+ Legacy codebase → Migrate gradually (see Migration Guide)
1653
+ ```
1654
+
1655
+ ### The Real Question
1656
+
1657
+ #### "Will this project still exist in 3 months?"
1658
+
1659
+ - **No:** C# events are fine
1660
+ - **Yes:** Use DxMessaging
1661
+
1662
+ ##### "Will anyone else work on this code?"
1663
+
1664
+ - **No:** C# events might be okay
1665
+ - **Yes:** Use DxMessaging (future you counts as "someone else")
453
1666
 
454
- Ask yourself:
1667
+ ### Rule of Thumb
455
1668
 
456
- 1. **Project lifespan?** <1 week → Skip DxMessaging | >1 month → Consider it
457
- 1. **Team size?** Solo → Optional | 3+ devs → Highly valuable
458
- 1. **Codebase size?** <5k lines → Optional | >10k lines → Recommended
459
- 1. **Event complexity?** <10 events → Skip | >20 events → Use DxMessaging
460
- 1. **Memory leak history?** None → Optional | Frequent → MUST HAVE
1669
+ If you're reading this and thinking:
461
1670
 
462
- **Rule of thumb:** If you're reading this and thinking "I've experienced these pain points," DxMessaging will help. If you're thinking "I've never had these problems," maybe you don't need it yet.
1671
+ - **"I've experienced these pain points"** DxMessaging will help
1672
+ - **"This seems like overkill"** → You probably don't need it yet
1673
+ - **"I need this yesterday"** → Welcome home 🚀
463
1674
 
464
1675
  See also
465
1676