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,21 +1,64 @@
1
- # UI Buttons + Inspector (Sample)
1
+ # UI Buttons + Inspector Sample
2
2
 
3
- Make a Unity UI Button send a message with a single OnClick hook — no plumbing, no scene-wide singletons, and easy to understand even if you’re new to event systems.
3
+ > **Perfect for:** Seeing DxMessaging work with Unity UI in 60 seconds
4
4
 
5
- ## What Youll Learn
5
+ ## What You'll Learn (No Code Required!)
6
6
 
7
- - How to wire a UI Button to emit a DxMessaging message from the Inspector.
8
- - How to observe messages in the Console with a tiny listener component.
9
- - How to turn on diagnostics to see message traffic while you click.
7
+ **Stop writing button click handlers!** This sample shows you how to:
10
8
 
11
- ## Import The Sample (60 seconds)
9
+ 1. **Wire UI Buttons to messages** - directly from the Inspector (drag & drop)
10
+ 2. **See messages flow in real-time** - watch the Console as you click
11
+ 3. **Enable diagnostics** - see every message with timestamps and payloads
12
12
 
13
- 1. Open `Window > Package Manager` in Unity.
14
- 1. Select `com.wallstop-studios.dxmessaging`.
15
- 1. In Samples, import “UI Buttons + Inspector”.
16
- 1. Open the sample scene (created under your project’s `Assets/Samples/...`).
13
+ **Why this matters:** You can add new systems (analytics, audio, achievements) that react to button clicks WITHOUT touching existing code.
17
14
 
18
- That’s it — the scene includes everything you need to click and see messages.
15
+ ---
16
+
17
+ ## The Power Move
18
+
19
+ ### Before DxMessaging:
20
+
21
+ ```csharp
22
+ public class PlayButton : MonoBehaviour {
23
+ [SerializeField] private GameManager gameManager; // Coupling
24
+ [SerializeField] private AudioManager audio; // Coupling
25
+ [SerializeField] private Analytics analytics; // Coupling
26
+
27
+ public void OnClick() {
28
+ gameManager.StartGame(); // Manual call
29
+ audio.PlayClickSound(); // Manual call
30
+ analytics.LogButtonClick(); // Manual call
31
+ }
32
+ }
33
+ ```
34
+
35
+ #### Every new system = update PlayButton script. Exhausting.
36
+
37
+ ##### With DxMessaging:
38
+
39
+ ```csharp
40
+ public class PlayButton : MonoBehaviour {
41
+ public void OnClick() {
42
+ new ButtonClicked("Play").Emit();
43
+ // Done! Everything reacts automatically.
44
+ }
45
+ }
46
+ ```
47
+
48
+ ###### GameManager, Audio, and Analytics listen independently. Zero coupling.
49
+
50
+ ## Import & Run (30 Seconds)
51
+
52
+ ### Want to see it immediately?
53
+
54
+ 1. **Window → Package Manager**
55
+ 2. **Find DxMessaging** → Scroll to **Samples**
56
+ 3. **"UI Buttons + Inspector"** → Click **Import**
57
+ 4. **Navigate to** Assets/Samples/.../UI Buttons + Inspector/
58
+ 5. **Open the scene** → **Press Play** 🎮
59
+ 6. **Click the buttons** → Watch Console logs
60
+
61
+ **Done!** You're seeing DxMessaging in action.
19
62
 
20
63
  ## Click-To-Message: The Quick Path
21
64
 
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 5e5eb4ac04b570c40984987663b055c6
3
+ TextScriptImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
@@ -0,0 +1,444 @@
1
+ namespace DxMessaging.Tests.Runtime.Benchmarks
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Globalization;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using System.Runtime.InteropServices;
9
+ using System.Text;
10
+ using System.Text.RegularExpressions;
11
+ using UnityEngine;
12
+
13
+ internal readonly struct BenchmarkEntry
14
+ {
15
+ internal BenchmarkEntry(string messageTech, long operationsPerSecond, bool allocating)
16
+ {
17
+ MessageTech = messageTech;
18
+ OperationsPerSecond = operationsPerSecond;
19
+ Allocating = allocating;
20
+ }
21
+
22
+ internal string MessageTech { get; }
23
+
24
+ internal long OperationsPerSecond { get; }
25
+
26
+ internal bool Allocating { get; }
27
+ }
28
+
29
+ public sealed class BenchmarkSession : IDisposable
30
+ {
31
+ private readonly string _sectionName;
32
+ private readonly string _headingPrefix;
33
+ private readonly IReadOnlyList<Func<string>> _docPathResolvers;
34
+ private readonly List<BenchmarkEntry> _entries = new();
35
+
36
+ internal BenchmarkSession(
37
+ string sectionName,
38
+ string headingPrefix,
39
+ IReadOnlyList<Func<string>> docPathResolvers
40
+ )
41
+ {
42
+ _sectionName = sectionName;
43
+ _headingPrefix = headingPrefix;
44
+ _docPathResolvers = docPathResolvers;
45
+
46
+ Debug.Log("| Message Tech | Operations / Second | Allocations? |");
47
+ Debug.Log("| ------------ | ------------------- | ------------ | ");
48
+ }
49
+
50
+ internal void Record(string messageTech, long operationsPerSecond, bool allocating)
51
+ {
52
+ string formattedOperations = operationsPerSecond.ToString(
53
+ "N0",
54
+ CultureInfo.InvariantCulture
55
+ );
56
+ Debug.Log($"| {messageTech} | {formattedOperations} | {(allocating ? "Yes" : "No")} |");
57
+ _entries.Add(new BenchmarkEntry(messageTech, operationsPerSecond, allocating));
58
+ }
59
+
60
+ public void Dispose()
61
+ {
62
+ try
63
+ {
64
+ if (_entries.Count == 0)
65
+ {
66
+ return;
67
+ }
68
+
69
+ if (string.IsNullOrEmpty(_sectionName))
70
+ {
71
+ Debug.LogWarning(
72
+ "Skipping benchmark documentation update because the section name could not be determined."
73
+ );
74
+ return;
75
+ }
76
+
77
+ BenchmarkDocumentation.TryWriteBenchmarks(
78
+ _sectionName!,
79
+ _headingPrefix,
80
+ _entries,
81
+ _docPathResolvers
82
+ );
83
+ }
84
+ finally
85
+ {
86
+ _entries.Clear();
87
+ }
88
+ }
89
+ }
90
+
91
+ internal static class BenchmarkDocumentation
92
+ {
93
+ private const string PerformanceHeader =
94
+ "# Performance Benchmarks\n\n"
95
+ + "This page is auto-updated by the Unity PlayMode benchmark tests in `Tests/Runtime/Benchmarks/PerformanceTests.cs`.\n\n"
96
+ + "How it works:\n\n"
97
+ + "- Run PlayMode tests locally in your Unity project that references this package.\n"
98
+ + "- The benchmark test writes an OS-specific section below with a markdown table.\n"
99
+ + "- CI runs skip writing to avoid noisy diffs.\n\n"
100
+ + "See also: `Docs/DesignAndArchitecture.md#performance-optimizations` for design details.\n";
101
+
102
+ internal static string GetOperatingSystemSection()
103
+ {
104
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
105
+ {
106
+ return "Windows";
107
+ }
108
+
109
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
110
+ {
111
+ return "macOS";
112
+ }
113
+
114
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
115
+ {
116
+ return "Linux";
117
+ }
118
+
119
+ return null;
120
+ }
121
+
122
+ private static bool IsRunningInContinuousIntegration()
123
+ {
124
+ if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITHUB_ACTIONS")))
125
+ {
126
+ return true;
127
+ }
128
+
129
+ if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CI")))
130
+ {
131
+ return true;
132
+ }
133
+
134
+ if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("JENKINS_URL")))
135
+ {
136
+ return true;
137
+ }
138
+
139
+ if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("GITLAB_CI")))
140
+ {
141
+ return true;
142
+ }
143
+
144
+ return false;
145
+ }
146
+
147
+ internal static void TryWriteBenchmarks(
148
+ string sectionName,
149
+ string headingPrefix,
150
+ IReadOnlyList<BenchmarkEntry> entries,
151
+ IReadOnlyList<Func<string>> docPathResolvers
152
+ )
153
+ {
154
+ if (entries.Count == 0)
155
+ {
156
+ return;
157
+ }
158
+
159
+ if (IsRunningInContinuousIntegration())
160
+ {
161
+ Debug.Log(
162
+ $"Skipping benchmarks update for {sectionName} because the benchmarks are running in CI."
163
+ );
164
+ return;
165
+ }
166
+
167
+ string docPath = ResolveDocPath(docPathResolvers);
168
+ if (string.IsNullOrEmpty(docPath))
169
+ {
170
+ Debug.LogWarning(
171
+ $"Skipping benchmarks update for {sectionName} because no documentation target could be located."
172
+ );
173
+ return;
174
+ }
175
+
176
+ try
177
+ {
178
+ string table = BuildTable(entries);
179
+ string originalContent = File.Exists(docPath)
180
+ ? File.ReadAllText(docPath)
181
+ : string.Empty;
182
+ string updatedContent = ReplaceSection(
183
+ originalContent,
184
+ sectionName,
185
+ headingPrefix,
186
+ table
187
+ );
188
+
189
+ if (string.Equals(originalContent, updatedContent, StringComparison.Ordinal))
190
+ {
191
+ Debug.Log(
192
+ $"Benchmark section for {sectionName} is already up to date in {docPath}."
193
+ );
194
+ return;
195
+ }
196
+
197
+ File.WriteAllText(docPath, updatedContent, new UTF8Encoding(false));
198
+ Debug.Log($"Updated benchmarks for {sectionName} in {docPath}.");
199
+ }
200
+ catch (Exception exception)
201
+ {
202
+ Debug.LogWarning(
203
+ $"Failed to update benchmark documentation for {sectionName}: {exception}"
204
+ );
205
+ }
206
+ }
207
+
208
+ internal static string TryFindPerformanceDocPath()
209
+ {
210
+ return TryFindDocPath(Path.Combine("Docs", "Performance.md"), PerformanceHeader);
211
+ }
212
+
213
+ internal static string TryFindComparisonsDocPath()
214
+ {
215
+ return TryFindDocPath(Path.Combine("Docs", "Comparisons.md"));
216
+ }
217
+
218
+ internal static string TryFindReadmePath()
219
+ {
220
+ return TrySearchForFile("README.md");
221
+ }
222
+
223
+ private static string BuildTable(IReadOnlyList<BenchmarkEntry> entries)
224
+ {
225
+ StringBuilder builder = new();
226
+ builder.AppendLine("| Message Tech | Operations / Second | Allocations? |");
227
+ builder.AppendLine("| ------------ | ------------------- | ------------ |");
228
+
229
+ foreach (BenchmarkEntry entry in entries)
230
+ {
231
+ builder
232
+ .Append("| ")
233
+ .Append(entry.MessageTech)
234
+ .Append(" | ")
235
+ .Append(entry.OperationsPerSecond.ToString("N0", CultureInfo.InvariantCulture))
236
+ .Append(" | ")
237
+ .Append(entry.Allocating ? "Yes" : "No")
238
+ .AppendLine(" |");
239
+ }
240
+
241
+ return builder.ToString().TrimEnd('\r', '\n');
242
+ }
243
+
244
+ private static string ReplaceSection(
245
+ string content,
246
+ string sectionName,
247
+ string headingPrefix,
248
+ string tableContent
249
+ )
250
+ {
251
+ string replacement = $"{headingPrefix}{sectionName}\n\n{tableContent}\n";
252
+ int headingLevel = CountHeadingLevel(headingPrefix);
253
+ string stopPattern = headingLevel <= 1 ? "#" : $"#{{1,{headingLevel}}}";
254
+ string pattern =
255
+ $@"^{Regex.Escape(headingPrefix)}{Regex.Escape(sectionName)}[^\S\r\n]*(?:\r?\n|$)[\s\S]*?(?=^\s*{stopPattern}\s|\Z)";
256
+ Regex regex = new(pattern, RegexOptions.CultureInvariant | RegexOptions.Multiline);
257
+ string updated = regex.Replace(content, replacement, 1);
258
+
259
+ if (string.Equals(content, updated, StringComparison.Ordinal))
260
+ {
261
+ string prefix = content.EndsWith("\n", StringComparison.Ordinal)
262
+ ? string.Empty
263
+ : "\n";
264
+ updated = $"{content}{prefix}{replacement}";
265
+ }
266
+
267
+ if (!updated.EndsWith("\n", StringComparison.Ordinal))
268
+ {
269
+ updated += "\n";
270
+ }
271
+
272
+ return updated;
273
+ }
274
+
275
+ private static int CountHeadingLevel(string headingPrefix)
276
+ {
277
+ int level = headingPrefix.Count(c => c == '#');
278
+ return level > 0 ? level : 1;
279
+ }
280
+
281
+ private static string ResolveDocPath(IReadOnlyList<Func<string>> resolvers)
282
+ {
283
+ foreach (Func<string> resolver in resolvers)
284
+ {
285
+ if (resolver == null)
286
+ {
287
+ continue;
288
+ }
289
+
290
+ try
291
+ {
292
+ string path = resolver();
293
+ if (!string.IsNullOrEmpty(path))
294
+ {
295
+ return path;
296
+ }
297
+ }
298
+ catch (Exception exception)
299
+ {
300
+ Debug.LogWarning($"Failed to resolve documentation path: {exception}");
301
+ }
302
+ }
303
+
304
+ return null;
305
+ }
306
+
307
+ private static string TryFindDocPath(string relativePath, string seedContent = null)
308
+ {
309
+ string discovered = TrySearchForFile(relativePath);
310
+ if (!string.IsNullOrEmpty(discovered))
311
+ {
312
+ return discovered;
313
+ }
314
+
315
+ string readmePath = TryFindReadmePath();
316
+ if (!string.IsNullOrEmpty(readmePath))
317
+ {
318
+ string candidate = TryEnsureFileAdjacent(readmePath, relativePath, seedContent);
319
+ if (!string.IsNullOrEmpty(candidate))
320
+ {
321
+ return candidate;
322
+ }
323
+ }
324
+
325
+ return TryEnsureFileAdjacent(
326
+ Directory.GetCurrentDirectory(),
327
+ relativePath,
328
+ seedContent
329
+ );
330
+ }
331
+
332
+ private static string TrySearchForFile(string relativePath)
333
+ {
334
+ string current = Directory.GetCurrentDirectory();
335
+ while (!string.IsNullOrEmpty(current))
336
+ {
337
+ string candidate = Path.Combine(current, relativePath);
338
+ if (File.Exists(candidate))
339
+ {
340
+ return candidate;
341
+ }
342
+
343
+ string packageCandidate = Path.Combine(
344
+ current,
345
+ "Packages",
346
+ "com.wallstop-studios.dxmessaging",
347
+ relativePath
348
+ );
349
+ if (File.Exists(packageCandidate))
350
+ {
351
+ return packageCandidate;
352
+ }
353
+
354
+ DirectoryInfo parent = Directory.GetParent(current);
355
+ current = parent?.FullName;
356
+ }
357
+
358
+ string assemblyLocation = typeof(BenchmarkDocumentation).Assembly.Location;
359
+ if (string.IsNullOrEmpty(assemblyLocation))
360
+ {
361
+ return null;
362
+ }
363
+
364
+ string assemblyDirectory = Path.GetDirectoryName(assemblyLocation);
365
+ if (string.IsNullOrEmpty(assemblyDirectory))
366
+ {
367
+ return null;
368
+ }
369
+
370
+ DirectoryInfo directory = new(assemblyDirectory);
371
+ while (directory != null)
372
+ {
373
+ string candidate = Path.Combine(directory.FullName, relativePath);
374
+ if (File.Exists(candidate))
375
+ {
376
+ return candidate;
377
+ }
378
+
379
+ string packageCandidate = Path.Combine(
380
+ directory.FullName,
381
+ "Packages",
382
+ "com.wallstop-studios.dxmessaging",
383
+ relativePath
384
+ );
385
+ if (File.Exists(packageCandidate))
386
+ {
387
+ return packageCandidate;
388
+ }
389
+
390
+ directory = directory.Parent;
391
+ }
392
+
393
+ return null;
394
+ }
395
+
396
+ private static string TryEnsureFileAdjacent(
397
+ string anchorPath,
398
+ string relativePath,
399
+ string seedContent
400
+ )
401
+ {
402
+ string baseDirectory = File.Exists(anchorPath)
403
+ ? Path.GetDirectoryName(anchorPath) ?? string.Empty
404
+ : anchorPath;
405
+ if (string.IsNullOrEmpty(baseDirectory))
406
+ {
407
+ return null;
408
+ }
409
+
410
+ string candidate = Path.Combine(baseDirectory, relativePath);
411
+ string directory = Path.GetDirectoryName(candidate);
412
+ try
413
+ {
414
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
415
+ {
416
+ Directory.CreateDirectory(directory);
417
+ }
418
+
419
+ if (File.Exists(candidate))
420
+ {
421
+ return candidate;
422
+ }
423
+
424
+ if (!string.IsNullOrEmpty(seedContent))
425
+ {
426
+ File.WriteAllText(candidate, seedContent, new UTF8Encoding(false));
427
+ }
428
+ else
429
+ {
430
+ File.WriteAllText(candidate, string.Empty, new UTF8Encoding(false));
431
+ }
432
+
433
+ return candidate;
434
+ }
435
+ catch (Exception exception)
436
+ {
437
+ Debug.LogWarning(
438
+ $"Failed to create documentation file at {candidate}: {exception}"
439
+ );
440
+ return null;
441
+ }
442
+ }
443
+ }
444
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: a4f22c4eaa83480cb7a50f9702c19454
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -0,0 +1,94 @@
1
+ namespace DxMessaging.Tests.Runtime.Benchmarks
2
+ {
3
+ using System;
4
+ using System.Globalization;
5
+ using DxMessaging.Tests.Runtime.Core;
6
+ using Scripts.Components;
7
+ using UnityEngine;
8
+ using Object = UnityEngine.Object;
9
+
10
+ public abstract class BenchmarkTestBase : MessagingTestBase
11
+ {
12
+ protected const int NumInvocationsPerIteration = 10_000;
13
+
14
+ private BenchmarkSession _session;
15
+
16
+ protected BenchmarkSession CurrentSession => _session;
17
+
18
+ protected void RunWithSession(BenchmarkSession session, Action body)
19
+ {
20
+ if (session == null)
21
+ {
22
+ throw new ArgumentNullException(nameof(session));
23
+ }
24
+
25
+ if (body == null)
26
+ {
27
+ throw new ArgumentNullException(nameof(body));
28
+ }
29
+
30
+ _session = session;
31
+ try
32
+ {
33
+ using (session)
34
+ {
35
+ body();
36
+ }
37
+ }
38
+ finally
39
+ {
40
+ _session = null;
41
+ }
42
+ }
43
+
44
+ protected void RecordBenchmark(string label, int count, TimeSpan duration, bool allocating)
45
+ {
46
+ long operationsPerSecond =
47
+ duration.TotalSeconds <= 0.0 ? 0 : (long)Math.Floor(count / duration.TotalSeconds);
48
+
49
+ if (_session != null)
50
+ {
51
+ _session.Record(label, operationsPerSecond, allocating);
52
+ return;
53
+ }
54
+
55
+ string formatted = operationsPerSecond.ToString("N0", CultureInfo.InvariantCulture);
56
+ Debug.Log($"| {label} | {formatted} | {(allocating ? "Yes" : "No")} |");
57
+ }
58
+
59
+ protected GameObject CreateBenchmarkGameObject()
60
+ {
61
+ GameObject target = new(
62
+ "Benchmark",
63
+ typeof(EmptyMessageAwareComponent),
64
+ typeof(SpriteRenderer),
65
+ typeof(Rigidbody2D),
66
+ typeof(CircleCollider2D),
67
+ typeof(LineRenderer)
68
+ );
69
+ _spawned.Add(target);
70
+ return target;
71
+ }
72
+
73
+ protected void RunWithComponent(Action<EmptyMessageAwareComponent> action)
74
+ {
75
+ if (action == null)
76
+ {
77
+ throw new ArgumentNullException(nameof(action));
78
+ }
79
+
80
+ GameObject go = CreateBenchmarkGameObject();
81
+ try
82
+ {
83
+ EmptyMessageAwareComponent component =
84
+ go.GetComponent<EmptyMessageAwareComponent>();
85
+ action(component);
86
+ }
87
+ finally
88
+ {
89
+ _spawned.Remove(go);
90
+ Object.Destroy(go);
91
+ }
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,11 @@
1
+ fileFormatVersion: 2
2
+ guid: 93674124ccfd4bcbb1fa16ce2bb68f86
3
+ MonoImporter:
4
+ externalObjects: {}
5
+ serializedVersion: 2
6
+ defaultReferences: []
7
+ executionOrder: 0
8
+ icon: {instanceID: 0}
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant: