From d40cfcad9090c46f98544cfd52c1cc916d48a965 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 21 Feb 2026 03:29:13 -0800 Subject: [PATCH] Reduce Stormwind stutter from spawn retry churn and log I/O - Cache non-renderable creature display IDs and fail-fast future spawn attempts - Mark GUIDs tied to non-renderable displays as permanent failures to avoid long retry loops - Skip queued spawn retry work immediately for known non-renderable display IDs - Clear non-renderable display cache on expansion reload/logout - Downgrade high-volume UNIT spawn logs to debug and fix mislabeled time-sync log --- src/core/application.cpp | 23 +++++++++++++++++++++++ src/game/game_handler.cpp | 12 ++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/core/application.cpp b/src/core/application.cpp index 9306bfca..ec1e39fa 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -495,6 +495,7 @@ void Application::reloadExpansionData() { humanoidExtraMap_.clear(); creatureModelIds_.clear(); creatureRenderPosCache_.clear(); + nonRenderableCreatureDisplayIds_.clear(); buildCreatureDisplayLookups(); } @@ -508,6 +509,7 @@ void Application::logoutToLogin() { weaponsSheathed_ = false; wasAutoAttacking_ = false; loadedMapId_ = 0xFFFFFFFF; + nonRenderableCreatureDisplayIds_.clear(); world.reset(); if (renderer) { // Remove old player model so it doesn't persist into next session @@ -3629,10 +3631,16 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x // Skip if already spawned if (creatureInstances_.count(guid)) return; + if (nonRenderableCreatureDisplayIds_.count(displayId)) { + creaturePermanentFailureGuids_.insert(guid); + return; + } // Get model path from displayId std::string m2Path = getModelPathForDisplayId(displayId); if (m2Path.empty()) { + nonRenderableCreatureDisplayIds_.insert(displayId); + creaturePermanentFailureGuids_.insert(guid); return; } { @@ -3642,6 +3650,7 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x [](unsigned char c) { return static_cast(std::tolower(c)); }); if (lowerPath.find("invisiblestalker") != std::string::npos || lowerPath.find("invisible_stalker") != std::string::npos) { + nonRenderableCreatureDisplayIds_.insert(displayId); creaturePermanentFailureGuids_.insert(guid); return; } @@ -3663,12 +3672,16 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x auto m2Data = assetManager->readFile(m2Path); if (m2Data.empty()) { LOG_WARNING("Failed to read creature M2: ", m2Path); + nonRenderableCreatureDisplayIds_.insert(displayId); + creaturePermanentFailureGuids_.insert(guid); return; } pipeline::M2Model model = pipeline::M2Loader::load(m2Data); if (model.vertices.empty()) { LOG_WARNING("Failed to parse creature M2: ", m2Path); + nonRenderableCreatureDisplayIds_.insert(displayId); + creaturePermanentFailureGuids_.insert(guid); return; } @@ -3695,6 +3708,8 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x if (!charRenderer->loadModel(model, modelId)) { LOG_WARNING("Failed to load creature model: ", m2Path); + nonRenderableCreatureDisplayIds_.insert(displayId); + creaturePermanentFailureGuids_.insert(guid); return; } @@ -5688,6 +5703,14 @@ void Application::processCreatureSpawnQueue() { PendingCreatureSpawn s = pendingCreatureSpawns_.front(); pendingCreatureSpawns_.erase(pendingCreatureSpawns_.begin()); + if (nonRenderableCreatureDisplayIds_.count(s.displayId)) { + pendingCreatureSpawnGuids_.erase(s.guid); + creatureSpawnRetryCounts_.erase(s.guid); + processed++; + rotationsLeft = pendingCreatureSpawns_.size(); + continue; + } + const bool needsNewModel = (displayIdModelCache_.find(s.displayId) == displayIdModelCache_.end()); if (needsNewModel && newModelLoads >= MAX_NEW_CREATURE_MODELS_PER_FRAME) { // Defer additional first-time model/texture loads to later frames so diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 0eb6f638..c4bfaeb3 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -1885,7 +1885,7 @@ void GameHandler::handlePacket(network::Packet& packet) { break; } uint32_t reason = packet.readUInt32(); - LOG_INFO("Resurrect cancel reason: ", reason); + LOG_DEBUG("Time sync request reason: ", reason); resurrectPending_ = false; resurrectRequestPending_ = false; break; @@ -4986,8 +4986,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } // Trigger creature spawn callback for units/players with displayId if (block.objectType == ObjectType::UNIT && unit->getDisplayId() == 0) { - LOG_INFO("[Spawn] UNIT guid=0x", std::hex, block.guid, std::dec, - " has displayId=0 — no spawn (entry=", unit->getEntry(), ")"); + LOG_DEBUG("[Spawn] UNIT guid=0x", std::hex, block.guid, std::dec, + " has displayId=0 — no spawn (entry=", unit->getEntry(), ")"); } if ((block.objectType == ObjectType::UNIT || block.objectType == ObjectType::PLAYER) && unit->getDisplayId() != 0) { if (block.objectType == ObjectType::PLAYER && block.guid == playerGuid) { @@ -5004,9 +5004,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } } } else if (creatureSpawnCallback_) { - LOG_INFO("[Spawn] UNIT guid=0x", std::hex, block.guid, std::dec, - " displayId=", unit->getDisplayId(), " at (", - unit->getX(), ",", unit->getY(), ",", unit->getZ(), ")"); + LOG_DEBUG("[Spawn] UNIT guid=0x", std::hex, block.guid, std::dec, + " displayId=", unit->getDisplayId(), " at (", + unit->getX(), ",", unit->getY(), ",", unit->getZ(), ")"); creatureSpawnCallback_(block.guid, unit->getDisplayId(), unit->getX(), unit->getY(), unit->getZ(), unit->getOrientation()); if (unitInitiallyDead && npcDeathCallback_) {