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
@@ -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: