feat(A.5 T18): use cached WorldEntity AABB in dispatcher; populate at register
Per Phase A.5 spec §4.6 Change #2: WalkEntities's per-entity AABB frustum cull was recomputing Position±5 per frame per entity. With ~10.7K entities (N1=4) at 240 FPS that is ~2.5M wasted Vector3 ops/sec. Read the AABB from the WorldEntity cache (T8 schema) instead. RefreshAabb runs lazily on AabbDirty=true. Populate at register time: - LandblockLoader.BuildEntitiesFromInfo: RefreshAabb after each new WorldEntity construction (stabs + buildings). Refactored from inline object-initializer to named variable to enable the call. - EntitySpawnAdapter.OnCreate: RefreshAabb after entity state init (position/rotation already set via the WorldEntity passed in). Dynamic entities (NPCs, players) move every frame via direct Position writes in GameWindow.cs. Migrated all three per-frame write sites to SetPosition() (T8 mutator) so AabbDirty propagates: - line 5942: player entity render position update - line 6951: remote animated entity interpolated path - line 7279: remote animated entity landing/movement path The lazy RefreshAabb in WalkEntities catches up on the next frame after any SetPosition call — render thread only, no races. Build green, 986 passed / 8 pre-existing failures unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
003443cd1a
commit
0afd741ea7
3 changed files with 17 additions and 7 deletions
|
|
@ -5939,7 +5939,7 @@ public sealed class GameWindow : IDisposable
|
||||||
// the physics-resolved location each frame.
|
// the physics-resolved location each frame.
|
||||||
if (_entitiesByServerGuid.TryGetValue(_playerServerGuid, out var pe))
|
if (_entitiesByServerGuid.TryGetValue(_playerServerGuid, out var pe))
|
||||||
{
|
{
|
||||||
pe.Position = result.RenderPosition;
|
pe.SetPosition(result.RenderPosition); // A.5 T18: SetPosition propagates AabbDirty
|
||||||
pe.Rotation = System.Numerics.Quaternion.CreateFromAxisAngle(
|
pe.Rotation = System.Numerics.Quaternion.CreateFromAxisAngle(
|
||||||
System.Numerics.Vector3.UnitZ, _playerController.Yaw - MathF.PI / 2f);
|
System.Numerics.Vector3.UnitZ, _playerController.Yaw - MathF.PI / 2f);
|
||||||
|
|
||||||
|
|
@ -6948,7 +6948,7 @@ public sealed class GameWindow : IDisposable
|
||||||
rm.MaxSeqSpeedSinceLastUP = seqSpeedNow;
|
rm.MaxSeqSpeedSinceLastUP = seqSpeedNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
ae.Entity.Position = rm.Body.Position;
|
ae.Entity.SetPosition(rm.Body.Position); // A.5 T18: SetPosition propagates AabbDirty
|
||||||
ae.Entity.Rotation = rm.Body.Orientation;
|
ae.Entity.Rotation = rm.Body.Orientation;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -7276,7 +7276,7 @@ public sealed class GameWindow : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ae.Entity.Position = rm.Body.Position;
|
ae.Entity.SetPosition(rm.Body.Position); // A.5 T18: SetPosition propagates AabbDirty
|
||||||
ae.Entity.Rotation = rm.Body.Orientation;
|
ae.Entity.Rotation = rm.Body.Orientation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,12 @@ public sealed class EntitySpawnAdapter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A.5 T18: populate cached AABB so WalkEntities reads from the cache
|
||||||
|
// rather than recomputing Position±5 per frame. Called here because
|
||||||
|
// all entity-state initialization (position, rotation) is complete
|
||||||
|
// by this point via the WorldEntity passed in.
|
||||||
|
entity.RefreshAabb();
|
||||||
|
|
||||||
// Build the per-entity AnimatedEntityState. The sequencer factory
|
// Build the per-entity AnimatedEntityState. The sequencer factory
|
||||||
// may return a stub (in tests) or a fully-constructed sequencer from
|
// may return a stub (in tests) or a fully-constructed sequencer from
|
||||||
// the MotionTable (in production). Factory must not return null —
|
// the MotionTable (in production). Factory must not return null —
|
||||||
|
|
|
||||||
|
|
@ -42,28 +42,32 @@ public static class LandblockLoader
|
||||||
{
|
{
|
||||||
if (!IsSupported(stab.Id))
|
if (!IsSupported(stab.Id))
|
||||||
continue;
|
continue;
|
||||||
result.Add(new WorldEntity
|
var stabEntity = new WorldEntity
|
||||||
{
|
{
|
||||||
Id = nextId++,
|
Id = nextId++,
|
||||||
SourceGfxObjOrSetupId = stab.Id,
|
SourceGfxObjOrSetupId = stab.Id,
|
||||||
Position = stab.Frame.Origin,
|
Position = stab.Frame.Origin,
|
||||||
Rotation = stab.Frame.Orientation,
|
Rotation = stab.Frame.Orientation,
|
||||||
MeshRefs = Array.Empty<MeshRef>(),
|
MeshRefs = Array.Empty<MeshRef>(),
|
||||||
});
|
};
|
||||||
|
stabEntity.RefreshAabb(); // A.5 T18: populate cached AABB at construction
|
||||||
|
result.Add(stabEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var building in info.Buildings)
|
foreach (var building in info.Buildings)
|
||||||
{
|
{
|
||||||
if (!IsSupported(building.ModelId))
|
if (!IsSupported(building.ModelId))
|
||||||
continue;
|
continue;
|
||||||
result.Add(new WorldEntity
|
var buildingEntity = new WorldEntity
|
||||||
{
|
{
|
||||||
Id = nextId++,
|
Id = nextId++,
|
||||||
SourceGfxObjOrSetupId = building.ModelId,
|
SourceGfxObjOrSetupId = building.ModelId,
|
||||||
Position = building.Frame.Origin,
|
Position = building.Frame.Origin,
|
||||||
Rotation = building.Frame.Orientation,
|
Rotation = building.Frame.Orientation,
|
||||||
MeshRefs = Array.Empty<MeshRef>(),
|
MeshRefs = Array.Empty<MeshRef>(),
|
||||||
});
|
};
|
||||||
|
buildingEntity.RefreshAabb(); // A.5 T18: populate cached AABB at construction
|
||||||
|
result.Add(buildingEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue