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.
This commit is contained in:
Kelsi 2026-03-29 21:26:01 -07:00
parent 74cc048767
commit 3dd1128ecf
2 changed files with 20 additions and 12 deletions

View file

@ -1033,16 +1033,17 @@ void CameraController::update(float deltaTime) {
terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y); terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y);
} }
if (wmoAsync) { if (wmoAsync) {
auto [h, nz] = wmoFuture.get(); try { auto [h, nz] = wmoFuture.get(); wmoH = h; wmoNormalZ = nz; }
wmoH = h; catch (const std::exception& e) { LOG_ERROR("WMO floor query: ", e.what()); }
wmoNormalZ = nz;
} }
if (m2Async) { if (m2Async) {
auto [h, nz] = m2Future.get(); try {
m2H = h; auto [h, nz] = m2Future.get();
if (m2H && nz < MIN_WALKABLE_NORMAL_M2) { m2H = h;
m2H = std::nullopt; 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 // Reject steep WMO slopes

View file

@ -3538,7 +3538,8 @@ void Renderer::update(float deltaTime) {
// Wait for M2 doodad animation to finish (was launched earlier in parallel with character anim) // Wait for M2 doodad animation to finish (was launched earlier in parallel with character anim)
if (m2AnimLaunched) { 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 // 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 --- // --- Wait for workers ---
if (terrainFuture.valid()) lastTerrainRenderMs = terrainFuture.get(); // Guard with try-catch: future::get() re-throws any exception from the
if (wmoFuture.valid()) lastWMORenderMs = wmoFuture.get(); // async task. Without this, a single bad_alloc in a render worker would
if (m2Future.valid()) lastM2RenderMs = m2Future.get(); // 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) --- // --- Main thread: record post-opaque (SEC_POST) ---
{ {