refactor(streaming): capture landblock build origin
This commit is contained in:
parent
2216a02de5
commit
090b0354ff
8 changed files with 522 additions and 54 deletions
|
|
@ -2491,8 +2491,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
// buildMeshOrNull (T12) receives the already-loaded LoadedLandblock so
|
||||
// it can call LandblockMesh.Build without a dat read — _heightTable and
|
||||
// _blendCtx are read-only after init, _surfaceCache is ConcurrentDictionary (T9).
|
||||
_streamer = new AcDream.App.Streaming.LandblockStreamer(
|
||||
loadLandblock: (id, kind) => BuildLandblockForStreaming(id, kind),
|
||||
_streamer = AcDream.App.Streaming.LandblockStreamer.CreateForRequests(
|
||||
loadLandblock: BuildLandblockForStreaming,
|
||||
buildMeshOrNull: (id, lb) =>
|
||||
{
|
||||
if (lb is null || _heightTable is null || _blendCtx is null || _surfaceCache is null)
|
||||
|
|
@ -2508,7 +2508,14 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
_streamer.Start();
|
||||
|
||||
_streamingController = new AcDream.App.Streaming.StreamingController(
|
||||
enqueueLoad: (id, kind, generation) => _streamer.EnqueueLoad(id, kind, generation),
|
||||
enqueueLoad: (id, kind, generation) => _streamer.EnqueueLoad(
|
||||
new AcDream.App.Streaming.LandblockBuildRequest(
|
||||
id,
|
||||
kind,
|
||||
generation,
|
||||
new AcDream.App.Streaming.LandblockBuildOrigin(
|
||||
_liveCenterX,
|
||||
_liveCenterY))),
|
||||
enqueueUnload: (id, generation) => _streamer.EnqueueUnload(id, generation),
|
||||
drainCompletions: _streamer.DrainCompletions,
|
||||
applyTerrain: ApplyLoadedTerrain,
|
||||
|
|
@ -3792,8 +3799,7 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
/// early-out at the source.
|
||||
/// </summary>
|
||||
private AcDream.App.Streaming.LandblockBuild? BuildLandblockForStreaming(
|
||||
uint landblockId,
|
||||
AcDream.App.Streaming.LandblockStreamJobKind kind)
|
||||
AcDream.App.Streaming.LandblockBuildRequest request)
|
||||
{
|
||||
if (_dats is null) return null;
|
||||
|
||||
|
|
@ -3816,31 +3822,31 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
{
|
||||
long waitedMs = sw.ElapsedMilliseconds;
|
||||
sw.Restart();
|
||||
var built = BuildLandblockForStreamingLocked(landblockId, kind);
|
||||
var built = BuildLandblockForStreamingLocked(request);
|
||||
AcDream.Core.Physics.PhysicsDiagnostics.LogTeleport(
|
||||
"BUILD", landblockId,
|
||||
$"waited={waitedMs}ms held={sw.ElapsedMilliseconds}ms kind={kind}");
|
||||
"BUILD", request.LandblockId,
|
||||
$"waited={waitedMs}ms held={sw.ElapsedMilliseconds}ms kind={request.Kind}");
|
||||
return built;
|
||||
}
|
||||
}
|
||||
lock (_datLock)
|
||||
{
|
||||
return BuildLandblockForStreamingLocked(landblockId, kind);
|
||||
return BuildLandblockForStreamingLocked(request);
|
||||
}
|
||||
}
|
||||
|
||||
private AcDream.App.Streaming.LandblockBuild? BuildLandblockForStreamingLocked(
|
||||
uint landblockId,
|
||||
AcDream.App.Streaming.LandblockStreamJobKind kind)
|
||||
AcDream.App.Streaming.LandblockBuildRequest request)
|
||||
{
|
||||
if (_dats is null) return null;
|
||||
uint landblockId = request.LandblockId;
|
||||
|
||||
// ISSUE #54: far-tier early-out — heightmap only, empty entities.
|
||||
// Skips the LandBlockInfo dat read AND all entity hydration (stabs
|
||||
// + buildings) AND the SceneryGenerator AND interior cells. Cuts
|
||||
// worker-thread cost per far-tier LB from ~tens of ms to a single
|
||||
// dat read.
|
||||
if (kind == AcDream.App.Streaming.LandblockStreamJobKind.LoadFar)
|
||||
if (request.Kind == AcDream.App.Streaming.LandblockStreamJobKind.LoadFar)
|
||||
{
|
||||
var heightmapOnly = _dats.Get<DatReaderWriter.DBObjs.LandBlock>(landblockId);
|
||||
if (heightmapOnly is null) return null;
|
||||
|
|
@ -3849,7 +3855,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
landblockId,
|
||||
heightmapOnly,
|
||||
System.Array.Empty<AcDream.Core.World.WorldEntity>(),
|
||||
AcDream.Core.World.PhysicsDatBundle.Empty)); // far tier: no cells/buildings/entities
|
||||
AcDream.Core.World.PhysicsDatBundle.Empty),
|
||||
Origin: request.Origin); // far tier: no cells/buildings/entities
|
||||
}
|
||||
|
||||
var baseLoaded = AcDream.Core.World.LandblockLoader.Load(_dats, landblockId);
|
||||
|
|
@ -3858,8 +3865,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
int lbX = (int)((landblockId >> 24) & 0xFFu);
|
||||
int lbY = (int)((landblockId >> 16) & 0xFFu);
|
||||
var worldOffset = new System.Numerics.Vector3(
|
||||
(lbX - _liveCenterX) * 192f,
|
||||
(lbY - _liveCenterY) * 192f,
|
||||
(lbX - request.Origin.CenterX) * 192f,
|
||||
(lbY - request.Origin.CenterY) * 192f,
|
||||
0f);
|
||||
|
||||
// Hydrate the stabs: same logic as the old OnLoad preload. Each stab
|
||||
|
|
@ -3926,9 +3933,18 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
|
||||
// Task 8: merge stabs + scenery + interior into one entity list.
|
||||
var merged = new List<AcDream.Core.World.WorldEntity>(hydrated);
|
||||
merged.AddRange(BuildSceneryEntitiesForStreaming(baseLoaded, lbX, lbY));
|
||||
merged.AddRange(BuildSceneryEntitiesForStreaming(
|
||||
baseLoaded,
|
||||
lbX,
|
||||
lbY,
|
||||
request.Origin));
|
||||
var envCellBuild = new AcDream.App.Rendering.Wb.EnvCellLandblockBuildBuilder(landblockId);
|
||||
merged.AddRange(BuildInteriorEntitiesForStreaming(landblockId, lbX, lbY, envCellBuild));
|
||||
merged.AddRange(BuildInteriorEntitiesForStreaming(
|
||||
landblockId,
|
||||
lbX,
|
||||
lbY,
|
||||
request.Origin,
|
||||
envCellBuild));
|
||||
|
||||
// datLock fix: pre-read (under the worker's _datLock) every dat the apply
|
||||
// consumes, so ApplyLoadedTerrainLocked can run lock-free. Built AFTER
|
||||
|
|
@ -3941,7 +3957,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
baseLoaded.Heightmap,
|
||||
merged,
|
||||
physicsDats),
|
||||
completedEnvCells);
|
||||
completedEnvCells,
|
||||
request.Origin);
|
||||
}
|
||||
|
||||
private void EnsureEnvCellMeshesAfterPin(
|
||||
|
|
@ -4036,7 +4053,10 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
/// LoadedLandblock instead of iterating worldView.Landblocks.
|
||||
/// </summary>
|
||||
private List<AcDream.Core.World.WorldEntity> BuildSceneryEntitiesForStreaming(
|
||||
AcDream.Core.World.LoadedLandblock lb, int lbX, int lbY)
|
||||
AcDream.Core.World.LoadedLandblock lb,
|
||||
int lbX,
|
||||
int lbY,
|
||||
AcDream.App.Streaming.LandblockBuildOrigin origin)
|
||||
{
|
||||
var result = new List<AcDream.Core.World.WorldEntity>();
|
||||
if (_dats is null || _heightTable is null) return result;
|
||||
|
|
@ -4069,8 +4089,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
if (spawns.Count == 0) return result;
|
||||
|
||||
var lbOffset = new System.Numerics.Vector3(
|
||||
(lbX - _liveCenterX) * 192f,
|
||||
(lbY - _liveCenterY) * 192f,
|
||||
(lbX - origin.CenterX) * 192f,
|
||||
(lbY - origin.CenterY) * 192f,
|
||||
0f);
|
||||
|
||||
// Per-landblock id namespace. The top nibble identifies procedural
|
||||
|
|
@ -4265,6 +4285,7 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
uint landblockId,
|
||||
int lbX,
|
||||
int lbY,
|
||||
AcDream.App.Streaming.LandblockBuildOrigin origin,
|
||||
AcDream.App.Rendering.Wb.EnvCellLandblockBuildBuilder envCellBuild)
|
||||
{
|
||||
var result = new List<AcDream.Core.World.WorldEntity>();
|
||||
|
|
@ -4274,8 +4295,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
if (lbInfo is null || lbInfo.NumCells == 0) return result;
|
||||
|
||||
var lbOffset = new System.Numerics.Vector3(
|
||||
(lbX - _liveCenterX) * 192f,
|
||||
(lbY - _liveCenterY) * 192f,
|
||||
(lbX - origin.CenterX) * 192f,
|
||||
(lbY - origin.CenterY) * 192f,
|
||||
0f);
|
||||
|
||||
// Per-landblock id namespace — see AcDream.Core.World.InteriorEntityIdAllocator
|
||||
|
|
@ -4654,8 +4675,8 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
int lbX = (int)lbXu;
|
||||
int lbY = (int)lbYu;
|
||||
var origin = new System.Numerics.Vector3(
|
||||
(lbX - _liveCenterX) * 192f,
|
||||
(lbY - _liveCenterY) * 192f,
|
||||
(lbX - build.Origin.CenterX) * 192f,
|
||||
(lbY - build.Origin.CenterY) * 192f,
|
||||
0f);
|
||||
|
||||
// Phase A.5 T15/T16: route through AddLandblockWithMesh — the named
|
||||
|
|
@ -4820,7 +4841,7 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
|
|||
{
|
||||
// #146 (2026-06-24): clear this landblock's prior building cache so
|
||||
// the loop re-bases each building's STREAMING-RELATIVE WorldTransform
|
||||
// with the CURRENT _liveCenter (origin, recomputed above per apply).
|
||||
// with this completion's captured origin (recomputed above per apply).
|
||||
// CacheBuilding's per-cell first-wins then applies fresh within this
|
||||
// pass — mirrors terrain's per-apply WorldOffset re-base (AddLandblock).
|
||||
// Without it, CacheBuilding's idempotent guard locks the transform at
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue