From e72cb4d380b9d1408c717686e84caffb95c8a6f8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 29 Mar 2026 19:16:27 -0700 Subject: [PATCH] fix: async creature upload budget blocked cache hits and failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-tick GPU upload budget check ran before consuming async futures, so after 1 upload ALL remaining ready results were deferred — including permanent failures and cache hits that need zero GPU work. Moved the budget gate after failure/cache-hit processing so only actual uploads count. Re-queues over-budget results as pending spawns for next frame. --- src/core/application.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/core/application.cpp b/src/core/application.cpp index 0ef4d778..579aa38d 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -8472,16 +8472,14 @@ void Application::processAsyncCreatureResults(bool unlimited) { continue; } - // Peek: if this result needs a NEW model upload (not cached) and we've hit - // the upload budget, defer to next frame without consuming the future. - if (modelUploads >= maxUploadsThisTick) { - break; - } - auto result = it->future.get(); it = asyncCreatureLoads_.erase(it); asyncCreatureDisplayLoads_.erase(result.displayId); + // Failures and cache hits need no GPU work — process them even when the + // upload budget is exhausted. Previously the budget check was above this + // point, blocking ALL ready futures (including zero-cost ones) after a + // single upload, which throttled creature spawn throughput during world load. if (result.permanent_failure) { nonRenderableCreatureDisplayIds_.insert(result.displayId); creaturePermanentFailureGuids_.insert(result.guid); @@ -8516,6 +8514,23 @@ void Application::processAsyncCreatureResults(bool unlimited) { continue; } + // Only actual GPU uploads count toward the per-tick budget. + if (modelUploads >= maxUploadsThisTick) { + // Re-queue this result — it needs a GPU upload but we're at budget. + // Push a new pending spawn so it's retried next frame. + pendingCreatureSpawnGuids_.erase(result.guid); + creatureSpawnRetryCounts_.erase(result.guid); + PendingCreatureSpawn s{}; + s.guid = result.guid; + s.displayId = result.displayId; + s.x = result.x; s.y = result.y; s.z = result.z; + s.orientation = result.orientation; + s.scale = result.scale; + pendingCreatureSpawns_.push_back(s); + pendingCreatureSpawnGuids_.insert(result.guid); + continue; + } + // Model parsed on background thread — upload to GPU on main thread. auto* charRenderer = renderer ? renderer->getCharacterRenderer() : nullptr; if (!charRenderer) {