Unlimited creature model uploads during load screen, remove duplicate code

Loading screen now calls processCreatureSpawnQueue(unlimited=true) which
removes the 1-upload-per-frame cap and 2ms time budget, allowing all pending
creature models to upload to GPU in bulk. Also increases concurrent async
background loads from 4 to 16 during load screen. Replaces 40-line inline
duplicate of processAsyncCreatureResults with the shared function.
This commit is contained in:
Kelsi 2026-03-07 17:31:47 -08:00
parent 24f2ec75ec
commit 63efac9fa6
2 changed files with 14 additions and 58 deletions

View file

@ -215,7 +215,7 @@ private:
std::future<PreparedCreatureModel> future;
};
std::vector<AsyncCreatureLoad> asyncCreatureLoads_;
void processAsyncCreatureResults();
void processAsyncCreatureResults(bool unlimited = false);
static constexpr int MAX_ASYNC_CREATURE_LOADS = 4; // concurrent background loads
std::unordered_set<uint64_t> deadCreatureGuids_; // GUIDs that should spawn in corpse/death pose
std::unordered_map<uint32_t, uint32_t> displayIdModelCache_; // displayId → modelId (model caching)
@ -373,7 +373,7 @@ private:
std::unordered_set<uint64_t> pendingPlayerSpawnGuids_;
void processPlayerSpawnQueue();
std::unordered_set<uint64_t> creaturePermanentFailureGuids_;
void processCreatureSpawnQueue();
void processCreatureSpawnQueue(bool unlimited = false);
struct PendingGameObjectSpawn {
uint64_t guid;

View file

@ -4207,53 +4207,8 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
processPlayerSpawnQueue();
// During load screen warmup: lift per-frame budgets so GPU uploads
// happen in bulk while the loading screen is still visible.
// Process ALL async creature model uploads (no 3-per-frame cap).
{
for (auto it = asyncCreatureLoads_.begin(); it != asyncCreatureLoads_.end(); ) {
if (!it->future.valid() ||
it->future.wait_for(std::chrono::milliseconds(0)) != std::future_status::ready) {
++it;
continue;
}
auto result = it->future.get();
it = asyncCreatureLoads_.erase(it);
if (result.permanent_failure) {
nonRenderableCreatureDisplayIds_.insert(result.displayId);
creaturePermanentFailureGuids_.insert(result.guid);
pendingCreatureSpawnGuids_.erase(result.guid);
creatureSpawnRetryCounts_.erase(result.guid);
continue;
}
if (!result.valid || !result.model) {
pendingCreatureSpawnGuids_.erase(result.guid);
creatureSpawnRetryCounts_.erase(result.guid);
continue;
}
auto* charRenderer = renderer ? renderer->getCharacterRenderer() : nullptr;
if (!charRenderer) { pendingCreatureSpawnGuids_.erase(result.guid); continue; }
if (!charRenderer->loadModel(*result.model, result.modelId)) {
nonRenderableCreatureDisplayIds_.insert(result.displayId);
creaturePermanentFailureGuids_.insert(result.guid);
pendingCreatureSpawnGuids_.erase(result.guid);
creatureSpawnRetryCounts_.erase(result.guid);
continue;
}
displayIdModelCache_[result.displayId] = result.modelId;
pendingCreatureSpawnGuids_.erase(result.guid);
creatureSpawnRetryCounts_.erase(result.guid);
if (!creatureInstances_.count(result.guid) &&
!creaturePermanentFailureGuids_.count(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;
pendingCreatureSpawns_.push_back(s);
pendingCreatureSpawnGuids_.insert(result.guid);
}
}
}
processCreatureSpawnQueue();
// and spawns happen in bulk while the loading screen is still visible.
processCreatureSpawnQueue(true); // unlimited: no model upload cap, no time budget
processAsyncNpcCompositeResults();
processDeferredEquipmentQueue();
if (auto* cr = renderer ? renderer->getCharacterRenderer() : nullptr) {
@ -6804,9 +6759,10 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
" displayId=", displayId, " at (", x, ", ", y, ", ", z, ")");
}
void Application::processAsyncCreatureResults() {
void Application::processAsyncCreatureResults(bool unlimited) {
// Check completed async model loads and finalize on main thread (GPU upload + instance creation).
// Limit GPU model uploads per frame to avoid spikes, but always drain cheap bookkeeping.
// In unlimited mode (load screen), process all pending uploads without cap.
static constexpr int kMaxModelUploadsPerFrame = 1;
int modelUploads = 0;
@ -6819,9 +6775,7 @@ void Application::processAsyncCreatureResults() {
// 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 >= kMaxModelUploadsPerFrame) {
// Check if this displayId already has a cached model (cheap spawn, no GPU upload).
// We can't peek the displayId without getting the future, so just break.
if (!unlimited && modelUploads >= kMaxModelUploadsPerFrame) {
break;
}
@ -6967,13 +6921,14 @@ void Application::processAsyncNpcCompositeResults() {
}
}
void Application::processCreatureSpawnQueue() {
void Application::processCreatureSpawnQueue(bool unlimited) {
auto startTime = std::chrono::steady_clock::now();
// Budget: max 2ms per frame for creature spawning to prevent stutter.
// In unlimited mode (load screen), process everything without budget cap.
static constexpr float kSpawnBudgetMs = 2.0f;
// First, finalize any async model loads that completed on background threads.
processAsyncCreatureResults();
processAsyncCreatureResults(unlimited);
{
auto now = std::chrono::steady_clock::now();
float asyncMs = std::chrono::duration<float, std::milli>(now - startTime).count();
@ -6992,11 +6947,11 @@ void Application::processCreatureSpawnQueue() {
int asyncLaunched = 0;
size_t rotationsLeft = pendingCreatureSpawns_.size();
while (!pendingCreatureSpawns_.empty() &&
processed < MAX_SPAWNS_PER_FRAME &&
(unlimited || processed < MAX_SPAWNS_PER_FRAME) &&
rotationsLeft > 0) {
// Check time budget every iteration (including first — async results may
// have already consumed the budget via GPU model uploads).
{
if (!unlimited) {
auto now = std::chrono::steady_clock::now();
float elapsedMs = std::chrono::duration<float, std::milli>(now - startTime).count();
if (elapsedMs >= kSpawnBudgetMs) break;
@ -7017,7 +6972,8 @@ void Application::processCreatureSpawnQueue() {
// For new models: launch async load on background thread instead of blocking.
if (needsNewModel) {
if (static_cast<int>(asyncCreatureLoads_.size()) + asyncLaunched >= MAX_ASYNC_CREATURE_LOADS) {
const int maxAsync = unlimited ? (MAX_ASYNC_CREATURE_LOADS * 4) : MAX_ASYNC_CREATURE_LOADS;
if (static_cast<int>(asyncCreatureLoads_.size()) + asyncLaunched >= maxAsync) {
// Too many in-flight — defer to next frame
pendingCreatureSpawns_.push_back(s);
rotationsLeft--;