Fix terrain streaming crash: pendingTiles data race and missing null checks

Guard pendingTiles.erase() with queueMutex in processReadyTiles and
unloadTile to prevent data race with worker threads. Add defensive null
checks in M2/WMO render and animation paths. Move cleanupUnusedModels
out of per-tile unload loop to run once after all tiles are removed.
This commit is contained in:
Kelsi 2026-02-07 18:57:34 -08:00
parent c156f3d390
commit 0d94bca896
3 changed files with 44 additions and 19 deletions

View file

@ -676,7 +676,10 @@ void TerrainManager::processReadyTiles() {
if (pending) {
TileCoord coord = pending->coord;
finalizeTile(std::move(pending));
pendingTiles.erase(coord);
{
std::lock_guard<std::mutex> lock(queueMutex);
pendingTiles.erase(coord);
}
processed++;
}
}
@ -694,7 +697,10 @@ void TerrainManager::processAllReadyTiles() {
if (pending) {
TileCoord coord = pending->coord;
finalizeTile(std::move(pending));
pendingTiles.erase(coord);
{
std::lock_guard<std::mutex> lock(queueMutex);
pendingTiles.erase(coord);
}
}
}
}
@ -703,7 +709,10 @@ void TerrainManager::unloadTile(int x, int y) {
TileCoord coord = {x, y};
// Also remove from pending if it was queued but not yet loaded
pendingTiles.erase(coord);
{
std::lock_guard<std::mutex> lock(queueMutex);
pendingTiles.erase(coord);
}
auto it = loadedTiles.find(coord);
if (it == loadedTiles.end()) {
@ -750,14 +759,6 @@ void TerrainManager::unloadTile(int x, int y) {
}
loadedTiles.erase(it);
// Clean up any models that are no longer referenced
if (m2Renderer) {
m2Renderer->cleanupUnusedModels();
}
if (wmoRenderer) {
wmoRenderer->cleanupUnusedModels();
}
}
void TerrainManager::unloadAll() {
@ -1091,6 +1092,14 @@ void TerrainManager::streamTiles() {
}
if (!tilesToUnload.empty()) {
// Clean up models that lost all instances (once, after all tiles removed)
if (m2Renderer) {
m2Renderer->cleanupUnusedModels();
}
if (wmoRenderer) {
wmoRenderer->cleanupUnusedModels();
}
LOG_INFO("Unloaded ", tilesToUnload.size(), " distant tiles, ",
loadedTiles.size(), " remain");
}