perf(streaming): scope reveal warmup spatially
Source composite warmup from the canonical published destination neighborhood, including quiesced static and live projections, instead of rescanning the retained Far-tier world after every membership edge.
This lifecycle correction applies to both draw paths, preserving ef1d263337 as the sole G4 visual rollback.
Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
129dd77ddd
commit
03b10183e9
5 changed files with 212 additions and 6 deletions
|
|
@ -327,17 +327,27 @@ internal sealed class SessionPlayerCompositionPhase
|
||||||
d.Selection,
|
d.Selection,
|
||||||
live.WorldState,
|
live.WorldState,
|
||||||
content.Audio?.Engine);
|
content.Audio?.Engine);
|
||||||
|
var compositeWarmupSource =
|
||||||
|
new CompositeWarmupEntitySource(live.WorldState);
|
||||||
var worldReveal = new WorldRevealCoordinator(
|
var worldReveal = new WorldRevealCoordinator(
|
||||||
streaming.IsRenderNeighborhoodResident,
|
streaming.IsRenderNeighborhoodResident,
|
||||||
d.PhysicsEngine.IsSpawnCellReady,
|
d.PhysicsEngine.IsSpawnCellReady,
|
||||||
d.PhysicsEngine.IsNeighborhoodTerrainResident,
|
d.PhysicsEngine.IsNeighborhoodTerrainResident,
|
||||||
() => live.DrawDispatcher.CompositeTexturesReady,
|
() => live.DrawDispatcher.CompositeTexturesReady,
|
||||||
(destinationCell, radius) => live.DrawDispatcher.PrepareCompositeTextures(
|
(destinationCell, radius) =>
|
||||||
live.WorldState.Entities,
|
{
|
||||||
live.WorldState.FlatViewGeneration,
|
compositeWarmupSource.Refresh(destinationCell, radius);
|
||||||
destinationCell,
|
live.DrawDispatcher.PrepareCompositeTextures(
|
||||||
radius),
|
compositeWarmupSource.Entities,
|
||||||
live.DrawDispatcher.InvalidateCompositeWarmupReadiness,
|
compositeWarmupSource.Generation,
|
||||||
|
destinationCell,
|
||||||
|
radius);
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
compositeWarmupSource.Reset();
|
||||||
|
live.DrawDispatcher.InvalidateCompositeWarmupReadiness();
|
||||||
|
},
|
||||||
spawnClaimClassifier.IsUnhydratable,
|
spawnClaimClassifier.IsUnhydratable,
|
||||||
d.Log,
|
d.Log,
|
||||||
worldQuiescence,
|
worldQuiescence,
|
||||||
|
|
|
||||||
59
src/AcDream.App/Rendering/Wb/CompositeWarmupEntitySource.cs
Normal file
59
src/AcDream.App/Rendering/Wb/CompositeWarmupEntitySource.cs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
using AcDream.App.Streaming;
|
||||||
|
using AcDream.Core.World;
|
||||||
|
|
||||||
|
namespace AcDream.App.Rendering.Wb;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retains the exact published destination-neighborhood snapshot consumed by
|
||||||
|
/// composite-texture reveal warmup. The source refreshes only when canonical
|
||||||
|
/// spatial membership or its destination scope changes, avoiding a scan of
|
||||||
|
/// the retained Far-tier world on every reveal tick.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class CompositeWarmupEntitySource
|
||||||
|
{
|
||||||
|
private readonly GpuWorldState _world;
|
||||||
|
private readonly List<WorldEntity> _entities = [];
|
||||||
|
private bool _initialized;
|
||||||
|
private ulong _worldGeneration;
|
||||||
|
private uint _destinationCell;
|
||||||
|
private int _radius;
|
||||||
|
|
||||||
|
public CompositeWarmupEntitySource(GpuWorldState world)
|
||||||
|
{
|
||||||
|
_world = world ?? throw new ArgumentNullException(nameof(world));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<WorldEntity> Entities => _entities;
|
||||||
|
public ulong Generation => _worldGeneration;
|
||||||
|
|
||||||
|
public void Refresh(uint destinationCell, int radius)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(radius);
|
||||||
|
ulong worldGeneration = _world.FlatViewGeneration;
|
||||||
|
if (_initialized
|
||||||
|
&& _worldGeneration == worldGeneration
|
||||||
|
&& _destinationCell == destinationCell
|
||||||
|
&& _radius == radius)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_world.CopyPublishedEntitiesNearLandblockForReadiness(
|
||||||
|
destinationCell,
|
||||||
|
radius,
|
||||||
|
_entities);
|
||||||
|
_worldGeneration = worldGeneration;
|
||||||
|
_destinationCell = destinationCell;
|
||||||
|
_radius = radius;
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_entities.Clear();
|
||||||
|
_initialized = false;
|
||||||
|
_worldGeneration = 0;
|
||||||
|
_destinationCell = 0;
|
||||||
|
_radius = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -449,6 +449,46 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies every published projection from a bounded destination
|
||||||
|
/// neighborhood for presentation-readiness work. Unlike gameplay-facing
|
||||||
|
/// spatial queries, this remains available while the world generation is
|
||||||
|
/// quiesced: the reveal barrier must prepare destination resources before
|
||||||
|
/// it makes that generation observable.
|
||||||
|
/// </summary>
|
||||||
|
internal void CopyPublishedEntitiesNearLandblockForReadiness(
|
||||||
|
uint centerCellOrLandblockId,
|
||||||
|
int landblockRadius,
|
||||||
|
List<WorldEntity> destination)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(destination);
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegative(landblockRadius);
|
||||||
|
destination.Clear();
|
||||||
|
|
||||||
|
int centerX = (int)((centerCellOrLandblockId >> 24) & 0xFFu);
|
||||||
|
int centerY = (int)((centerCellOrLandblockId >> 16) & 0xFFu);
|
||||||
|
for (int dx = -landblockRadius; dx <= landblockRadius; dx++)
|
||||||
|
for (int dy = -landblockRadius; dy <= landblockRadius; dy++)
|
||||||
|
{
|
||||||
|
int x = centerX + dx;
|
||||||
|
int y = centerY + dy;
|
||||||
|
if ((uint)x > 0xFFu || (uint)y > 0xFFu)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint landblockId =
|
||||||
|
((uint)x << 24) | ((uint)y << 16) | 0xFFFFu;
|
||||||
|
if (!_loaded.TryGetValue(
|
||||||
|
landblockId,
|
||||||
|
out LoadedLandblock? landblock))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < landblock.Entities.Count; i++)
|
||||||
|
destination.Add(landblock.Entities[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public readonly ref struct MutationBatch
|
public readonly ref struct MutationBatch
|
||||||
{
|
{
|
||||||
private readonly GpuWorldState _owner;
|
private readonly GpuWorldState _owner;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
using AcDream.App.Rendering.Wb;
|
||||||
|
using AcDream.App.Streaming;
|
||||||
|
using AcDream.Core.World;
|
||||||
|
using DatReaderWriter.DBObjs;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace AcDream.App.Tests.Rendering.Wb;
|
||||||
|
|
||||||
|
public sealed class CompositeWarmupEntitySourceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void RefreshTracksOnlyDestinationNeighborhoodMembership()
|
||||||
|
{
|
||||||
|
var center = Entity(1);
|
||||||
|
var neighbor = Entity(2);
|
||||||
|
var far = Entity(3);
|
||||||
|
var world = new GpuWorldState();
|
||||||
|
world.AddLandblock(Landblock(0x1010FFFFu, center));
|
||||||
|
world.AddLandblock(Landblock(0x1110FFFFu, neighbor));
|
||||||
|
world.AddLandblock(Landblock(0x1310FFFFu, far));
|
||||||
|
var source = new CompositeWarmupEntitySource(world);
|
||||||
|
|
||||||
|
source.Refresh(0x10100001u, radius: 1);
|
||||||
|
|
||||||
|
Assert.Equal(world.FlatViewGeneration, source.Generation);
|
||||||
|
Assert.Equal([center, neighbor], source.Entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RefreshObservesLaterMembershipWithoutReplacingItsStableView()
|
||||||
|
{
|
||||||
|
var center = Entity(1);
|
||||||
|
var later = Entity(2);
|
||||||
|
var world = new GpuWorldState();
|
||||||
|
world.AddLandblock(Landblock(0x1010FFFFu, center));
|
||||||
|
var source = new CompositeWarmupEntitySource(world);
|
||||||
|
source.Refresh(0x10100001u, radius: 0);
|
||||||
|
IReadOnlyList<WorldEntity> stableView = source.Entities;
|
||||||
|
|
||||||
|
world.AddLandblock(Landblock(0x1010FFFFu, center, later));
|
||||||
|
source.Refresh(0x10100001u, radius: 0);
|
||||||
|
|
||||||
|
Assert.Same(stableView, source.Entities);
|
||||||
|
Assert.Equal([center, later], source.Entities);
|
||||||
|
Assert.Equal(world.FlatViewGeneration, source.Generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LoadedLandblock Landblock(
|
||||||
|
uint id,
|
||||||
|
params WorldEntity[] entities) =>
|
||||||
|
new(id, new LandBlock(), entities);
|
||||||
|
|
||||||
|
private static WorldEntity Entity(uint id) => new()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
SourceGfxObjOrSetupId = 0x02000001u,
|
||||||
|
Position = Vector3.Zero,
|
||||||
|
Rotation = Quaternion.Identity,
|
||||||
|
MeshRefs = Array.Empty<MeshRef>(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -508,6 +508,42 @@ public sealed class GpuWorldStateVisibilityTests
|
||||||
Assert.DoesNotContain(destination, pair => pair.Key == 0u);
|
Assert.DoesNotContain(destination, pair => pair.Key == 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReadinessNeighborhood_CopiesStaticAndLiveWhileWorldIsQuiesced()
|
||||||
|
{
|
||||||
|
var availability = new WorldGenerationAvailabilityState();
|
||||||
|
availability.Begin(7);
|
||||||
|
var centerStatic = Entity(1, 0u);
|
||||||
|
var centerLive = Entity(2, 0x70000001u);
|
||||||
|
var neighborStatic = Entity(3, 0u);
|
||||||
|
var farStatic = Entity(4, 0u);
|
||||||
|
var state = new GpuWorldState(availability: availability);
|
||||||
|
state.AddLandblock(new LoadedLandblock(
|
||||||
|
0x1010FFFFu,
|
||||||
|
new LandBlock(),
|
||||||
|
new[] { centerStatic, centerLive }));
|
||||||
|
state.AddLandblock(new LoadedLandblock(
|
||||||
|
0x1110FFFFu,
|
||||||
|
new LandBlock(),
|
||||||
|
new[] { neighborStatic }));
|
||||||
|
state.AddLandblock(new LoadedLandblock(
|
||||||
|
0x1310FFFFu,
|
||||||
|
new LandBlock(),
|
||||||
|
new[] { farStatic }));
|
||||||
|
var destination = new List<WorldEntity> { farStatic };
|
||||||
|
|
||||||
|
state.CopyPublishedEntitiesNearLandblockForReadiness(
|
||||||
|
0x10100001u,
|
||||||
|
1,
|
||||||
|
destination);
|
||||||
|
|
||||||
|
Assert.Equal(3, destination.Count);
|
||||||
|
Assert.Contains(centerStatic, destination);
|
||||||
|
Assert.Contains(centerLive, destination);
|
||||||
|
Assert.Contains(neighborStatic, destination);
|
||||||
|
Assert.DoesNotContain(farStatic, destination);
|
||||||
|
}
|
||||||
|
|
||||||
private static WorldEntity Entity(uint id, uint guid) => new()
|
private static WorldEntity Entity(uint id, uint guid) => new()
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue