com.wallstop-studios.dxmessaging 2.0.0 → 2.1.1

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 (183) 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 +1229 -146
  10. package/Docs/Compatibility.md +27 -0
  11. package/Docs/DesignAndArchitecture.md +41 -34
  12. package/Docs/EmitShorthands.md +34 -0
  13. package/Docs/Helpers.md +91 -76
  14. package/Docs/Index.md +28 -25
  15. package/Docs/Install.md +29 -6
  16. package/Docs/Integrations/Reflex.md +292 -0
  17. package/{package-lock.json.meta → Docs/Integrations/Reflex.md.meta} +1 -1
  18. package/Docs/Integrations/VContainer.md +324 -0
  19. package/Docs/Integrations/VContainer.md.meta +7 -0
  20. package/Docs/Integrations/Zenject.md +333 -0
  21. package/Docs/Integrations/Zenject.md.meta +7 -0
  22. package/{Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.pdb.meta → Docs/Integrations.meta} +2 -1
  23. package/Docs/InterceptorsAndOrdering.md +371 -17
  24. package/Docs/ListeningPatterns.md +206 -0
  25. package/Docs/MessageBusProviders.md +496 -0
  26. package/Docs/MessageBusProviders.md.meta +7 -0
  27. package/Docs/MessageTypes.md +27 -0
  28. package/Docs/MigrationGuide.md +45 -0
  29. package/Docs/Patterns.md +286 -0
  30. package/Docs/Performance.md +9 -9
  31. package/Docs/QuickReference.md +31 -0
  32. package/Docs/QuickStart.md +1 -2
  33. package/Docs/RuntimeConfiguration.md +407 -0
  34. package/Docs/RuntimeConfiguration.md.meta +7 -0
  35. package/Docs/UnityIntegration.md +3 -1
  36. package/Docs/VisualGuide.md +206 -157
  37. package/Editor/CustomEditors/MessagingComponentEditor.cs +15 -6
  38. package/README.md +148 -26
  39. package/Runtime/AssemblyInfo.cs +4 -0
  40. package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs +1 -1
  41. package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs +1 -1
  42. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +1 -1
  43. package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs +1 -1
  44. package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs +1 -1
  45. package/Runtime/Core/Extensions/MessageBusExtensions.cs +253 -0
  46. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +12 -0
  47. package/Runtime/Core/Extensions/MessageExtensions.cs +137 -89
  48. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs +23 -0
  49. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +11 -0
  50. package/Runtime/Core/MessageBus/IMessageBusProvider.cs +14 -0
  51. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +11 -0
  52. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +18 -0
  53. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +11 -0
  54. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs +26 -0
  55. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +11 -0
  56. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +383 -0
  57. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +11 -0
  58. package/Runtime/Core/MessageHandler.cs +198 -27
  59. package/Runtime/Core/MessageRegistrationToken.cs +67 -25
  60. package/Runtime/Core/Messages/IBroadcastMessage.cs +1 -1
  61. package/Runtime/Core/Messages/ITargetedMessage.cs +1 -1
  62. package/Runtime/Core/Messages/IUntargetedMessage.cs +1 -1
  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 +175 -9
  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 +79 -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 +65 -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 +5 -7
  118. package/Samples~/Mini Combat/Walkthrough.md +18 -24
  119. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs +12 -2
  120. package/Samples~/UI Buttons + Inspector/README.md.meta +7 -0
  121. package/Tests/Runtime/Benchmarks/BenchmarkSession.cs +444 -0
  122. package/Tests/Runtime/Benchmarks/BenchmarkSession.cs.meta +11 -0
  123. package/Tests/Runtime/Benchmarks/BenchmarkTestBase.cs +94 -0
  124. package/Tests/Runtime/Benchmarks/BenchmarkTestBase.cs.meta +11 -0
  125. package/Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs +395 -0
  126. package/Tests/Runtime/Benchmarks/ComparisonPerformanceTests.cs.meta +11 -0
  127. package/Tests/Runtime/Benchmarks/PerformanceTests.cs +77 -429
  128. package/Tests/Runtime/Benchmarks/ProviderResolutionBenchmarks.cs +142 -0
  129. package/Tests/Runtime/Benchmarks/ProviderResolutionBenchmarks.cs.meta +12 -0
  130. package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef +50 -0
  131. package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef.meta +7 -0
  132. package/Tests/Runtime/Core/DefaultBusFallbackTests.cs +333 -0
  133. package/Tests/Runtime/Core/DefaultBusFallbackTests.cs.meta +11 -0
  134. package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs +278 -0
  135. package/Tests/Runtime/Core/Extensions/MessageBusExtensionsTests.cs.meta +11 -0
  136. package/Tests/Runtime/Core/Extensions/MessageExtensionsProviderTests.cs +289 -0
  137. package/Tests/Runtime/Core/Extensions/MessageExtensionsProviderTests.cs.meta +11 -0
  138. package/Tests/Runtime/Core/Extensions.meta +8 -0
  139. package/Tests/Runtime/Core/IntegrationShimSmokeTests.cs +57 -0
  140. package/Tests/Runtime/Core/IntegrationShimSmokeTests.cs.meta +11 -0
  141. package/Tests/Runtime/Core/MessageHandlerGlobalBusTests.cs +219 -0
  142. package/Tests/Runtime/Core/MessageHandlerGlobalBusTests.cs.meta +11 -0
  143. package/Tests/Runtime/Core/MessageRegistrationBuilderTests.cs +204 -0
  144. package/Tests/Runtime/Core/MessageRegistrationBuilderTests.cs.meta +11 -0
  145. package/Tests/Runtime/Core/MessagingTestBase.cs +4 -4
  146. package/Tests/Runtime/Core/NominalTests.cs +2 -2
  147. package/Tests/Runtime/Core/TypedShorthandTests.cs +2 -2
  148. package/Tests/Runtime/Core/UntargetedEquivalenceTests.cs +1 -1
  149. package/Tests/Runtime/Core/UntargetedPrefreezeTests.cs +2 -4
  150. package/Tests/Runtime/Integrations/Reflex/ReflexIntegrationTests.cs +162 -0
  151. package/Tests/Runtime/Integrations/Reflex/ReflexIntegrationTests.cs.meta +11 -0
  152. package/Tests/Runtime/Integrations/Reflex/Resources/ReflexSettings.asset +16 -0
  153. package/Tests/Runtime/Integrations/Reflex/Resources/ReflexSettings.asset.meta +8 -0
  154. package/Tests/Runtime/Integrations/Reflex/Resources.meta +8 -0
  155. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef +27 -0
  156. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef.meta +7 -0
  157. package/Tests/Runtime/Integrations/Reflex.meta +8 -0
  158. package/Tests/Runtime/Integrations/VContainer/VContainerIntegrationTests.cs +140 -0
  159. package/Tests/Runtime/Integrations/VContainer/VContainerIntegrationTests.cs.meta +11 -0
  160. package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef +37 -0
  161. package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef.meta +7 -0
  162. package/Tests/Runtime/Integrations/VContainer.meta +8 -0
  163. package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef +37 -0
  164. package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef.meta +7 -0
  165. package/Tests/Runtime/Integrations/Zenject/ZenjectIntegrationTests.cs +140 -0
  166. package/Tests/Runtime/Integrations/Zenject/ZenjectIntegrationTests.cs.meta +11 -0
  167. package/Tests/Runtime/Integrations/Zenject.meta +8 -0
  168. package/Tests/Runtime/Integrations.meta +8 -0
  169. package/Tests/Runtime/Scripts/Components/EmptyMessageAwareComponent.cs +1 -1
  170. package/Tests/Runtime/Scripts/Components/GenericMessageAwareComponent.cs +1 -1
  171. package/Tests/Runtime/Scripts/Components/SimpleMessageAwareComponent.cs +1 -1
  172. package/Tests/Runtime/TestUtilities/UnityFixtureBase.cs +64 -0
  173. package/Tests/Runtime/TestUtilities/UnityFixtureBase.cs.meta +12 -0
  174. package/Tests/Runtime/TestUtilities.meta +9 -0
  175. package/Tests/Runtime/Unity/MessageBusProviderAssetTests.cs +57 -0
  176. package/Tests/Runtime/Unity/MessageBusProviderAssetTests.cs.meta +11 -0
  177. package/Tests/Runtime/Unity/MessageBusProviderHandleTests.cs +107 -0
  178. package/Tests/Runtime/Unity/MessageBusProviderHandleTests.cs.meta +12 -0
  179. package/Tests/Runtime/Unity/MessagingComponentProviderIntegrationTests.cs +210 -0
  180. package/Tests/Runtime/Unity/MessagingComponentProviderIntegrationTests.cs.meta +12 -0
  181. package/Tests/Runtime/Unity.meta +9 -0
  182. package/Tests/Runtime/WallstopStudios.DxMessaging.Tests.Runtime.asmdef +3 -1
  183. package/package.json +1 -1
