feat: ChunkStreamer dirty set with cardinal-neighbor remesh on load

This commit is contained in:
2026-06-01 11:40:55 +02:00
parent 7d06706400
commit da051ed413
2 changed files with 171 additions and 9 deletions

View File

@@ -82,7 +82,7 @@ public class ChunkStreamerTests
}
[Fact]
public void Tick_PlayerMoves_LoadsNewChunksOnly()
public void Tick_PlayerMoves_LoadsNewChunks()
{
var cfg = new Configuration
{
@@ -94,10 +94,24 @@ public class ChunkStreamerTests
var (streamer, world, created) = CreateStreamer(cfg);
streamer.Tick(new Vector3i(0, 0, 0)); // loads 9
int firstWave = created.Count;
streamer.Tick(new Vector3i(1, 0, 0)); // loads only the 3 new column to +x
Assert.Equal(firstWave + 3, created.Count);
// After the second tick the 3 new x=2 chunks must be ready.
// Cardinal-neighbor dirty-marking may also produce remesh renderers, so
// we assert on world state rather than an exact renderer count.
streamer.Tick(new Vector3i(1, 0, 0));
for (int z = -1; z <= 1; z++)
{
var newPos = new Vector3i(2, 0, z);
Assert.NotNull(world.GetChunk(newPos));
Assert.True(streamer.IsReady(newPos));
}
// x=-1 column must be unloaded
for (int z = -1; z <= 1; z++)
{
Assert.Null(world.GetChunk(new Vector3i(-1, 0, z)));
Assert.False(streamer.IsReady(new Vector3i(-1, 0, z)));
}
}
[Fact]
@@ -240,4 +254,67 @@ public class ChunkStreamerTests
for (int i = 0; i < 9; i++) streamer.Tick(new Vector3i(0, 0, 0));
Assert.True(streamer.SpawnReady);
}
[Fact]
public void MarkDirty_RemeshesChunkOnNextTick()
{
var cfg = new Configuration
{
RenderDistanceHorizontal = 0,
RenderDistanceVertical = 0,
MaxGenerateJobsInFlight = 100,
MaxMeshUploadsPerFrame = 100,
};
var (streamer, _, created) = CreateStreamer(cfg);
streamer.Tick(new Vector3i(0, 0, 0));
Assert.Single(created);
var initialRenderer = created[0];
streamer.MarkDirty(new Vector3i(0, 0, 0));
streamer.Tick(new Vector3i(0, 0, 0)); // same chunk, but a remesh is queued
// Two renderers exist over the lifetime — old disposed, new live.
Assert.Equal(2, created.Count);
Assert.True(initialRenderer.Disposed);
Assert.False(created[1].Disposed);
}
[Fact]
public void Tick_LoadingChunk_MarksReadyCardinalNeighborsDirty()
{
var cfg = new Configuration
{
RenderDistanceHorizontal = 1,
RenderDistanceVertical = 0,
MaxGenerateJobsInFlight = 100,
MaxMeshUploadsPerFrame = 100,
};
var (streamer, _, created) = CreateStreamer(cfg);
streamer.Tick(new Vector3i(0, 0, 0)); // load initial 3×3
int firstWave = created.Count; // 9 chunks
// Move so we load a new x=2 column. The new chunks become ready and
// mark their cardinal neighbors (x=1 column) dirty during Drain.
streamer.Tick(new Vector3i(1, 0, 0)); // dispatch + drain new loads → dirty marks set
streamer.Tick(new Vector3i(1, 0, 0)); // dispatch the queued remeshes → drain them
// Should have: 3 new loads + at least 3 remeshes of the x=1 column.
Assert.True(created.Count > firstWave + 3,
$"Expected remeshes beyond the 3 new loads; got {created.Count - firstWave}");
}
[Fact]
public void MarkDirty_OnUntrackedChunk_IsNoOp()
{
var cfg = new Configuration { RenderDistanceHorizontal = 0, RenderDistanceVertical = 0 };
var (streamer, _, created) = CreateStreamer(cfg);
streamer.MarkDirty(new Vector3i(99, 0, 99));
streamer.Tick(new Vector3i(0, 0, 0));
// Only the chunk in the load box was created; no spurious remesh.
Assert.Single(created);
}
}