perf(pipeline): MP-Alloc Task 4 - reuse per-frame animatedIds + drawableCells sets
Completes the safe-batch (implementer finished this but died before committing; coordinator independently verified bit-identity + safety post-interruption). Two per-frame HashSet<uint> allocations replaced by reused cleared-in-place fields: - GameWindow._animatedIdsScratch: WalkEntitiesInto treats null and empty identically (line 682 `is null || Count == 0`), so an always-non-null empty set == the old null-when-empty local. Verified. - RetailPViewRenderer._drawableCellsScratch: flows into RetailPViewFrameResult.DrawableCells (live ref) but every consumer reads it same-frame; sigDrawableCells is a per-frame OnRender local that EmitRenderSignatureIfChanged only FORMATS to a string (no reference retention, no _lastSig set field). Built frame N -> read frame N -> cleared frame N+1. No cross-frame corruption. Build green, full suite 4116 passed / 4 skipped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
76b8b4e9fc
commit
91afea24b4
2 changed files with 26 additions and 8 deletions
|
|
@ -252,6 +252,13 @@ public sealed class GameWindow : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<uint, AnimatedEntity> _animatedEntities = new();
|
private readonly Dictionary<uint, AnimatedEntity> _animatedEntities = new();
|
||||||
|
|
||||||
|
// MP-Alloc (2026-07-05): reusable per-frame snapshot of _animatedEntities.Keys.
|
||||||
|
// WbDrawDispatcher.WalkEntitiesInto treats null and an empty set identically
|
||||||
|
// (`animatedEntityIds is null || animatedEntityIds.Count == 0`), so this can
|
||||||
|
// always be a live (possibly empty) HashSet instead of `new`ing one every
|
||||||
|
// frame or passing null.
|
||||||
|
private readonly HashSet<uint> _animatedIdsScratch = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tier 1 cache (#53): per-entity classification results for static
|
/// Tier 1 cache (#53): per-entity classification results for static
|
||||||
/// entities (those NOT in <see cref="_animatedEntities"/>). Conceptually
|
/// entities (those NOT in <see cref="_animatedEntities"/>). Conceptually
|
||||||
|
|
@ -9104,13 +9111,15 @@ public sealed class GameWindow : IDisposable
|
||||||
var envCellViewProj = camera.View * camera.Projection;
|
var envCellViewProj = camera.View * camera.Projection;
|
||||||
_envCellFrustum?.Update(envCellViewProj);
|
_envCellFrustum?.Update(envCellViewProj);
|
||||||
|
|
||||||
HashSet<uint>? animatedIds = null;
|
// MP-Alloc: reuse _animatedIdsScratch instead of `new`ing a
|
||||||
if (_animatedEntities.Count > 0)
|
// HashSet<uint> every frame. Downstream consumers (WbDrawDispatcher.
|
||||||
{
|
// WalkEntitiesInto) treat null and an empty set identically, so an
|
||||||
animatedIds = new HashSet<uint>(_animatedEntities.Count);
|
// always-non-null (possibly empty) set is behaviorally the same as
|
||||||
foreach (var k in _animatedEntities.Keys)
|
// the old null-when-nothing-animated local.
|
||||||
animatedIds.Add(k);
|
_animatedIdsScratch.Clear();
|
||||||
}
|
foreach (var k in _animatedEntities.Keys)
|
||||||
|
_animatedIdsScratch.Add(k);
|
||||||
|
HashSet<uint>? animatedIds = _animatedIdsScratch;
|
||||||
|
|
||||||
// Phase G.1: sky renderer — draws the far-plane-infinity
|
// Phase G.1: sky renderer — draws the far-plane-infinity
|
||||||
// celestial meshes FIRST so the rest of the scene z-tests
|
// celestial meshes FIRST so the rest of the scene z-tests
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,13 @@ public sealed class RetailPViewRenderer
|
||||||
// visible.
|
// visible.
|
||||||
private readonly InteriorEntityPartition.Result _partitionResult = new();
|
private readonly InteriorEntityPartition.Result _partitionResult = new();
|
||||||
|
|
||||||
|
// MP-Alloc (2026-07-05): DrawInside's drawable-cell set, reused across
|
||||||
|
// frames instead of `new HashSet<uint>(pvFrame.OrderedVisibleCells)` every
|
||||||
|
// call. Every consumer (DrawEntityBucket, DrawExitPortalMasks,
|
||||||
|
// DrawCellObjectLists, RetailPViewFrameResult.DrawableCells) reads it
|
||||||
|
// synchronously within the same frame it was built.
|
||||||
|
private readonly HashSet<uint> _drawableCellsScratch = new();
|
||||||
|
|
||||||
// T2 (BR-4): retail has NO distance constant on the flood-admission chain
|
// T2 (BR-4): retail has NO distance constant on the flood-admission chain
|
||||||
// (DrawBuilding → portal walk → ConstructView: viewconeCheck + side test +
|
// (DrawBuilding → portal walk → ConstructView: viewconeCheck + side test +
|
||||||
// GetClip + GetVisible only). The old 48 m seed cap is replaced by the
|
// GetClip + GetVisible only). The old 48 m seed cap is replaced by the
|
||||||
|
|
@ -118,7 +125,9 @@ public sealed class RetailPViewRenderer
|
||||||
// so every visible cell's shell has a prepared batch and seals — killing the grey
|
// so every visible cell's shell has a prepared batch and seals — killing the grey
|
||||||
// (the old clipAssembly.CellIdToSlot.Keys filter silently dropped slot-less cells).
|
// (the old clipAssembly.CellIdToSlot.Keys filter silently dropped slot-less cells).
|
||||||
// Per-slice trim still applies in DrawEnvCellShells (Task 4 makes it self-contained).
|
// Per-slice trim still applies in DrawEnvCellShells (Task 4 makes it self-contained).
|
||||||
var drawableCells = new HashSet<uint>(pvFrame.OrderedVisibleCells);
|
_drawableCellsScratch.Clear();
|
||||||
|
_drawableCellsScratch.UnionWith(pvFrame.OrderedVisibleCells);
|
||||||
|
var drawableCells = _drawableCellsScratch;
|
||||||
UseIndoorMembershipOnlyRouting();
|
UseIndoorMembershipOnlyRouting();
|
||||||
|
|
||||||
// #124: look-in cells need prepared shell batches + their statics routed
|
// #124: look-in cells need prepared shell batches + their statics routed
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue