com.wallstop-studios.dxmessaging 3.0.1 → 3.2.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 (148) hide show
  1. package/CHANGELOG.md +497 -2
  2. package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +3 -3
  3. package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +3 -3
  4. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +69 -62
  5. package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +3 -3
  6. package/Editor/AssemblyInfo.cs.meta +9 -1
  7. package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
  8. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +24 -15
  9. package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
  10. package/Editor/CustomEditors/MessagingComponentEditor.cs.meta +9 -1
  11. package/Editor/DxMessagingEditorIdle.cs +62 -0
  12. package/{Runtime/Core/Internal/TypedDispatchLinkIndex.cs.meta → Editor/DxMessagingEditorIdle.cs.meta} +1 -1
  13. package/Editor/DxMessagingEditorInitializer.cs +112 -15
  14. package/Editor/DxMessagingEditorInitializer.cs.meta +9 -1
  15. package/Editor/DxMessagingEditorLog.cs +32 -0
  16. package/Editor/DxMessagingEditorLog.cs.meta +11 -0
  17. package/Editor/DxMessagingMenu.cs.meta +3 -3
  18. package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
  19. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +137 -15
  20. package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
  21. package/Editor/Settings/DxMessagingSettings.cs +92 -31
  22. package/Editor/Settings/DxMessagingSettings.cs.meta +9 -1
  23. package/Editor/Settings/DxMessagingSettingsProvider.cs.meta +9 -1
  24. package/Editor/SetupCscRsp.cs +539 -304
  25. package/Editor/SetupCscRsp.cs.meta +9 -1
  26. package/Editor/Testing/MessagingComponentEditorHarness.cs +1 -1
  27. package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +9 -1
  28. package/README.md +17 -18
  29. package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +3 -3
  30. package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +3 -3
  31. package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +3 -3
  32. package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +3 -3
  33. package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +3 -3
  34. package/Runtime/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  35. package/Runtime/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +46 -0
  36. package/Runtime/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  37. package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +2 -2
  38. package/Runtime/Analyzers.meta +8 -0
  39. package/Runtime/AssemblyInfo.cs.meta +9 -1
  40. package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +9 -1
  41. package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs.meta +9 -1
  42. package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
  43. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +2 -4
  44. package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs.meta +9 -1
  45. package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs.meta +9 -1
  46. package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs.meta +9 -1
  47. package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs +56 -0
  48. package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs.meta +11 -0
  49. package/Runtime/Core/DataStructure/CyclicBuffer.cs +44 -26
  50. package/Runtime/Core/DataStructure/CyclicBuffer.cs.meta +9 -1
  51. package/Runtime/Core/Diagnostics/MessageEmissionData.cs.meta +9 -1
  52. package/Runtime/Core/Diagnostics/MessageRegistrationData.cs.meta +9 -1
  53. package/Runtime/Core/Diagnostics/MessageRegistrationType.cs.meta +9 -1
  54. package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
  55. package/Runtime/Core/Extensions/EnumExtensions.cs +6 -5
  56. package/Runtime/Core/Extensions/EnumExtensions.cs.meta +9 -1
  57. package/Runtime/Core/Extensions/IListExtensions.cs.meta +9 -1
  58. package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
  59. package/Runtime/Core/Extensions/MessageExtensions.cs +0 -60
  60. package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
  61. package/Runtime/Core/Helper/MessageCache.cs.meta +9 -1
  62. package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +9 -1
  63. package/Runtime/Core/IMessage.cs.meta +3 -3
  64. package/Runtime/Core/InstanceId.cs +25 -1
  65. package/Runtime/Core/InstanceId.cs.meta +3 -3
  66. package/Runtime/Core/Internal/DxUnsafe.cs +60 -0
  67. package/Runtime/Core/Internal/DxUnsafe.cs.meta +11 -0
  68. package/Runtime/Core/Internal/FlatDispatch.cs +198 -0
  69. package/Runtime/Core/Internal/FlatDispatch.cs.meta +11 -0
  70. package/Runtime/Core/Internal/TypedSlots.cs +47 -21
  71. package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
  72. package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
  73. package/Runtime/Core/MessageBus/IMessageBus.cs +75 -40
  74. package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
  75. package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
  76. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +1 -0
  77. package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
  78. package/Runtime/Core/MessageBus/Internal/BusSlots.cs +7 -6
  79. package/Runtime/Core/MessageBus/MessageBus.cs +2864 -3413
  80. package/Runtime/Core/MessageBus/MessageBus.cs.meta +3 -3
  81. package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +3 -3
  82. package/Runtime/Core/MessageBus/MessageBusRegistration.cs +207 -0
  83. package/Runtime/Core/MessageBus/MessageBusRegistration.cs.meta +11 -0
  84. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +187 -14
  85. package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +3 -3
  86. package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +3 -3
  87. package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +3 -3
  88. package/Runtime/Core/MessageHandler.cs +2206 -1510
  89. package/Runtime/Core/MessageHandler.cs.meta +3 -3
  90. package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
  91. package/Runtime/Core/MessageRegistrationToken.cs +1667 -812
  92. package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
  93. package/Runtime/Core/Messages/GlobalStringMessage.cs.meta +9 -1
  94. package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +3 -3
  95. package/Runtime/Core/Messages/ITargetedMessage.cs.meta +3 -3
  96. package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +3 -3
  97. package/Runtime/Core/Messages/ReflexiveMessage.cs.meta +9 -1
  98. package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +3 -3
  99. package/Runtime/Core/Messages/StringMessage.cs.meta +9 -1
  100. package/Runtime/Core/MessagingDebug.cs.meta +3 -3
  101. package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +3 -3
  102. package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +3 -3
  103. package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +3 -3
  104. package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +9 -1
  105. package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +3 -3
  106. package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +9 -1
  107. package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
  108. package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +9 -1
  109. package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
  110. package/Runtime/Unity/MessageAwareComponent.cs +46 -1
  111. package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
  112. package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
  113. package/Runtime/Unity/MessagingComponent.cs +43 -10
  114. package/Runtime/Unity/MessagingComponent.cs.meta +3 -3
  115. package/Runtime/Unity/MessagingComponentInstaller.cs.meta +3 -3
  116. package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +3 -3
  117. package/Runtime/WallstopStudios.DxMessaging.asmdef +1 -1
  118. package/Samples~/DI/README.md +7 -7
  119. package/Samples~/DI/Reflex/SampleInstaller.cs.meta +3 -3
  120. package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +3 -3
  121. package/Samples~/DI/Zenject/SampleInstaller.cs.meta +3 -3
  122. package/Samples~/Mini Combat/Boot.cs.meta +3 -3
  123. package/Samples~/Mini Combat/Enemy.cs.meta +3 -3
  124. package/Samples~/Mini Combat/Messages.cs.meta +3 -3
  125. package/Samples~/Mini Combat/Player.cs.meta +3 -3
  126. package/Samples~/Mini Combat/UIOverlay.cs.meta +3 -3
  127. package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +3 -3
  128. package/Samples~/UI Buttons + Inspector/Messages.cs.meta +3 -3
  129. package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +3 -3
  130. package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +3 -3
  131. package/SourceGenerators/Directory.Build.props +50 -3
  132. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +96 -63
  133. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
  134. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +745 -87
  135. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +9 -1
  136. package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +40 -46
  137. package/SourceGenerators/global.json +7 -0
  138. package/SourceGenerators/global.json.meta +7 -0
  139. package/package.json +30 -40
  140. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
  141. package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -33
  142. package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
  143. package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs +0 -51
  144. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  145. /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
  146. /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
  147. /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
  148. /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,501 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.2.0]
