fix(physics): make collision activation starvation-free
This commit is contained in:
parent
d94145e6b8
commit
6b28ff999c
14 changed files with 4637 additions and 474 deletions
|
|
@ -20,35 +20,86 @@ generation, and correctness depended on a later optional landblock callback.
|
|||
The asynchronous unit is now one Runtime-owned collision generation:
|
||||
|
||||
1. `BeginCollisionAdmission` issues the exact Runtime/landblock generation.
|
||||
2. `PrepareCollisionGeneration` clones the bounded resident spatial records
|
||||
into a private cache, graph, engine, and shadow registry. Global immutable
|
||||
GfxObj/Setup catalogs are not copied; the accepted build's exact closure is
|
||||
populated by the existing cursors.
|
||||
3. App and Headless publish terrain, EnvCells, topology, buildings, prepared
|
||||
2. `PrepareCollisionGeneration` creates empty private cache, graph, engine, and
|
||||
shadow facades and retains the active aggregate root reference in O(1).
|
||||
Global immutable GfxObj/Setup catalogs are not copied; the accepted build's
|
||||
exact closure is populated by the existing cursors.
|
||||
3. Stable
|
||||
landblock and logical-owner slot suffixes then materialize each non-target
|
||||
cache, CellGraph, engine, and shadow leaf into an empty private root under
|
||||
the host's existing frame meter. The 32-resident-landblock gate proves
|
||||
admission performs no resident copy and every advance reports at most one
|
||||
work unit.
|
||||
4. App and Headless publish terrain, EnvCells, topology, buildings, prepared
|
||||
collision assets, and target-root static owners only into that private
|
||||
generation.
|
||||
4. A mutation-stable cursor captures every non-suspended owner that touches —
|
||||
or has a withdrawn repair marker for — the target prefix. That includes
|
||||
live dynamic owners and statics rooted in an adjacent landblock. Only a
|
||||
5. Stable per-prefix owner slots capture every non-suspended owner that touches
|
||||
or has a withdrawn repair marker for the target prefix. That includes live
|
||||
dynamic owners and statics rooted in an adjacent landblock. Only a
|
||||
target-root static is omitted, because the authored replacement supersedes
|
||||
it. Each retained owner refresh captures its exact
|
||||
`ShadowObjectRegistry` mutation version; a rowless withdrawn owner therefore
|
||||
remains freshness-gated when state or payload changes.
|
||||
5. Explicit one-work-unit cursors build the complete replacement before the
|
||||
it. The scan has a fixed slot suffix and is unaffected by mutations in other
|
||||
prefixes. Vacated slots are tombstoned and reused rather than retained for
|
||||
the whole session. A single Runtime-scoped versioned journal records each
|
||||
mutation once and coalesces repeated changes by logical owner, independently
|
||||
of the number of live drafts. After topology sealing, each draft reconciles
|
||||
the latest exact state of owners changed during that draft's lifetime one
|
||||
owner per seal call. A discovered relevant owner then receives scoped exact
|
||||
updates, preserving continuous-motion progress without restoring global
|
||||
fanout. A membership transition is routed by the owner's changed landblock
|
||||
prefix to the one matching draft, so an owner first entering or leaving the
|
||||
target after its global journal slot was visited is still reconciled once.
|
||||
During topology construction, a visited unrelated owner retains only a cheap
|
||||
coalesced notification; its exact mirror runs later as one metered seal unit
|
||||
rather than once per draft on the mutation path. Once the topology seal
|
||||
exists, observed owners temporarily write through exactly until same-call
|
||||
activation. The finite pre-seal queue therefore drains even when two or more
|
||||
unrelated owners mutate before every host step. Slots predating a newer root snapshot are superseded
|
||||
by a tail slot, not reused behind live cursors. New drafts begin at their
|
||||
captured suffix, obsolete slots compact one visit per seal call, and the
|
||||
journal clears when its last draft closes. Unrelated and continuously moving
|
||||
owners therefore never restart capture or sealing.
|
||||
6. Explicit one-work-unit cursors build the complete replacement before the
|
||||
activation frame: requested global collision records, cells/topology,
|
||||
buildings, cell graph removals, affected static owners, retained-owner
|
||||
states, and removal lists. An active-owner mutation restarts capture and
|
||||
sealing without touching the active world.
|
||||
6. `CommitCollisionGeneration` performs only the final mutation-version check
|
||||
and installs the already sealed replacement synchronously on the update
|
||||
thread. The dense 256-owner gate measures zero managed bytes in this final
|
||||
activation. Only after the complete replacement does Runtime emit
|
||||
`CollisionGenerationCommitted` and a ready acknowledgement.
|
||||
states, and removal lists. A late unarmed relevant owner consumes at most
|
||||
one refresh unit on a seal call; when that drains the queue an already-built
|
||||
seal is immediately ready. Immutable global GfxObj/Setup closure entries are
|
||||
preinstalled during these metered steps, not during activation.
|
||||
7. Cache, CellGraph, engine-landblock, and shadow topology share one
|
||||
`CollisionWorldStateSlot`. `CommitCollisionGeneration` transfers the
|
||||
complete off-side aggregate through one volatile reference on the update
|
||||
thread, then revokes the staging slot. The public `PhysicsDataCache`,
|
||||
`CellGraph`, `PhysicsEngine`, and `ShadowObjectRegistry` facade identities
|
||||
stay stable. Warm 256-owner, cold
|
||||
first-load, changed EnvCell/building, and new static-bucket gates all measure
|
||||
exactly zero managed bytes in final activation. Only afterwards does Runtime
|
||||
emit `CollisionGenerationCommitted` and a ready acknowledgement.
|
||||
8. Multiple landblocks may prepare concurrently. Preparation order is the
|
||||
activation order. Only after an older generation commits is its exact delta
|
||||
queued into every later draft. Each additional seal call applies at most one
|
||||
cache, CellGraph, synthesized outdoor-cell, engine-landblock, or logical
|
||||
owner leaf. Later generations retain their own completed target seal but
|
||||
cannot activate before every committed delta drains. Cancelled older drafts
|
||||
therefore contribute nothing, final activation performs no peer work, and a
|
||||
later root cannot overwrite or expose an older snapshot. Seam-crossing
|
||||
statics are forcibly re-evaluated against the later topology. Demotion and
|
||||
withdrawal cancel a matching queued or active rebase, suppress that prefix
|
||||
in unfinished source scans, and retire one shadow owner, cache/graph leaf,
|
||||
authored outdoor cell, or landblock leaf per later seal call before
|
||||
activation. Retirement storage is growable rather than coupled to the
|
||||
concurrent-preparation limit, and final commit rechecks both pending rebase
|
||||
and retirement work after the seal-to-commit gap.
|
||||
The host performs the zero-work root transfer in the same update-thread call
|
||||
that completes final reconciliation, eliminating a seal-to-next-frame quiet
|
||||
window for continuously moving unrelated owners.
|
||||
9. GfxObj/Setup closure entries are immutable content-addressed catalog data,
|
||||
not world topology. Their metered early installation may survive a cancelled
|
||||
generation as ordinary process cache residency; no cell, building,
|
||||
landblock, or shadow becomes visible through that catalog alone.
|
||||
|
||||
The stable borrowed `PhysicsEngine` and `PhysicsDataCache` object identities do
|
||||
not change. Presentation and no-window hosts use the same Runtime transaction.
|
||||
Network workers still enqueue immutable messages and cannot mutate collision or
|
||||
shadow state.
|
||||
Presentation and no-window hosts use the same Runtime transaction. Network
|
||||
workers still enqueue immutable messages and cannot mutate collision or shadow
|
||||
state.
|
||||
|
||||
## Failure and lifetime rules
|
||||
|
||||
|
|
@ -71,17 +122,51 @@ The focused Runtime/App tests pin:
|
|||
- previous terrain/cells/buildings/statics remain visible until commit;
|
||||
- exactly one notification after a successful complete activation;
|
||||
- stale admission replacement has no active-world side effect;
|
||||
- movement during staging rejects, refreshes only the dirty owner, and then
|
||||
installs its latest cell set;
|
||||
- an authoritative state change on a retained rowless owner rejects a stale
|
||||
seal and installs the refreshed state on retry;
|
||||
- unrelated movement on every capture/seal step never restarts the target;
|
||||
- two relevant owners moving on every seal step converge without restarting
|
||||
the topology meter and install their latest positions at activation;
|
||||
- an authoritative state change on a retained rowless owner updates an
|
||||
already-sealed generation without a global restart;
|
||||
- a neighboring static whose shadow crossed the seam is restored atomically
|
||||
on reload and its withdrawn-prefix marker clears only at activation;
|
||||
- spawn and deletion during staging both reject stale activation;
|
||||
- a late spawn blocks activation until its one metered refresh; deletion of an
|
||||
armed owner writes through directly;
|
||||
- Headless faults immediately after admission and after staging preserve the
|
||||
prior complete world and leave no collision admission behind;
|
||||
- dense sealing consumes at most one work unit per call, while its final
|
||||
256-owner activation allocates zero managed bytes;
|
||||
- dense sealing consumes at most one work unit per call, while warm 256-owner,
|
||||
cold first-load, changed EnvCell/building, and new static-bucket activation
|
||||
all allocate zero managed bytes;
|
||||
- concurrently prepared landblocks rebase and preserve both terrain roots and
|
||||
static-shadow owners across their activation order, with zero-byte final
|
||||
commits and revoked staging access;
|
||||
- dense 32-landblock admission performs no resident copy, stays within its
|
||||
constant allocation envelope, and materializes at most one leaf per advance;
|
||||
- cancelled older drafts contribute no topology to later roots, while a live
|
||||
owner mutation after the older commit wins over the queued rebase;
|
||||
- a newly committed seam-crossing static refloods against the later draft's
|
||||
topology before that draft may activate;
|
||||
- post-seal arrivals drain one owner per seal call without resetting capture;
|
||||
- an unrelated owner entering the target after its journal slot was visited is
|
||||
routed by prefix and reconciled in one metered seal unit;
|
||||
- target departure and same-ID reuse preserve the exact new-prefix owner rows;
|
||||
- unrelated state mutation publishes only after every row changes;
|
||||
- unrelated demotion/withdrawal and the live `CurrCell` cannot be resurrected
|
||||
or rolled back by a later draft;
|
||||
- queued and partially applied peer rebases cannot resurrect a later demoted or
|
||||
withdrawn landblock;
|
||||
- deleting an outgoing target static before, during, or after staging cannot
|
||||
erase an authored same-ID replacement;
|
||||
- 10,000 repeated mutations with 32 drafts retain one coalesced journal entry
|
||||
and allocate no more than the owner mutation itself;
|
||||
- 512 unique changed owners reconcile in exactly 512 metered seal units and
|
||||
the final activation still allocates zero managed bytes;
|
||||
- compacted journal slots are never reused behind a live cursor, while a
|
||||
4,096-slot obsolete tail retires incrementally and a later draft starts at
|
||||
its captured suffix rather than scanning old tombstones;
|
||||
- post-seal retirement blocks activation until its cursor drains, and more
|
||||
than 256 distinct retirements remain metered and lossless;
|
||||
- prefix-owner slots remain bounded under GUID churn and empty containers are
|
||||
reclaimed across unique prefixes without invalidating a live seal cursor;
|
||||
- graphical and no-window publishers use the same Runtime transaction;
|
||||
- removal and terminal teardown converge the active ownership ledger.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue