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.
- package/CHANGELOG.md +497 -2
- package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +3 -3
- package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +3 -3
- package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +69 -62
- package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +3 -3
- package/Editor/AssemblyInfo.cs.meta +9 -1
- package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
- package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +24 -15
- package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
- package/Editor/CustomEditors/MessagingComponentEditor.cs.meta +9 -1
- package/Editor/DxMessagingEditorIdle.cs +62 -0
- package/{Runtime/Core/Internal/TypedDispatchLinkIndex.cs.meta → Editor/DxMessagingEditorIdle.cs.meta} +1 -1
- package/Editor/DxMessagingEditorInitializer.cs +112 -15
- package/Editor/DxMessagingEditorInitializer.cs.meta +9 -1
- package/Editor/DxMessagingEditorLog.cs +32 -0
- package/Editor/DxMessagingEditorLog.cs.meta +11 -0
- package/Editor/DxMessagingMenu.cs.meta +3 -3
- package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +137 -15
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
- package/Editor/Settings/DxMessagingSettings.cs +92 -31
- package/Editor/Settings/DxMessagingSettings.cs.meta +9 -1
- package/Editor/Settings/DxMessagingSettingsProvider.cs.meta +9 -1
- package/Editor/SetupCscRsp.cs +539 -304
- package/Editor/SetupCscRsp.cs.meta +9 -1
- package/Editor/Testing/MessagingComponentEditorHarness.cs +1 -1
- package/Editor/Testing/MessagingComponentEditorHarness.cs.meta +9 -1
- package/README.md +17 -18
- package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +3 -3
- package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +3 -3
- package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +3 -3
- package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +3 -3
- package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +3 -3
- package/Runtime/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
- package/Runtime/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +46 -0
- package/Runtime/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +2 -2
- package/Runtime/Analyzers.meta +8 -0
- package/Runtime/AssemblyInfo.cs.meta +9 -1
- package/Runtime/Core/Attributes/DxAutoConstructorAttribute.cs.meta +9 -1
- package/Runtime/Core/Attributes/DxBroadcastMessageAttribute.cs.meta +9 -1
- package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
- package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs +2 -4
- package/Runtime/Core/Attributes/DxOptionalParameterAttribute.cs.meta +9 -1
- package/Runtime/Core/Attributes/DxTargetedMessageAttribute.cs.meta +9 -1
- package/Runtime/Core/Attributes/DxUntargetedMessageAttribute.cs.meta +9 -1
- package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs +56 -0
- package/Runtime/Core/Attributes/Il2CppSetOptionAttribute.cs.meta +11 -0
- package/Runtime/Core/DataStructure/CyclicBuffer.cs +44 -26
- package/Runtime/Core/DataStructure/CyclicBuffer.cs.meta +9 -1
- package/Runtime/Core/Diagnostics/MessageEmissionData.cs.meta +9 -1
- package/Runtime/Core/Diagnostics/MessageRegistrationData.cs.meta +9 -1
- package/Runtime/Core/Diagnostics/MessageRegistrationType.cs.meta +9 -1
- package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
- package/Runtime/Core/Extensions/EnumExtensions.cs +6 -5
- package/Runtime/Core/Extensions/EnumExtensions.cs.meta +9 -1
- package/Runtime/Core/Extensions/IListExtensions.cs.meta +9 -1
- package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
- package/Runtime/Core/Extensions/MessageExtensions.cs +0 -60
- package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
- package/Runtime/Core/Helper/MessageCache.cs.meta +9 -1
- package/Runtime/Core/Helper/MessageHelperIndexer.cs.meta +9 -1
- package/Runtime/Core/IMessage.cs.meta +3 -3
- package/Runtime/Core/InstanceId.cs +25 -1
- package/Runtime/Core/InstanceId.cs.meta +3 -3
- package/Runtime/Core/Internal/DxUnsafe.cs +60 -0
- package/Runtime/Core/Internal/DxUnsafe.cs.meta +11 -0
- package/Runtime/Core/Internal/FlatDispatch.cs +198 -0
- package/Runtime/Core/Internal/FlatDispatch.cs.meta +11 -0
- package/Runtime/Core/Internal/TypedSlots.cs +47 -21
- package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
- package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageBus.cs +75 -40
- package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs +1 -0
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
- package/Runtime/Core/MessageBus/Internal/BusSlots.cs +7 -6
- package/Runtime/Core/MessageBus/MessageBus.cs +2864 -3413
- package/Runtime/Core/MessageBus/MessageBus.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessageBusRegistration.cs +207 -0
- package/Runtime/Core/MessageBus/MessageBusRegistration.cs.meta +11 -0
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs +187 -14
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +3 -3
- package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +3 -3
- package/Runtime/Core/MessageHandler.cs +2206 -1510
- package/Runtime/Core/MessageHandler.cs.meta +3 -3
- package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
- package/Runtime/Core/MessageRegistrationToken.cs +1667 -812
- package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
- package/Runtime/Core/Messages/GlobalStringMessage.cs.meta +9 -1
- package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +3 -3
- package/Runtime/Core/Messages/ITargetedMessage.cs.meta +3 -3
- package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +3 -3
- package/Runtime/Core/Messages/ReflexiveMessage.cs.meta +9 -1
- package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +3 -3
- package/Runtime/Core/Messages/StringMessage.cs.meta +9 -1
- package/Runtime/Core/MessagingDebug.cs.meta +3 -3
- package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +3 -3
- package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +3 -3
- package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +3 -3
- package/Runtime/Unity/Integrations/Reflex/AssemblyInfo.cs.meta +9 -1
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +3 -3
- package/Runtime/Unity/Integrations/VContainer/AssemblyInfo.cs.meta +9 -1
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
- package/Runtime/Unity/Integrations/Zenject/AssemblyInfo.cs.meta +9 -1
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
- package/Runtime/Unity/MessageAwareComponent.cs +46 -1
- package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
- package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
- package/Runtime/Unity/MessagingComponent.cs +43 -10
- package/Runtime/Unity/MessagingComponent.cs.meta +3 -3
- package/Runtime/Unity/MessagingComponentInstaller.cs.meta +3 -3
- package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +3 -3
- package/Runtime/WallstopStudios.DxMessaging.asmdef +1 -1
- package/Samples~/DI/README.md +7 -7
- package/Samples~/DI/Reflex/SampleInstaller.cs.meta +3 -3
- package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +3 -3
- package/Samples~/DI/Zenject/SampleInstaller.cs.meta +3 -3
- package/Samples~/Mini Combat/Boot.cs.meta +3 -3
- package/Samples~/Mini Combat/Enemy.cs.meta +3 -3
- package/Samples~/Mini Combat/Messages.cs.meta +3 -3
- package/Samples~/Mini Combat/Player.cs.meta +3 -3
- package/Samples~/Mini Combat/UIOverlay.cs.meta +3 -3
- package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +3 -3
- package/Samples~/UI Buttons + Inspector/Messages.cs.meta +3 -3
- package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +3 -3
- package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +3 -3
- package/SourceGenerators/Directory.Build.props +50 -3
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs +96 -63
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs +745 -87
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxMessageIdGenerator.cs.meta +9 -1
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +40 -46
- package/SourceGenerators/global.json +7 -0
- package/SourceGenerators/global.json.meta +7 -0
- package/package.json +30 -40
- package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
- package/Editor/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -33
- package/Editor/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- package/Runtime/Core/Internal/TypedDispatchLinkIndex.cs +0 -51
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
- /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/
|
|
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://
|
|
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
|
|