11
+
12
+ ### Added
13
+
14
+ - Three per-kind **marginal registration** benchmark scenarios
15
+ (`UntargetedRegistration_Marginal`, `TargetedRegistration_Marginal`,
16
+ `BroadcastRegistration_Marginal`) that measure the GC-allocation cost of an additional
17
+ same-type registration on an already-warm bus -- the surface the registration
18
+ allocation reductions target -- so the published performance tables now show per-kind
19
+ registration cost (allocation count + bytes), not just dispatch throughput. Each
20
+ registers 1000 more handlers of one warmed message type using distinct, pre-built
21
+ handler delegates, so the measured window captures only the registration machinery
22
+ (never the handler delegate, and never a same-handler refcount bump). They are
23
+ wall-clock (zero-throughput) rows whose allocation columns populate on the
24
+ profiler-bearing in-editor PlayMode/Mono leg and read `n/a` on the published Standalone
25
+ IL2CPP Release leg (which strips the profiler), exactly like the existing
26
+ registration-flood rows.
27
+ - Benchmarks now report the total allocated BYTES per measurement batch (`gcAllocatedBytes`)
28
+ alongside the existing managed-allocation CALL count (`gcAllocations`). Bytes are
29
+ measured from a before/after delta of the live Unity
30
+ `ProfilerRecorder(ProfilerCategory.Memory, "GC Allocated In Frame").CurrentValue`
31
+ -- a within-frame `GC.Alloc`-hook byte accumulator that SUMS allocation-hook bytes
32
+ rather than measuring a heap-size difference, so it is exact (verified: 100 x
33
+ `byte[10000]` reads 1,003,200 bytes, run-to-run identical) and immune to mid-window
34
+ collections (a heavy-churn region that swung a `GC.GetTotalMemory` delta to -133 MB
35
+ read a stable 8,000,000 bytes here). The byte counter is profiler-dependent
36
+ like the count probe: in the editor and development players it is functional, and on
37
+ the published non-development Standalone IL2CPP Release leg -- where the profiler is
38
+ stripped -- BOTH metrics report the `Unmeasured` sentinel (rendered `n/a`), never a
39
+ fabricated `0`. Bytes are INFORMATIONAL -- the perf-delta PR comment renders byte
40
+ deltas goodness-signed (`N fewer bytes` / `N more bytes`) -- while the regression gate
41
+ stays on the allocation COUNT, which remains the canonical signal (Unity's own
42
+ Performance Testing package likewise reads the alloc-CALL count, not bytes). The
43
+ cross-library comparison tables now also surface this byte metric as a dedicated
44
+ GC-allocated-bytes matrix, and the Unity `SendMessage` comparison boxes its value
45
+ payload on every dispatch (as any real `SendMessage(value)` call must) so its
46
+ allocation columns show that real per-call cost instead of a misleading zero. New API:
47
+ `AllocationProbe.MeasureWithBytes` returning `AllocationProbe.AllocationSample`,
48
+ `AllocationProbe.Window.SampleBytes()`/`SampleBoth()`,
49
+ `AllocationProbe.BytesFunctional`, `BenchmarkMeasurement.GcAllocatedBytes`,
50
+ `ColdLatencyMeasurement.MedianGcAllocatedBytes`, and
51
+ `MinimumMeasurement<T>.GcAllocatedBytes`.
52
+ - New "What's New in 3.x" documentation page (Getting Started) that summarizes
53
+ the user-visible improvements across the 3.x line -- faster zero-allocation
54
+ dispatch, the base-call analyzer and inspector overlay, project-wide settings,
55
+ memory-reclamation controls, `ReregisterOnEnableAfterRelease`, the clarified
56
+ `ToggleMessageHandler` semantics, Unity 6.4+/6.5 compatibility, and the
57
+ dependency-injection helpers -- with links to the authoritative changelog and
58
+ the migration guide.
59
+
60
+ ### Fixed
61
+
62
+ - The token-creation allocation guard no longer flakes in a warm editor. The
63
+ diagnostics-lazy win (token `Create` allocating 7 managed objects instead of 11) was
64
+ guarded by an absolute `GC.Alloc` recorder COUNT budget, but a warm, long-lived editor
65
+ domain attributes background allocations to whatever measurement window is open --
66
+ measured on the host editor, the minimum over 64 windows was ~19 allocations for that
67
+ 7-allocation operation (median ~51), well above any budget tight enough to catch the
68
+ revert -- so the guard false-failed run-to-run. A measure-first probe also disproved the
69
+ assumption that the swing came from the `DxPools` collection pools: the steady refcount
70
+ registration path does not rent from them at all (hits = misses = 0), so the noise is
71
+ pure background-editor `GC.Alloc` pollution that no pool pre-warm can remove. The guard
72
+ is now a DETERMINISTIC state assertion -- after `Create` with diagnostics off, the lazy
73
+ `_callCounts`/`_emissionBuffer` backing fields must still be `null` -- which uses no
74
+ allocation probe, never flakes, and runs in the per-PR EditMode correctness leg rather
75
+ than the weekly perf-gated Allocation scope.
76
+ - Two more memory-reclamation/registration allocation guards no longer flake in a warm
77
+ editor. The forced-trim guard measured a `GC.Alloc` COUNT budget over a 32-trim window
78
+ and the dirty-target reuse guard a per-batch count budget; both false-failed run-to-run
79
+ on the same warm-editor ambient-noise floor that the token-creation guard hit. The
80
+ forced-trim guard is now a DETERMINISTIC assertion on the exact `IMessageBus.TrimResult`
81
+ eviction counts (the first force-trim reclaims; every subsequent one is an idempotent
82
+ no-op that evicts nothing and leaves the live slot count stable) plus a `DxPools`
83
+ Misses-flat check (repeated no-op trims rent no fresh pooled collection); the
84
+ dirty-target guard now relies solely on its existing exact pool Hits/Misses assertions
85
+ (renting the warmed collection, never allocating a fresh one). Neither uses an allocation
86
+ probe, so neither flakes. The two deterministic registration STORAGE structural guards
87
+ (the by-value metadata parameter and the staging-function map value type) were also moved
88
+ from the weekly perf-gated Allocation suite into the per-PR EditMode correctness leg, so
89
+ they protect every PR. Internal test/quality change only; no public API or runtime
90
+ behavior change.
91
+ - Benchmark and library-comparison allocation reporting is now honest. The harness
92
+ measured allocations with `GC.GetAllocatedBytesForCurrentThread()`, which returns
93
+ `0` for every allocation under Unity's Boehm GC (verified: a forced 1 MB array
94
+ allocation read back a `0`-byte delta), so the published "allocated bytes" column
95
+ was a vacuous `0` for every technology -- hiding real per-operation allocations
96
+ (Unity `SendMessage`, for example, boxes its value-type payload once per call).
97
+ The metric is now a COUNT of managed allocations from the reliable `GC.Alloc`
98
+ profiler recorder (new `AllocationProbe`), renamed `gcAllocations`, and reports an
99
+ `Unmeasured` sentinel (rendered `n/a`) -- never a fabricated `0` -- when no probe
100
+ is available on a backend. The perf-doc/PR-comment renderers, the regression gate,
101
+ and the methodology docs were updated accordingly; the doc's allocation column
102
+ reads `n/a` until the next CI run repopulates real counts.
103
+ - Source generators (`[DxUntargetedMessage]`, `[DxTargetedMessage]`,
104
+ `[DxBroadcastMessage]`, `[DxAutoConstructor]`) now work in projects that do not use
105
+ assembly definitions. The generator and analyzer DLLs previously shipped under the
106
+ editor-only `Editor/Analyzers/` folder, which Unity scopes to the package's editor
107
+ assembly and assemblies that reference it -- so a consumer's runtime code in
108
+ `Assembly-CSharp` (or a runtime asmdef referencing only the runtime assembly) never
109
+ received the generator, and `[Dx*Message]` types failed to implement their generated
110
+ interface with cryptic `CS0315`/`CS0452` errors. The labeled DLLs now ship under
111
+ `Runtime/Analyzers/` (governed by the all-platforms runtime assembly), so Unity
112
+ applies the generator to the DxMessaging runtime assembly and every assembly that
113
+ references it, including the predefined `Assembly-CSharp`. No assembly definition is
114
+ required. Closes GitHub issue #229.
115
+ - Documentation no longer teaches the obsolete `IMessageBus.GlobalDiagnosticsMode`
116
+ API. The reference, patterns, glossary, and migration guides now use
117
+ `IMessageBus.GlobalDiagnosticsTargets` with the `DiagnosticsTarget` flags enum
118
+ (matching the canonical diagnostics guide), and the API reference no longer shows a
119
+ write to the read-only `IMessageBus.DiagnosticsMode` property. A source-derived
120
+ drift-guard (`DocsObsoleteApiReferenceTests`) now fails the build if any published
121
+ doc references a member marked `[Obsolete]` in the runtime, so this cannot regress.
122
+
123
+ ### Changed
124
+
125
+ - Registration now allocates one fewer managed object per handler in the common case. The
126
+ registration token stored every handle's de-registration in a per-handle holder object,
127
+ but the common case is EXACTLY ONE de-registration per handle, so the single
128
+ de-registration delegate is now stored INLINE in the token's de-registration map (with no
129
+ holder object); a holder is allocated only when a rare second de-registration accumulates
130
+ on the same handle (the re-entrant retarget-recovery replay). The ordering,
131
+ partial-failure, and rollback-baseline semantics are unchanged (pinned by the existing
132
+ `PendingDeregistrationStorageTests` and the full re-entrancy suite), and a new
133
+ `RegistrationStorageStructuralGuardTests.DeregistrationsStoreInlineActionNotPerHandleHolder`
134
+ deterministically guards the inline storage against regression. Internal change only; no
135
+ public API or behavior change.
136
+ - **BREAKING (v4):** the `IMessageBus` registration contract now returns an opaque,
137
+ zero-allocation handle instead of a deregistration delegate. The 14
138
+ `IMessageBus.Register*<T>` methods return a new `readonly struct`
139
+ `DxMessaging.Core.MessageBus.MessageBusRegistration` (previously `System.Action`), and a
140
+ new `void IMessageBus.Deregister<T>(in MessageBusRegistration registration) where T : IMessage`
141
+ undoes a registration (call it with the same `T` you registered with; for
142
+ `RegisterGlobalAcceptAll`, any `T : IMessage` works since the global slot is not
143
+ type-keyed). This removes the per-registration bus-side deregistration **closure** (the
144
+ prior `Action` plus its display class) from the handler registration path -- roughly
145
+ **two fewer managed allocations per registration** -- by packing the deregistration
146
+ snapshot into the returned value handle and re-expressing the bus's deregistration logic
147
+ against it (behaviour and the four reentrancy invariants -- generation guard, identity
148
+ liveness / over-deregistration, token idempotency, no counter underflow -- are unchanged).
149
+ Custom `IMessageBus` implementers that wrap `MessageBus` (the
150
+ `DelegatingMessageBus`-style extension point) just forward the new members; from-scratch
151
+ implementers mint handles via the public `MessageBusRegistration(long, object)`
152
+ constructor and read them back in their own `Deregister<T>`.
153
+ **The Unity-facing surface is unaffected:** `MessageRegistrationToken` and
154
+ `MessageAwareComponent` keep their existing public shapes. The `MessageHandler.Register*`
155
+ facades were narrowed from `public` to `internal` (they now return the internal
156
+ `HandlerDeregistration`, still implicitly an `Action`) as part of this bus<->handler
157
+ boundary rework -- see the dedicated entry below; only code that called those facades
158
+ **directly** is affected. The larger per-registration allocation reduction (collapsing the
159
+ token/handler closures onto a single per-handle `Registration` object) landed on top of this
160
+ contract change -- see the entries below.
161
+ - The published performance report no longer prints columns of `n/a`. The
162
+ Standalone IL2CPP leg runs in a Release player whose stripped profiler cannot
163
+ measure GC allocations or bytes, so the renderer now OMITS a memory column from a
164
+ per-scope dispatch table when every row is unmeasured (the Standalone table is
165
+ throughput-only), omits a whole cross-library memory matrix when no leg measured
166
+ that metric, and drops the unmeasured allocation/byte segment from each per-PR
167
+ delta cell -- instead of filling them with a wall of `n/a`. The real allocation
168
+ and byte numbers still publish from the in-editor PlayMode (Mono) leg, and `n/a`
169
+ now appears only as a genuine per-row or per-library cell (a metric measured for
170
+ the scope in general but missing for that one entry).
171
+ - Registration allocates less. Each registration token's diagnostics-only
172
+ call-count and emission-history collections are now created lazily instead of
173
+ eagerly, so a token whose owner never enables diagnostics (the default) no longer
174
+ pays for them -- token creation drops from 11 to 7 managed allocations. The
175
+ per-registration metadata is now passed by value instead of through a closure
176
+ factory that was invoked immediately, removing one delegate allocation per handler
177
+ registration. Steady-state dispatch stays allocation-free, and diagnostics, the
178
+ inspector overlay, and all lifecycle behavior are unchanged.
179
+ - Registering a handler OR a post-processor with the convenience `Action<T>` /
180
+ `Action<InstanceId, T>` overloads now allocates one fewer closure per registration.
181
+ This covers the handler registers (`RegisterUntargeted`, `RegisterTargeted`,
182
+ `RegisterTargetedWithoutTargeting`, the sourced-broadcast register, and
183
+ `RegisterBroadcastWithoutSource`) and the matching post-processor registers
184
+ (`RegisterTargetedPostProcessor`, `RegisterTargetedWithoutTargetingPostProcessor`, the
185
+ GameObject/Component/source broadcast post-processors, and
186
+ `RegisterBroadcastWithoutSourcePostProcessor`). Each such registration previously built
187
+ two delegates -- a diagnostics-augmented `Action` wrapper plus a separate by-ref
188
+ `FastHandler` adapter for the flat-dispatch path -- and now folds diagnostics into a
189
+ single by-ref `FastHandler` closure that is the dispatch target directly (also removing a
190
+ per-dispatch indirection). The public API is unchanged; the optimization is internal and
191
+ verified by differential allocation guards that pin the `Action` registration cost to the
192
+ already-optimal `FastHandler` registration cost. Diagnostics, deduplication, and dispatch
193
+ ordering are preserved.
194
+ - Every registration kind now allocates about two fewer managed objects. The token used to
195
+ wrap each staged registration in a per-registration parameterless `Action` (a delegate plus
196
+ its display class) whose only job was to re-bundle the handle, the staging function, and the
197
+ de-registration bookkeeping; the token now stores each staging function directly and pairs it
198
+ with its handle in the replay queue, so that wrapper -- and the closure `InternalRegister`
199
+ needed to build it -- is gone. Measured cold-registration floor (FastHandler, diagnostics off):
200
+ untargeted drops from 14.69 to 12.69 managed allocations per registration (a clean -2.00), with
201
+ the same ~2-allocation reduction across targeted, broadcast, without-targeting/source, and
202
+ post-processor registrations (~12% fewer registration allocations overall). The de-registration
203
+ replay, rollback-on-failure, re-entrancy, and equal-priority registration-order semantics are
204
+ unchanged; the public API is unchanged. Pinned structurally by a deterministic guard that the
205
+ token stores the staging function (not an `Action` wrapper).
206
+ - Every active registration now allocates about two fewer managed objects again. The token
207
+ tracked each handle's live de-registration in a per-handle holder that eagerly allocated a
208
+ `List` (plus its backing array) to hold what is almost always a single de-registration; the
209
+ holder now keeps that one de-registration inline and only allocates an overflow list on the
210
+ rare second de-registration for the same handle. Measured cold-registration floor (FastHandler,
211
+ diagnostics off): untargeted drops from 13.29 to 11.29 managed allocations per registration (a
212
+ clean -2.00), with the same ~2-allocation reduction across targeted and broadcast. The
213
+ insertion-order, partial-failure-retryable, and rollback-baseline de-registration semantics are
214
+ unchanged (verified by a differential simulation across every count/failure/start-index
215
+ combination); the public API is unchanged.
216
+ - Each handler/post-processor registration now allocates one fewer managed object by replacing
217
+ its per-registration de-registration **closure** with a per-handle **object**. The typed
218
+ handler used to hand back a captured parameterless `Action` (a delegate plus the display class
219
+ holding the slot, cache key, priority, and generation it closed over) as the thing the token
220
+ would later invoke to tear the registration down; that closure is now a single per-handle
221
+ `HandlerDeregistration` object whose `Deregister()` instance method carries the same captured
222
+ state as plain fields (and converts implicitly to `Action`, so the token's replay/rollback
223
+ machinery is untouched). To let one non-generic object tear down a generic
224
+ `HandlerActionCache<TU>` without re-introducing a per-handler-delegate-type closure, the erased
225
+ `IHandlerActionCache` gained three non-generic operations (`ContainsEntry`, `BumpVersion`,
226
+ `DeregisterEntry`). The de-registration **order** (generation guard, slot-version liveness,
227
+ keyed-vs-scalar split, identity/over-de-registration check, version bump, bus de-register,
228
+ refcount decrement) is a faithful re-expression of the prior closure body, pinned by the 19
229
+ arbitrary-order `MixedOrderDeregistrationTests` and the full re-entrancy suite. Because these
230
+ `MessageHandler.Register*` facades now return the internal `HandlerDeregistration` (still
231
+ implicitly an `Action`), they were narrowed from `public` to `internal` -- this is part of the
232
+ v4 bus<->handler boundary rework and does NOT touch the Unity-facing surface
233
+ (`MessageRegistrationToken`, `MessageAwareComponent`); only code calling
234
+ `MessageHandler.Register*` **directly** is affected.
235
+ - Every registration kind now allocates roughly half as many managed objects again by
236
+ collapsing the token's per-handle staging closures into one unified per-handle object.
237
+ Each registration previously staged a `Func<handle, HandlerDeregistration>` (a delegate plus
238
+ its display class capturing the target/source, user handler, and priority) whose nested
239
+ `AugmentedHandler` local function became a _second_ delegate (the diagnostics-augmented flat
240
+ invoker). Both are now a single per-handle `Registration` object stored in `_registrations`:
241
+ its fields hold the captured staging state, and its diagnostics-augmented invoker is an
242
+ _instance method_ bound to the object -- so `MessageHandler` still receives a `FastHandler<T>`
243
+ delegate and the hot dispatch path stays delegate-based (no virtual/interface call per
244
+ dispatch). A `Register()` method runs a kind-switch to call the matching
245
+ `MessageHandler.Register*` and reproduce the exact prior staging body per kind. Because the
246
+ constrained `MessageHandler.Register*<T>` calls require `T : ITargetedMessage` /
247
+ `IUntargetedMessage` / `IBroadcastMessage` (which a single `Registration<T> where T : IMessage`
248
+ cannot satisfy), the object is realized as three constraint-family generic subclasses plus one
249
+ non-generic global-accept-all subclass over a non-generic `Registration` base -- still a
250
+ unified per-handle object with a kind-switch, not a per-method subclass explosion. Measured
251
+ marginal cost (cold, FastHandler, diagnostics off) drops from ~9.3 to ~4.6 managed allocations
252
+ per registration (about half). The equal-priority registration-order dispatch, idempotent
253
+ double-deregister, partial-failure rollback, generation/slot-version guards, refcount handlers,
254
+ and diagnostics call-counts/emission semantics are all unchanged (pinned by the 19
255
+ `MixedOrderDeregistrationTests` and the full re-entrancy suite); the public API is unchanged.
256
+ Pinned structurally by a deterministic guard that `_registrations` stores the unified
257
+ `Registration` object, not a `Func`/`Action` wrapper.
258
+ - Deregistration is now O(1) on the steady path. The v4 bus `Deregister<T>` had been
259
+ re-resolving the sink from the registration method (a `switch` plus `ScalarSinkForMethod` /
260
+ `ContextSinkForMethod`) and re-walking the whole sink -> type -> [context ->] priority ->
261
+ handler chain on every call -- even though the leaf handler-cache is already captured on the
262
+ registration handle. It now operates on that captured cache DIRECTLY, deferring the
263
+ re-resolution to a cold fallback taken only when the handler is not found there (the rare
264
+ post-sweep or over-deregistration case, which the fallback classifies as a silent no-op vs an
265
+ error). This removes the per-deregistration re-resolution that had regressed the
266
+ deregistration-heavy teardown path, with no change to dispatch, registration allocations, the
267
+ generation/sweep-staleness/over-deregistration guards, or the public API; the throw-safe
268
+ ordering (the handler-cache mutation stays after the throwing `IMessageBus.Deregister`) is
269
+ unchanged. A stale over-deregistration whose `(type, priority)` slot was reused by a different
270
+ handler now leaves the live registration completely untouched (new
271
+ `StaleOverDeregistrationTests` regression coverage). The token's de-registration store also
272
+ records the common first-registration in a single dictionary operation (`TryAdd`) instead of a
273
+ probe-then-insert pair.
274
+ - The bug-report issue template now offers the package version as a dropdown of
275
+ released versions (with an `Other` fallback) instead of a free-text field, so
276
+ reports carry an exact, valid version. The list is generated from
277
+ `package.json`, `CHANGELOG.md`, and git tags, kept in sync by
278
+ `npm run check:issue-template-versions` (gated in `validate:all`), and
279
+ self-heals on the default branch via the `Update Issue Template Versions`
280
+ workflow. Closes GitHub issue #230.
281
+ - The Roslyn source generator no longer copies its DLLs into the consumer's
282
+ `Assets/Plugins/Editor/` folder on editor load; it (with its pinned Roslyn dependency
283
+ DLLs) ships ready-to-use under the package's `Runtime/Analyzers/` folder
284
+ (RoslynAnalyzer-labeled, excluded from player builds). Projects upgrading from an
285
+ earlier version have the redundant in-project copy removed automatically during asset
286
+ import, before script compilation, so the package's copy and the old in-project copy
287
+ never both run the generator (which would otherwise duplicate generated members). No
288
+ manual action is required: the cleanup only removes the
289
+ `Assets/Plugins/Editor/WallstopStudios.DxMessaging` folder when it contains the
290
+ first-party source-generator DLL plus exact known legacy analyzer/dependency DLL names
291
+ the package created, and leaves any foreign DLL, foreign `.meta`, subfolder, or
292
+ other content untouched. If a mixed or incomplete legacy payload is detected, the
293
+ editor logs one warning with manual cleanup guidance instead of deleting the folder
294
+ silently.
295
+
296
+ ## [3.1.0]
297
+
298
+ ### Added
299
+
300
+ - Deregistration-speed benchmarks (`DeregistrationFlood_1000Types_Cold` and
301
+ `DeregistrationFlood_1000Types_WarmJit`): the teardown mirror of the existing
302
+ registration floods. Each stages 1000 live registrations untimed, then times
303
+ `MessageRegistrationToken.UnregisterAll()` (the production deregistration path).
304
+ Both are wall-clock (latency) rows alongside the registration floods in the
305
+ rendered dispatch tables (closes the deregistration ask in GitHub issue #31).
306
+ - `MessageAwareComponent.ReregisterOnEnableAfterRelease`: opt-in virtual property
307
+ (default `false`, preserving existing behavior). When overridden to return
308
+ `true`, a component whose registration token was released (for example via
309
+ `MessagingComponent.Release`) re-creates its token and replays
310
+ `RegisterMessageHandlers` the next time it is enabled, instead of staying
311
+ permanently unregistered. The replay runs exactly once per release; plain
312
+ enable/disable cycles without a release never re-stage registrations.
313
+
314
+ ### Changed
315
+
316
+ - The Unity object identity backing `InstanceId` (the dispatch key) is now read
317
+ through a single version-gated source, `InstanceId.StableId`. On Unity 6.4+ it
318
+ uses the non-deprecated `EntityId.ToULong(...)` accessor and keeps its low 32
319
+ bits -- exactly the value the legacy `GetInstanceID()` returned (verified across
320
+ `GameObject`, `Component`, and `ScriptableObject`); older Unity keeps
321
+ `GetInstanceID()`. This keeps the package compiling on Unity 6.5+, where
322
+ `GetInstanceID()` becomes a compile error, and removes its deprecation warning on
323
+ Unity 6.4+. The 32-bit dispatch key, equality, and hashing are unchanged. Closes
324
+ GitHub issue #208.
325
+ - `MessagingComponent.ToggleMessageHandler(false)` is no longer silently ignored
326
+ while `emitMessagesWhenDisabled` is true: explicit toggle calls now always win,
327
+ in both directions. Instead, the Unity enable/disable lifecycle itself now skips
328
+ the handler toggle while `emitMessagesWhenDisabled` is true, so disabling the
329
+ component still keeps emission alive (the flag's documented purpose) AND an
330
+ explicit `ToggleMessageHandler(false)` is no longer reverted by a later
331
+ enable/disable cycle. Previously the veto made explicit deactivation requests
332
+ silent no-ops and `OnEnable` force-reactivated the handler. One consequence:
333
+ setting the flag while the handler is lifecycle-deactivated (disabled with the
334
+ flag clear) means a later enable no longer auto-reactivates - call
335
+ `ToggleMessageHandler(true)` to resume.
336
+
337
+ - Removed the internal per-handler dispatch-link machinery (the ten
338
+ `*DispatchLink` wrapper classes, their lazily-populated slot array, and
339
+ the outer-generation guard) plus the vestigial non-global prefreeze
340
+ descriptors that the flattened dispatch had already stopped consuming:
341
+ snapshots for non-global slots no longer build per-handler bucket entry
342
+ arrays at all (they dispatch exclusively through the resolved flat
343
+ delegate arrays and keep count-only buckets for the legacy "found any
344
+ handlers" reporting), which removes one pooled array rent/fill/return
345
+ per priority bucket from every snapshot rebuild. No public API or
346
+ dispatch semantics change (verified emission-for-emission against the
347
+ previous implementation, including global accept-all mid-emission
348
+ mutation ordering, trim-then-re-register staleness, reset-mid-dispatch,
349
+ and zero steady-state allocations).
350
+ - Each emission now consults a cached per-(bus, message-type, kind) dispatch
351
+ plan instead of re-resolving interceptor, global accept-all, and
352
+ post-processor sinks with multiple type-cache lookups per emit. When the
353
+ plan shows none of those features are present (the common case), a fast
354
+ emit lane runs only the handle phase: one plan fetch, one validity check,
355
+ snapshot acquisition, and the flat dispatch loop. Plans are invalidated by
356
+ a single bus-wide version stamp that every registration, deregistration,
357
+ interceptor/global/post mutation, sweep/Trim, `ResetState`, and runtime
358
+ settings reload bumps; mutations performed by handlers mid-emission are
359
+ re-detected at phase boundaries, so frozen-snapshot semantics, mid-emission
360
+ registration gating, interceptor re-targeting, and mid-dispatch reset
361
+ behavior are unchanged (verified emission-for-emission against the previous
362
+ implementation). Diagnostics mode and `MessagingDebug.enabled` are still
363
+ read live on every emission. Out-of-Unity rig measurements (directional):
364
+ one-handler throughput up roughly 1.5-1.7x per kind and four-handler
365
+ fan-out up ~35%, with feature-heavy paths unchanged within noise and zero
366
+ steady-state allocations.
367
+ - The internal flat-dispatch shape assertion (`DebugAssertFlatShape`) moved
368
+ from `DEBUG` builds to the opt-in `DXMESSAGING_INTERNAL_CHECKS` scripting
369
+ define; it cost a type test per dispatch on Editor hot paths.
370
+ - On IL2CPP players, the dispatch hot loops (flat snapshot walks, global
371
+ accept-all bucket walks, and global entry invokers) now opt out of the
372
+ generated per-iteration null and array-bounds checks via a vendored
373
+ internal `Unity.IL2CPP.CompilerServices.Il2CppSetOptionAttribute`. The
374
+ elided checks guard invariants the snapshot builder already guarantees
375
+ (frozen arrays, non-null handler/invoker pairs, `count` never exceeding
376
+ the array length), all pinned by tests; rig/diagnostic builds keep the
377
+ `DXMESSAGING_INTERNAL_CHECKS` shape assertions. No behavior change under
378
+ Mono or in the editor.
379
+ - On IL2CPP players, the per-emit AOT untyped-dispatch bridge registration
380
+ (`EnsureAot*Bridge<T>`, IL2CPP-only) now runs when a message type's dispatch
381
+ plan is first built on a bus (the first typed emit) instead of on every emit,
382
+ removing a generic-static class-initialization check and a non-inlined call
383
+ from the steady-state IL2CPP dispatch prologue. The registration is latched
384
+ process-globally, so it actually executes once per type; the guarded call site
385
+ is simply reached at first plan build rather than on every emit. The bridge is
386
+ rooted before the first untyped dispatch of a type by either any registration
387
+ of that type or its first typed emit, so the untyped-dispatch contract is
388
+ unchanged (a never-touched type still throws the same missing-bridge error).
389
+ No behavior change under Mono or in the editor (the calls compile out there).
390
+ - Dispatch for every message kind (untargeted, targeted, and broadcast;
391
+ handle and post-process phases) now resolves handlers to flat, pooled
392
+ delegate arrays at snapshot-build time instead of walking per-handler
393
+ dictionaries and dispatch links per message. Measured on Editor PlayMode
394
+ Mono x64 versus the previous release: one untargeted handler 17.5M to
395
+ 22.1M emits/sec, four untargeted handlers 3.9M to 20.0M, one targeted
396
+ listener 11.4M to 15.7M, sixteen targeted listeners 0.73M to 8.7M, one
397
+ broadcast handler 8.0M to 15.6M, four post-processors 3.3M to 12.6M -
398
+ all with zero steady-state allocations, an 8% faster cold registration
399
+ flood, and unchanged dispatch semantics (snapshot freezing, priority and
400
+ registration order, mid-emission registration visibility). One deliberate
401
+ refinement: a handler that deactivates itself mid-emission now skips its
402
+ own remaining delegates in that emission for every kind (the active check
403
+ runs per delegate instead of once per handler), matching the documented
404
+ immediate-deactivation semantics. Mid-emission registration gating is
405
+ also now uniform: a delegate registered during an emission never fires in
406
+ that emission, for every kind and every registration shape. Previously a
407
+ handler registering a different-shaped delegate for the same type on its
408
+ own MessageHandler could fire it in the same emission, depending on
409
+ unrelated handler counts.
410
+
411
+ ### Fixed
412
+
413
+ - Domain-reload advisory warnings are now re-evaluated after deferred Editor
414
+ settings creation/migration, so an initial passive load with no settings asset
415
+ cannot permanently skip an unsuppressed warning for that editor domain.
416
+ - Token cleanup now clears token-local diagnostics and stale teardown state:
417
+ `UnregisterAll()`, `Dispose()`, final-handle removal, released
418
+ `MessagingComponent` tokens, and disposed registration leases no longer
419
+ retain metadata, call counts, emission history, or stale deregistration
420
+ closures that could report old registrations or over-deregister later.
421
+ Failed `Enable()` replays now roll back partial registrations before
422
+ throwing the original failure; failed active `RetargetMessageBus()` replays
423
+ roll back partial new-bus registrations and restore previous-bus
424
+ registrations that are not still live behind a failed rollback cleanup.
425
+ Deregistration actions that throw before cleanup remain retryable instead
426
+ of being forgotten, including through owning registration leases and
427
+ `MessagingComponent.Release()` retries. `ActivateOnBuild` failures now
428
+ clean up the partially built lease before throwing again; if cleanup cannot
429
+ complete, `MessageRegistrationBuildException` exposes the retryable lease
430
+ so callers can dispose it after resolving the cleanup failure.
431
+ - Interceptors registered through a `MessageRegistrationToken` bound to a
432
+ custom bus (`MessageRegistrationToken.Create(handler, customBus)`) now land
433
+ on that bus. `RegisterUntargetedInterceptor`, `RegisterTargetedInterceptor`,
434
+ and `RegisterBroadcastInterceptor` previously dropped the token's bus and
435
+ registered on the handler's default (typically global) bus, so they never
436
+ saw custom-bus emissions and silently intercepted global traffic instead.
437
+ The fix also covers registrations staged while disabled (they activate on
438
+ the token's bus at `Enable()` time) and `RetargetMessageBus`, which now
439
+ re-routes interceptors along with every other registration kind.
440
+ - Registering or deregistering a handler mid-emission and then emitting the
441
+ same message type reentrant-style from inside a handler no longer corrupts
442
+ the in-flight dispatch.
443
+ The nested emission's snapshot rebuild previously released the pooled
444
+ arrays the outer emission was still iterating
445
+ (`NullReferenceException` / `ArgumentOutOfRangeException`, or silent
446
+ cross-dispatch aliasing at deeper nesting). Displaced snapshots are now
447
+ released only after the outermost dispatch exits, each emission keeps its
448
+ own frozen-cache identity across nested emissions, and the broadcast
449
+ priority walk tolerates nested membership churn.
450
+ - A `MessageRegistrationLease` whose `OnActivate` callback throws no longer
451
+ wedges its registrations. The lease previously recorded itself as inactive
452
+ while the registrations stayed live, so `Deactivate()` and `Dispose()`
453
+ silently refused to release them. The lease now marks itself active before
454
+ invoking the callback: the exception still propagates, and standard
455
+ `Deactivate()`/`Dispose()` teardown fully releases the registrations.
456
+ - Calling `DxMessagingStaticState.Reset` (or resetting a bus) from inside a
457
+ message handler no longer crashes the in-flight emission. Targeted and
458
+ broadcast dispatch previously returned the active emission's pooled snapshot
459
+ arrays mid-iteration (`NullReferenceException` /
460
+ `ArgumentOutOfRangeException`); the teardown is now deferred until the
461
+ outermost dispatch exits, mirroring the existing `Trim` deferral, and the
462
+ remaining handlers in that emission short-circuit cleanly.
463
+ - Equal-priority handlers now always dispatch in live registration order. The
464
+ documented "same priority uses registration order" contract previously broke
465
+ after remove/re-register churn (a new handler could dispatch in a removed
466
+ handler's old position), after `Disable()`/`Enable()` cycles (replay order
467
+ permuted), and across components (a newly created component could dispatch
468
+ in a destroyed component's old position). Handler caches, bus-side priority
469
+ buckets, and token replay now preserve insertion order explicitly.
470
+ - Targeted and broadcast post-processors now follow an interceptor-rewritten
471
+ target/source. When an interceptor redirects a message via its
472
+ `ref InstanceId` parameter, post-processors registered for the rewritten id
473
+ run (previously the broadcast path never re-resolved the rewritten source,
474
+ and the targeted path preferred a stale pre-interceptor snapshot, so the
475
+ rewritten id's post-processors were silently skipped).
476
+ - `DiagnosticsTarget` gating now matches its documented semantics. Player
477
+ builds previously enabled diagnostics for the `Editor` flag and ignored the
478
+ `Runtime` flag, and the Editor enabled diagnostics when only `Runtime` was
479
+ set. `Editor` now enables diagnostics only inside the Unity Editor,
480
+ `Runtime` only in player/runtime builds, and `All` in both, exactly as the
481
+ diagnostics guide describes.
482
+ - Analyzer payload builds are now reproducible under the pinned
483
+ `SourceGenerators/global.json` SDK. The shipped analyzer and source-generator
484
+ DLLs no longer embed source revision or PDB metadata, CI double-builds them
485
+ into temporary payload directories before comparing against
486
+ `Editor/Analyzers`, and Unity runner maintenance now verifies or repairs the
487
+ full active editor matrix outside ordinary test jobs.
488
+ - IL2CPP builds now root untyped dispatch bridges for concrete
489
+ source-visible message types without changing the public API. The source
490
+ generator emits IL2CPP-only AOT bridge registration for attributed messages
491
+ and manual `IUntargetedMessage` / `ITargetedMessage` /
492
+ `IBroadcastMessage` implementations, while open generic definitions are
493
+ skipped until a closed generic type is used through the typed registration
494
+ path. Untyped dispatch also keeps separate per-kind delegate caches, so a
495
+ message type that participates in more than one dispatch kind can no longer
496
+ reuse the wrong cached delegate shape.
497
+ - Unity projects no longer keep stale DxMessaging analyzer entries in `csc.rsp`; the setup script removes package-cache analyzer registrations so Unity loads the shipped analyzer and source generator once through the `RoslynAnalyzer`-labeled plugin copy. The base-call ignore sidecar's `-additionalfile:` entry is also re-synchronized after deferred sidecar regeneration, so first-load `OnValidate` writes and Inspector ignore-list edits repair missing `csc.rsp` wiring during the same editor session.
498
+ - Provider-backed emit helpers now route sourced, targeted, and untargeted messages through the resolved `IMessageBus`, so custom bus and DI-provider callers no longer fall back to the global bus for interface-shaped message dispatch.
499
+ - Standalone and IL2CPP player builds now compile. The dispatch hot path performs reinterpret casts that previously used `System.Runtime.CompilerServices.Unsafe`; the Unity Editor supplies that type, but player builds under the .NET Standard 2.0 profile do not, so editmode and playmode passed while standalone IL2CPP failed to build with `CS0103: The name 'Unsafe' does not exist in the current context`. Those calls now route through Unity's built-in `UnsafeUtility` (in `UnityEngine.CoreModule`), which resolves identically in the Editor and every player scripting backend. The change preserves the existing zero-allocation dispatch behavior and adds no package dependency or shipped assembly.
500
+ - Shipped source generators now compile against Unity 2021-compatible Roslyn 3.8 APIs and use the classic `ISourceGenerator` entry point, preventing Unity 2021.3 analyzer-host load failures (`CS8032`) while preserving the generated message-id and auto-constructor output for newer Unity editors.
501
+ - `MessageRegistrationToken.RemoveRegistration(handle)` now compiles cleanly on Unity 2021 while preserving the existing behavior of removing the active deregistration, staged registration, metadata, and diagnostic call-count entries.
502
+ - Unity 2021.3 no longer aborts compilation with _Multiple precompiled assemblies with the same name_ (`PrecompiledAssemblyException`). The shipped Roslyn analyzer and source-generator DLLs are now excluded from every build platform (the Editor included) and activated solely by the `RoslynAnalyzer` asset label, so Unity treats them as compiler analyzers rather than managed precompiled assemblies that collide with the copy placed in the consuming project's `Assets/`. Generated message-id and auto-constructor output is unchanged.
503
+ - The dependency-injection sample README (`Samples~/DI`) now links to the real `Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs`; the previous link dropped the `VContainer/` folder and resolved to a missing file.
504
+
10
505
  ## [3.0.1]
11
506
 
12
507
  ### Added
@@ -44,7 +539,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
44
539
  - `RegisterGlobalAcceptAll` (`HandleGlobalUntargeted`/`HandleGlobalTargeted`/`HandleGlobalBroadcast`) is intentionally NOT covered by this fix. The bus's global accept-all dispatch path prefreezes lazily per-entry inside the dispatch loop, so a sibling `MessageHandler` that removes another's global registration mid-emit causes the removed handler to be skipped on the in-flight emission. The behavior is pinned by `MutationPostProcessorAcrossHandlersTests.RemoveOtherGlobalAcceptAllAcrossHandlersDuringDispatch`; if a future change introduces upfront global-handler prefreeze, that test must be updated to expect the snapshot semantics that the per-kind paths already provide.
45
540
  - `DxMessagingStaticState.Reset` is now race-safe against deferred deregistrations. Previously, when a message-aware component was destroyed but its disable callback had not yet run (Unity defers Object.Destroy to end of frame) and Reset ran in between, the deferred token teardown would log spurious "Received over-deregistration of {type} for {handler}" errors against the user's Unity console. The bus now stamps each captured deregister closure with a generation counter and silently no-ops closures captured before a Reset. Applied uniformly across every register entry point (untargeted, targeted, broadcast, GlobalAcceptAll, and all three interceptor kinds). The same race-safety guarantee is now propagated to user-installed custom global buses via `MessageBus.BumpResetGeneration()`, which `DxMessagingStaticState.Reset` invokes on the active global bus when it differs from the built-in default; the custom bus's sinks are intentionally left intact to avoid clobbering state the user installed it to preserve. User code is unaffected except that previously-spurious error logs disappear.
46
541
  - `MessageRegistrationToken.RemoveRegistration(handle)` no longer leaks the staged registration entry, so a `Disable()`/`Enable()` cycle after `RemoveRegistration` no longer silently re-registers the removed handler. The fix also drops the matching metadata and call-count entries so diagnostic mode does not accumulate stale handles.
47
- - Resolved [issue #204](https://github.com/wallstop/DxMessaging/issues/204) (build artifacts and orphaned `.meta` files leaking into the npm tarball) and prevented its regression: `scripts/validate-npm-meta.js` now runs `validateNoBuildArtifactsInTarball` (rejects `bin/`, `obj/`, `*.pdb`, `*.tmp`, `*.csproj.user`, `.vs/`, `.idea/`, `*.suo`, and `*.DotSettings.user` paths in the tarball) and `validatePublishedFilesArePairedWithMetas` (every shipped Unity-relevant file has its `.meta` neighbour and every shipped directory has its directory `.meta`), wired into `prepack` and the `validate-npm-meta` workflow so the next publish cannot reintroduce the regression.
542
+ - Resolved [issue #204](https://github.com/Ambiguous-Interactive/DxMessaging/issues/204) (build artifacts and orphaned `.meta` files leaking into the npm tarball) and prevented its regression: `scripts/validate-npm-meta.js` validates real `npm pack --json --dry-run --ignore-scripts` output (and release tarballs via `--tarball`) by rejecting `bin/`, `obj/`, `*.pdb`, `*.tmp`, `*.csproj.user`, `.vs/`, `.idea/`, `*.suo`, `*.user`, and `*.DotSettings.user` paths, while enforcing `.meta` pairing for shipped Unity files and directories. The guard now runs in `validate:all`, pre-push hooks, and the release workflow.
48
543
 
49
544
  ## [2.2.0]
50
545
 
@@ -90,7 +585,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
90
585
  - Wiki synchronization workflow that automatically syncs documentation to GitHub Wiki
91
586
  - Documentation validation workflow that runs on pull requests and pushes
92
587
  - MkDocs build validation in pre-push hooks
93
- - Searchable documentation site at <https://wallstop.github.io/DxMessaging/>
588
+ - Searchable documentation site at <https://ambiguous-interactive.github.io/DxMessaging/>
94
589
  - Theme-aware Mermaid diagrams with automatic light/dark mode switching for GitHub Pages
95
590
  - User-visible error messages when Mermaid diagrams fail to render
96
591
 
@@ -6,6 +6,6 @@ MonoImporter:
6
6
  defaultReferences: []
7
7
  executionOrder: 0
8
8
  icon: {instanceID: 0}
9
- userData:
10
- assetBundleName:
11
- assetBundleVariant:
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant:
@@ -6,6 +6,6 @@ MonoImporter:
6
6
  defaultReferences: []
7
7
  executionOrder: 0
8
8
  icon: {instanceID: 0}
9
- userData:
10
- assetBundleName:
11
- assetBundleVariant:
9
+ userData:
10
+ assetBundleName:
11
+ assetBundleVariant: