Skip to content

refactor(textures): split upload throttle into processOne / processUntil#104

Merged
chiefcll merged 1 commit into
mainfrom
refactor/texture-upload-process-split
Jun 13, 2026
Merged

refactor(textures): split upload throttle into processOne / processUntil#104
chiefcll merged 1 commit into
mainfrom
refactor/texture-upload-process-split

Conversation

@chiefcll

Copy link
Copy Markdown
Contributor

What changed

Replaces the single CoreTextureManager.processSome(maxProcessingTime) — a sliding, numImageWorkers-sized prefetch window plus a re-queue-on-timeout pass — with two focused methods:

Method Used when Behavior
processOne() animating upload a single queued texture, then stop
processUntil(maxProcessingTime) idle upload serially until the per-frame time budget is exhausted

Shared per-texture work (dead-check → getTextureData guard → upload → try/catch) lives in a private uploadQueued. isTextureDead is a method (not an inline check) so TypeScript doesn't narrow state across the awaits.

Stage picks one based on animation state; the pre-init drain in CoreTextureManager now calls processUntil(Infinity).

Why

The prefetch window was effectively dead weight: loadTexture already awaits getTextureData before enqueuing image textures, so a queued texture is already decoded — the prefetch's getTextureData calls resolved immediately, and the time budget really only bounds GPU upload time. The re-queue-on-timeout pass existed only to recover decodes cut off mid-flight by that budget, which no longer happens. Removing both leaves a straightforward serial drain that's far easier to reason about.

Behavior change

  • While animating: exactly one texture is uploaded per frame (previously: as many as fit in half the time budget). This keeps upload cost off the animation frame more predictably.
  • Idle: unchanged in spirit — time-budgeted serial uploads, governed by the existing textureProcessingTimeLimit setting (default 10, untouched here).
  • Dead (failed/freed) textures that ended up in the queue are skipped for free. Note removeTextureFromQueue still has no callers, so a texture freed after enqueue stays in the queue until drained — uploadQueued discards it.

Testing

  • tsc --build clean; 243/243 unit tests pass.
  • Browser stress-images (1000 picsum images, idle path): full grid renders and the queue drains to a stable state; residual white squares are confirmed picsum-404 IDs, not stalled uploads. The animating processOne reuses the same verified uploadQueued.

🤖 Generated with Claude Code

Replace the single `processSome(maxProcessingTime)` — a sliding,
numImageWorkers-sized prefetch window plus a re-queue-on-timeout pass —
with two focused methods:

  - processOne()  — upload a single texture; used while animating so
                    uploads don't steal time from the animation
  - processUntil(maxProcessingTime) — upload serially until the per-frame
                    time budget is exhausted; used when idle

The per-texture work (dead-check, getTextureData guard, upload, try/catch)
is shared in a private `uploadQueued`; `isTextureDead` stays a method so TS
doesn't narrow `state` across the awaits.

Why the prefetch window was dead weight: `loadTexture` already awaits
`getTextureData` before enqueuing image textures, so queued textures are
already decoded — the prefetch's getTextureData calls resolved immediately
and the time budget really only bounds GPU upload time. The re-queue pass
only existed to recover decodes cut off mid-flight by that budget, which no
longer happens.

Behavior change: while animating, exactly one texture is uploaded per frame
(previously: as many as fit in half the time budget). The idle path keeps
the time-budgeted serial upload. Pre-init drain now uses
processUntil(Infinity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chiefcll chiefcll merged commit 78bd826 into main Jun 13, 2026
1 check passed
@chiefcll chiefcll deleted the refactor/texture-upload-process-split branch June 13, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant