com.wallstop-studios.dxmessaging 3.1.0 → 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 +286 -0
- package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +3 -3
- package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +3 -3
- package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +3 -3
- package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
- package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
- package/Editor/DxMessagingMenu.cs.meta +3 -3
- package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +2 -3
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
- package/Editor/SetupCscRsp.cs +232 -163
- package/README.md +1 -1
- package/Runtime/Analyzers.meta +8 -0
- package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
- package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
- package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
- package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
- package/Runtime/Core/IMessage.cs.meta +3 -3
- package/Runtime/Core/InstanceId.cs.meta +3 -3
- package/Runtime/Core/Internal/TypedSlots.cs +42 -0
- package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
- package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageBus.cs +63 -28
- package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessageBus.cs +548 -474
- 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.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 +1210 -394
- package/Runtime/Core/MessageHandler.cs.meta +3 -3
- package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
- package/Runtime/Core/MessageRegistrationToken.cs +1296 -819
- package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
- 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/SourcedStringMessage.cs.meta +3 -3
- 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/ReflexRegistrationInstaller.cs.meta +3 -3
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
- package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
- package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
- 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/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 +5 -5
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +12 -11
- package/package.json +6 -3
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,292 @@ 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
|
+
|
|
10
296
|
## [3.1.0]
|
|
11
297
|
|
|
12
298
|
### Added
|
|
@@ -66,9 +66,8 @@ namespace DxMessaging.Editor.Settings
|
|
|
66
66
|
|
|
67
67
|
/// <summary>
|
|
68
68
|
/// Regenerates the sidecar text file from the supplied settings asset. Writes only when
|
|
69
|
-
/// the on-disk content differs from what would be written,
|
|
70
|
-
///
|
|
71
|
-
/// AssetDatabase churn during domain reload.
|
|
69
|
+
/// the on-disk content differs from what would be written, to avoid AssetDatabase churn
|
|
70
|
+
/// during domain reload.
|
|
72
71
|
/// </summary>
|
|
73
72
|
/// <param name="settings">The settings asset. May be <c>null</c> -- no-op in that case.</param>
|
|
74
73
|
/// <remarks>
|