fix(app): Phase A.1 — restore MaxCompletionsPerFrame=4 (uncap caused OOM)
The previous fix inf792931set MaxCompletionsPerFrame to int.MaxValue on the theory that synchronous loading made the cap pointless. That ignored the GPU upload cost: applying 25 landblocks in one Tick allocates ~25 terrain VBOs + hundreds of entity GfxObj sub-mesh VBOs + all unique texture uploads in a single frame, which observably crashes with OutOfMemoryException on the first frame after login. The pending-spawn list (also added inf792931) is what actually fixes the spawn-drop bug — it makes the cap safe by parking late-arriving spawns until their landblock loads. With both fixes: - Cap=4 spreads the 25-landblock first-frame load over ~7 frames (~116ms at 60fps, below human perception) - Spawns for the 21 not-yet-loaded landblocks land in pending and back-fill as each one arrives over the next 6 frames - No data lost, no OOM 219 tests still green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f792931d21
commit
133c22ed2f
1 changed files with 12 additions and 16 deletions
|
|
@ -26,26 +26,22 @@ public sealed class StreamingController
|
|||
public int Radius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cap on completions drained per <see cref="Tick"/> call. Defaults to
|
||||
/// effectively unlimited because the current <c>LandblockStreamer</c>
|
||||
/// is synchronous — every <c>EnqueueLoad</c> writes to the outbox on
|
||||
/// the same thread, so by the time we drain there's no backlog
|
||||
/// to spread, and the cap only serves to *delay* applying landblocks
|
||||
/// the user is already trying to look at.
|
||||
/// Cap on completions drained per <see cref="Tick"/> call. The cap is
|
||||
/// the GPU upload budget for one frame: terrain mesh + per-entity GfxObj
|
||||
/// sub-mesh uploads + texture uploads for one landblock take a few ms;
|
||||
/// applying 25 of them in a single frame produces a memory spike
|
||||
/// (observed: out-of-memory crash on the 5×5 first-frame load).
|
||||
///
|
||||
/// <para>
|
||||
/// The original async design used a small cap (4) to limit per-frame
|
||||
/// GPU upload spikes. That reasoning becomes relevant again if/when
|
||||
/// the streamer moves back to async loading; lower this knob then.
|
||||
/// Crucially, dropping completions to a lower frame is what was
|
||||
/// silently breaking live spawns: the post-login spawn flood would
|
||||
/// arrive on a frame where only 4 of the 25 visible-window landblocks
|
||||
/// had been applied, the spawns for the other 21 hit
|
||||
/// <c>AppendLiveEntity</c> with no matching loaded slot, and got
|
||||
/// dropped (now: parked in the pending bucket).
|
||||
/// 4 is the original async-streamer value; it spreads a 5×5 first-frame
|
||||
/// load over ~7 frames (~116ms at 60fps), which is below the human
|
||||
/// perception threshold. Spawn races that previously dropped entities
|
||||
/// while landblocks were in flight are now handled by
|
||||
/// <see cref="GpuWorldState"/>'s pending-spawn list, so spreading
|
||||
/// completions doesn't lose any data.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public int MaxCompletionsPerFrame { get; set; } = int.MaxValue;
|
||||
public int MaxCompletionsPerFrame { get; set; } = 4;
|
||||
|
||||
public StreamingController(
|
||||
Action<uint> enqueueLoad,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue