Eliminate redundant PFE physics updates and cell snapshots
Follow-up to post-PR2574 production analysis tsk_hyt5pej7t7. Implement two largest remaining hot-loop improvements in a separate branch from develop so it can merge independently of open corpse lifecycle PR #2575. Worktree /home/mathias/Projects/worktrees/engines-physics-cells; branch perf/dirty-physics-cell-snapshots; base origin/develop f62d874ca. Evidence - At ~200 players, PFEs consume roughly 3–5.5 cores each in point samples. Managed wall-clock trace identifies PhysicsManagementPfSystem.OnTick 5.27% inclusive / 0.82% exclusive and Bepu Tree.GetOverlaps 3.22% exclusive, the largest active leaf. Current OnTick iterates every player and NPC in every awake playfield and calls StaticContainer.SetPosition/Simulation.Statics.ApplyDescription even when position is unchanged. Capture has 18,861 NPCs across 38 playfields. - Allocation remains 93.2 MiB/s / 11.5 minor GCs/s. Character[] is the largest type (~939 MB estimated in 61.8s). PlayfieldCell.HeartbeatCell and multiple broadcast/accessor paths copy _subscribers with ToArray/ToList; HeartbeatNpc is 6.69% inclusive. Concurrency constraints AOEMU_PARALLEL_CELL_TICKS=true is supported. Different cell workers and session/lifecycle threads may overlap. Do not replace copies with raw unsynchronized List enumeration or shared scratch. Physics simulation mutation remains single-owner under its existing write lock; producers publish/coalesce movement safely. Cell readers consume immutable/versioned snapshots atomically published only after subscription mutation under the cell lock. Late/stale movement and teardown cannot re-add removed bodies or apply old generation positions. Acceptance - Unchanged character poses cause zero Bepu ApplyDescription/broadphase updates. Multiple moves before owner drain coalesce to latest pose. Add/remove/move/cleanup races are deterministic and stale updates rejected. Existing raycasts/collision behavior preserved. - Heartbeat reads a stable immutable NPC/subscriber view without per-tick array/list construction. Subscribe/unsubscribe/current-cell transitions atomically publish snapshots; parallel readers never observe a mutating collection. Existing visibility/broadcast semantics and no-duplicate invariants preserved. - Focused warm tests assert zero allocation for unchanged physics tick and stable cell heartbeat/snapshot reads. Parallel churn/teardown stress and behavior regressions pass. - Extend infrastructure-free harness with physics unchanged/dirty and cell stable/churn scenarios; quantify before/after CPU, operations and allocation without login/DB/RabbitMQ/game assets. - Engine solution builds with warnings-as-errors. No production deployment or live experiment. Verified implementation milestone - StaticContainer now owns a last-position map beside each Bepu StaticHandle. Add/remove update both under the existing physics write lock. SetPosition returns before GetDescription/ApplyDescription when the exact pose is unchanged, so broadphase refit/overlap work is avoided; removed identities reject stale updates. System still performs a cheap owner scan of current entities each awake tick, but unchanged entities never enter Bepu. - PlayfieldCell now publishes a PlayfieldSubscriberSnapshot atomically whenever Subscribe/Unsubscribe mutates the locked source list. The snapshot wraps a copied array behind IReadOnlyList and a struct enumerator/ReadOnlySpan: existing readers cannot mutate it, direct foreach/heartbeat reads do not box or allocate, and old readers retain a stable version during concurrent churn. All former internal per-call ToArray copies for heartbeat, broadcast and cell transition notification read the published snapshot instead. LocalSubscribers remains an explicitly allocated filtered view for infrequent callers. - Physics tests: 100k unchanged submissions allocate exactly zero and produce zero ApplyDescription calls; actual changes apply once; removed bodies reject stale positions. Cell tests: old snapshot remains unchanged after publication, 30m warmed span reads allocate exactly zero, and 100k parallel reads during 10k subscribe/unsubscribe cycles observe no nulls/duplicates. - Infrastructure-free harness adds `physics` and `cell` scenarios. Production shapes: 20k unchanged bodies ×100 ticks = 2m submissions in ~103ms, 904 aggregate harness bytes, no GC, zero Bepu updates; 2k subscribers ×1k reads = 2m reads in ~15ms, 8,104 aggregate sample/harness bytes, no GC. Exact focused kernels are zero-allocation; timings are local comparison evidence. - 82 focused physics/cell/spawn/entrance/exit/dynel regressions pass, no skips. TheThreeEngines.sln builds with TreatWarningsAsErrors=true and zero warnings/errors in the explicit Bookworm CI container. git diff --check passes. - Committed as 8254444bd (`perf: Skip redundant physics and cell snapshot work`), then merged current origin/develop (including #2575) as 6fd1fdf42 without rewriting history. Post-merge validation reproduces the same passing build/tests/harness outcomes. Branch pushed and worktree clean. Pull request - #2586: perf: Skip redundant physics and cell snapshot work - https://git.project-rk.com/prk/engines/pulls/2586 - Head perf/dirty-physics-cell-snapshots -> develop. Ready for review; no deployment performed.
Checklist
3 of 3 complete- Skip unchanged Bepu character poses with owner-side cached positions and stale-remove regression coverage.
- Publish immutable cell subscriber snapshots on mutation and remove per-read/per-heartbeat copies safely under parallel readers.
- Validate allocation, concurrency and gameplay regressions; record local harness evidence and engine build.
- Area
- Project Rubi-Ka
- Project
- engines
- Kind
- Bug
- Priority
- High
- Task ID
tsk_p6rah2v72a- Updated