From 3dd1128ecf76fa5cae68346b49416af2fd07fc04 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 29 Mar 2026 21:26:01 -0700 Subject: [PATCH] fix: unguarded future::get() crashed on render/floor-query worker exceptions std::future::get() re-throws any exception from the async task. The 6 call sites in the render pipeline (terrain/WMO/M2 workers + animation worker) and 2 floor-query sites in camera_controller were unguarded, so a single bad_alloc in any worker would terminate the process with no recovery. Now wrapped in try-catch with error logging. --- src/rendering/camera_controller.cpp | 17 +++++++++-------- src/rendering/renderer.cpp | 15 +++++++++++---- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index c31ff01c..7db9c808 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -1033,16 +1033,17 @@ void CameraController::update(float deltaTime) { terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y); } if (wmoAsync) { - auto [h, nz] = wmoFuture.get(); - wmoH = h; - wmoNormalZ = nz; + try { auto [h, nz] = wmoFuture.get(); wmoH = h; wmoNormalZ = nz; } + catch (const std::exception& e) { LOG_ERROR("WMO floor query: ", e.what()); } } if (m2Async) { - auto [h, nz] = m2Future.get(); - m2H = h; - if (m2H && nz < MIN_WALKABLE_NORMAL_M2) { - m2H = std::nullopt; - } + try { + auto [h, nz] = m2Future.get(); + m2H = h; + if (m2H && nz < MIN_WALKABLE_NORMAL_M2) { + m2H = std::nullopt; + } + } catch (const std::exception& e) { LOG_ERROR("M2 floor query: ", e.what()); } } // Reject steep WMO slopes diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 4489da5c..c5f3ab05 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -3538,7 +3538,8 @@ void Renderer::update(float deltaTime) { // Wait for M2 doodad animation to finish (was launched earlier in parallel with character anim) if (m2AnimLaunched) { - m2AnimFuture.get(); + try { m2AnimFuture.get(); } + catch (const std::exception& e) { LOG_ERROR("M2 animation worker: ", e.what()); } } // Helper: play zone music, dispatching local files (file: prefix) vs MPQ paths @@ -5576,9 +5577,15 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) { } // --- Wait for workers --- - if (terrainFuture.valid()) lastTerrainRenderMs = terrainFuture.get(); - if (wmoFuture.valid()) lastWMORenderMs = wmoFuture.get(); - if (m2Future.valid()) lastM2RenderMs = m2Future.get(); + // Guard with try-catch: future::get() re-throws any exception from the + // async task. Without this, a single bad_alloc in a render worker would + // propagate as an unhandled exception and terminate the process. + try { if (terrainFuture.valid()) lastTerrainRenderMs = terrainFuture.get(); } + catch (const std::exception& e) { LOG_ERROR("Terrain render worker: ", e.what()); } + try { if (wmoFuture.valid()) lastWMORenderMs = wmoFuture.get(); } + catch (const std::exception& e) { LOG_ERROR("WMO render worker: ", e.what()); } + try { if (m2Future.valid()) lastM2RenderMs = m2Future.get(); } + catch (const std::exception& e) { LOG_ERROR("M2 render worker: ", e.what()); } // --- Main thread: record post-opaque (SEC_POST) --- {