package/Docs/Patterns.md CHANGED
@@ -42,6 +42,7 @@
42
42
  | Handle persistent systems across scene loads | [Pattern: Cross-Scene Persistent](#pattern-cross-scene-persistent-systems) |
43
43
  | See what's happening (debug message flow) | [Pattern 11: Diagnostics](#11-diagnostics-and-tuning) |
44
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) |
45
46
 
46
47
  ---
47
48
 
@@ -64,6 +65,7 @@
64
65
  - [Global Accept-All Handlers](#10-global-accept-all-handlers)
65
66
  - [Diagnostics and Tuning](#11-diagnostics-and-tuning)
66
67
  - [Testing](#12-testing)
68
+ - [Compatibility with Scriptable Object Architecture (SOA)](#14-compatibility-with-scriptable-object-architecture-soa)
67
69
 
68
70
  ### Real-World Scale Patterns
69
71
 
@@ -768,6 +770,290 @@ public class MatchStats : MessageAwareComponent {
768
770
 
769
771
  ---
770
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 partial 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
+
771
1057
  ## Related Documentation
772
1058
 
773
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,747,000 | Yes |
18
- | DxMessaging (GameObject) - Normal | 8,412,800 | No |
19
- | DxMessaging (Component) - Normal | 8,406,200 | No |
20
- | DxMessaging (GameObject) - No-Copy | 9,358,200 | No |
21
- | DxMessaging (Component) - No-Copy | 9,177,800 | No |
22
- | DxMessaging (Untargeted) - No-Copy | 14,922,800 | No |
23
- | Reflexive (One Argument) | 2,832,800 | No |
24
- | Reflexive (Two Arguments) | 2,371,200 | No |
25
- | Reflexive (Three Arguments) | 2,350,600 | No |
17
+ | Unity | 2,628,000 | Yes |
18
+ | DxMessaging (GameObject) - Normal | 8,208,000 | No |
19
+ | DxMessaging (Component) - Normal | 8,210,000 | No |
20
+ | DxMessaging (GameObject) - No-Copy | 9,412,000 | No |
21
+ | DxMessaging (Component) - No-Copy | 9,354,000 | No |
22
+ | DxMessaging (Untargeted) - No-Copy | 14,812,000 | No |
23
+ | Reflexive (One Argument) | 2,856,000 | No |
24
+ | Reflexive (Two Arguments) | 2,372,000 | No |
25
+ | Reflexive (Three Arguments) | 2,382,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
@@ -60,8 +60,7 @@ public readonly partial struct TookDamage
60
60
  public readonly int amount;
61
61
  }
62
62
 
63
- // Performance option: generic interfaces on structs (zero boxing)
64
- // public readonly struct Heal : ITargetedMessage<Heal> { public readonly int amount; public Heal(int amount) { this.amount = amount; } }
63
+ // Performance option: keep [DxTargetedMessage] on a readonly struct to stay zero-boxing friendly, and drop [DxAutoConstructor] only if you need custom constructor logic.
65
64
 
66
65
  // Optional parameters with custom defaults
67
66
  [DxTargetedMessage]