From 85767187b1aef3686a921d059a967c193ab744dc Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 13 Mar 2026 03:43:55 -0700 Subject: [PATCH] fix: clear gameObjectDisplayIdWmoCache_ on world transition, add stale-entry guard gameObjectDisplayIdWmoCache_ was not cleared on world unload/transition, causing stale WMO model IDs (e.g. 40006, 40003) to be looked up after the renderer cleared its model list, resulting in "Cannot create instance of unloaded WMO model" errors on zone re-entry. Changes: - Clear gameObjectDisplayIdWmoCache_ alongside other GO caches on world reset - Add WMORenderer::isModelLoaded() for cache-hit validation - Inline GO WMO path now verifies cached model is still renderer-resident before using it; evicts stale entries and falls back to reload --- include/rendering/wmo_renderer.hpp | 6 ++++++ src/core/application.cpp | 12 ++++++++++-- src/rendering/wmo_renderer.cpp | 4 ++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/rendering/wmo_renderer.hpp b/include/rendering/wmo_renderer.hpp index 08108dc0..7f6728af 100644 --- a/include/rendering/wmo_renderer.hpp +++ b/include/rendering/wmo_renderer.hpp @@ -69,6 +69,12 @@ public: */ bool loadModel(const pipeline::WMOModel& model, uint32_t id); + /** + * Check if a WMO model is currently resident in the renderer + * @param id WMO model identifier + */ + bool isModelLoaded(uint32_t id) const; + /** * Unload WMO model and free GPU resources * @param id WMO model identifier diff --git a/src/core/application.cpp b/src/core/application.cpp index 6c74e854..83886782 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -4113,6 +4113,7 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float gameObjectInstances_.clear(); gameObjectDisplayIdModelCache_.clear(); + gameObjectDisplayIdWmoCache_.clear(); gameObjectDisplayIdFailedCache_.clear(); // Force player character re-spawn on new map @@ -7115,8 +7116,15 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t auto itCache = gameObjectDisplayIdWmoCache_.find(displayId); if (itCache != gameObjectDisplayIdWmoCache_.end()) { modelId = itCache->second; - loadedAsWmo = true; - } else { + // Only use cached entry if the model is still resident in the renderer + if (wmoRenderer->isModelLoaded(modelId)) { + loadedAsWmo = true; + } else { + gameObjectDisplayIdWmoCache_.erase(itCache); + modelId = 0; + } + } + if (!loadedAsWmo && modelId == 0) { auto wmoData = assetManager->readFile(modelPath); if (!wmoData.empty()) { pipeline::WMOModel wmoModel = pipeline::WMOLoader::load(wmoData); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 4207b24b..7210d1be 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -805,6 +805,10 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) { return true; } +bool WMORenderer::isModelLoaded(uint32_t id) const { + return loadedModels.find(id) != loadedModels.end(); +} + void WMORenderer::unloadModel(uint32_t id) { auto it = loadedModels.find(id); if (it == loadedModels.end()) {