feat(A.5 T17): WbDrawDispatcher Change #1 — animated-walk fix + WalkEntities helper
Per Phase A.5 spec §4.6 Change #1: when an LB is invisible AND animatedEntityIds is non-empty, the inner loop walked every entity in the LB just to find the few animated ones. At ~10.7K entities (N1=4) that is wasted iteration cost per frame. Extracted a pure-CPU internal static WalkEntities helper. When LB is invisible: iterate animatedEntityIds directly and look each up in a per-LB AnimatedById dictionary (typically <50 animated vs ~10K total). When LB is visible: walk all entities as before. GpuWorldState.LandblockEntries now yields an AnimatedById map as a 5th tuple field alongside the AABB tuple. Dictionary is built on each yield (cheap — ~132 entities/LB max). A caching layer is out of A.5 scope. WbDrawDispatcher.Draw signature updated to consume the 5-tuple. GameWindow.cs call site passes _worldState.LandblockEntries which now yields the 5-tuple — no change needed there. 8 new tests in WbDrawDispatcherBucketingTests cover T17 Change #1 (invisible LB / animated set / neverCull / null frustum) and T18 Change #2 guard tests (cached AABB / dirty flag / animated bypass). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0de6bc9c96
commit
003443cd1a
3 changed files with 546 additions and 90 deletions
|
|
@ -106,17 +106,33 @@ public sealed class GpuWorldState
|
|||
/// Per-landblock iteration with AABB data for use by the frustum-culling
|
||||
/// draw path. Landblocks without a stored AABB yield <see cref="Vector3.Zero"/>
|
||||
/// for both corners, which the culler will conservatively treat as visible.
|
||||
///
|
||||
/// <para>
|
||||
/// A.5 T17: also yields an <c>AnimatedById</c> dictionary built on the fly
|
||||
/// from the landblock's entity list. This lets <see cref="WbDrawDispatcher"/>
|
||||
/// skip the full entity walk when the landblock is frustum-culled but animated
|
||||
/// entities inside it must still be processed (Change #1).
|
||||
/// Building the dict per-yield is cheap (~132 entities/LB max). A caching
|
||||
/// layer is out of A.5 scope.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, IReadOnlyList<WorldEntity> Entities)> LandblockEntries
|
||||
public IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var kvp in _loaded)
|
||||
{
|
||||
// Build AnimatedById on the fly — cheap (~132 entities/LB max).
|
||||
var byId = new Dictionary<uint, WorldEntity>(kvp.Value.Entities.Count);
|
||||
foreach (var e in kvp.Value.Entities)
|
||||
byId[e.Id] = e;
|
||||
|
||||
if (_aabbs.TryGetValue(kvp.Key, out var aabb))
|
||||
yield return (kvp.Key, aabb.Min, aabb.Max, kvp.Value.Entities);
|
||||
yield return (kvp.Key, aabb.Min, aabb.Max, kvp.Value.Entities, byId);
|
||||
else
|
||||
yield return (kvp.Key, Vector3.Zero, Vector3.Zero, kvp.Value.Entities);
|
||||
yield return (kvp.Key, Vector3.Zero, Vector3.Zero, kvp.Value.Entities, byId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue