feat: ChunkStreamer tracks failed chunks to prevent retry storms
This commit is contained in:
@@ -317,4 +317,38 @@ public class ChunkStreamerTests
|
||||
// Only the chunk in the load box was created; no spurious remesh.
|
||||
Assert.Single(created);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tick_GeneratorThrows_LogsAndDoesNotRetry()
|
||||
{
|
||||
var cfg = new Configuration
|
||||
{
|
||||
RenderDistanceHorizontal = 0,
|
||||
RenderDistanceVertical = 0,
|
||||
MaxGenerateJobsInFlight = 100,
|
||||
MaxMeshUploadsPerFrame = 100,
|
||||
};
|
||||
var registry = new BlockRegistry();
|
||||
var store = new InMemoryChunkModificationStore();
|
||||
var world = new World.World(registry, store);
|
||||
var mesher = new FakeMesher();
|
||||
var executor = new SynchronousJobExecutor();
|
||||
int generateCalls = 0;
|
||||
Func<Vector3i, VoxelChunk> generate = _ =>
|
||||
{
|
||||
generateCalls++;
|
||||
throw new InvalidOperationException("boom");
|
||||
};
|
||||
Func<float[], IChunkRenderer> factory = m => new FakeRenderer(m);
|
||||
|
||||
var streamer = new ChunkStreamer(cfg, world, generate, mesher, registry, executor, factory);
|
||||
|
||||
streamer.Tick(new Vector3i(0, 0, 0));
|
||||
Assert.Equal(1, generateCalls);
|
||||
Assert.False(streamer.IsReady(new Vector3i(0, 0, 0)));
|
||||
|
||||
// Tick again — same chunk, same position. Should NOT call generate again.
|
||||
streamer.Tick(new Vector3i(0, 0, 0));
|
||||
Assert.Equal(1, generateCalls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public sealed class ChunkStreamer : IDisposable
|
||||
private readonly PriorityQueue<Vector3i, int> _pendingLoad = new();
|
||||
private readonly ConcurrentQueue<ChunkJobResult> _completed = new();
|
||||
private readonly Queue<Vector3i> _pendingUnload = new();
|
||||
private readonly HashSet<Vector3i> _failed = new();
|
||||
|
||||
private Vector3i _lastPlayerChunk = new(int.MinValue, int.MinValue, int.MinValue);
|
||||
private int _inFlight;
|
||||
@@ -118,6 +119,7 @@ public sealed class ChunkStreamer : IDisposable
|
||||
|
||||
foreach (var pos in target)
|
||||
{
|
||||
if (this._failed.Contains(pos)) continue;
|
||||
if (this._tracked.Add(pos))
|
||||
{
|
||||
int priority = (pos.X - p.X) * (pos.X - p.X)
|
||||
@@ -162,6 +164,7 @@ public sealed class ChunkStreamer : IDisposable
|
||||
}
|
||||
this._world.RemoveChunk(pos);
|
||||
this._ready.Remove(pos);
|
||||
this._failed.Remove(pos);
|
||||
Log.Debug("Unloaded {Pos}", pos);
|
||||
}
|
||||
}
|
||||
@@ -190,6 +193,7 @@ public sealed class ChunkStreamer : IDisposable
|
||||
{
|
||||
if (result.Error != null || result.Mesh == null)
|
||||
{
|
||||
this._failed.Add(result.Pos);
|
||||
Log.Error(result.Error, "Chunk {Pos} failed to load", result.Pos);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user