mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 08:03:50 +00:00
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:
parent
0422b7573c
commit
c0803089c0
3 changed files with 44 additions and 19 deletions
|
|
@ -1318,9 +1318,11 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::
|
||||||
if (animCount < 32 || numAnimThreads_ <= 1) {
|
if (animCount < 32 || numAnimThreads_ <= 1) {
|
||||||
// Sequential — not enough work to justify thread overhead
|
// Sequential — not enough work to justify thread overhead
|
||||||
for (size_t i : boneWorkIndices) {
|
for (size_t i : boneWorkIndices) {
|
||||||
|
if (i >= instances.size()) continue;
|
||||||
auto& inst = instances[i];
|
auto& inst = instances[i];
|
||||||
const auto& mdl = models.find(inst.modelId)->second;
|
auto mdlIt = models.find(inst.modelId);
|
||||||
computeBoneMatrices(mdl, inst);
|
if (mdlIt == models.end()) continue;
|
||||||
|
computeBoneMatrices(mdlIt->second, inst);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Parallel — dispatch across worker threads
|
// Parallel — dispatch across worker threads
|
||||||
|
|
@ -1338,9 +1340,11 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::
|
||||||
[this, &boneWorkIndices, start, end]() {
|
[this, &boneWorkIndices, start, end]() {
|
||||||
for (size_t j = start; j < end; ++j) {
|
for (size_t j = start; j < end; ++j) {
|
||||||
size_t idx = boneWorkIndices[j];
|
size_t idx = boneWorkIndices[j];
|
||||||
|
if (idx >= instances.size()) continue;
|
||||||
auto& inst = instances[idx];
|
auto& inst = instances[idx];
|
||||||
const auto& mdl = models.find(inst.modelId)->second;
|
auto mdlIt = models.find(inst.modelId);
|
||||||
computeBoneMatrices(mdl, inst);
|
if (mdlIt == models.end()) continue;
|
||||||
|
computeBoneMatrices(mdlIt->second, inst);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
start = end;
|
start = end;
|
||||||
|
|
@ -1354,8 +1358,11 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::
|
||||||
|
|
||||||
// Phase 3: Particle update (sequential — uses RNG, not thread-safe)
|
// Phase 3: Particle update (sequential — uses RNG, not thread-safe)
|
||||||
for (size_t idx : boneWorkIndices) {
|
for (size_t idx : boneWorkIndices) {
|
||||||
|
if (idx >= instances.size()) continue;
|
||||||
auto& instance = instances[idx];
|
auto& instance = instances[idx];
|
||||||
const auto& model = models.find(instance.modelId)->second;
|
auto mdlIt = models.find(instance.modelId);
|
||||||
|
if (mdlIt == models.end()) continue;
|
||||||
|
const auto& model = mdlIt->second;
|
||||||
if (!model.particleEmitters.empty()) {
|
if (!model.particleEmitters.empty()) {
|
||||||
emitParticles(instance, model, deltaTime);
|
emitParticles(instance, model, deltaTime);
|
||||||
updateParticles(instance, deltaTime);
|
updateParticles(instance, deltaTime);
|
||||||
|
|
@ -1471,13 +1478,16 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
||||||
const M2ModelGPU* currentModel = nullptr;
|
const M2ModelGPU* currentModel = nullptr;
|
||||||
|
|
||||||
for (const auto& entry : sortedVisible) {
|
for (const auto& entry : sortedVisible) {
|
||||||
|
if (entry.index >= instances.size()) continue;
|
||||||
const auto& instance = instances[entry.index];
|
const auto& instance = instances[entry.index];
|
||||||
|
|
||||||
// Bind VAO once per model group
|
// Bind VAO once per model group
|
||||||
if (entry.modelId != currentModelId) {
|
if (entry.modelId != currentModelId) {
|
||||||
if (currentModel) glBindVertexArray(0);
|
if (currentModel) glBindVertexArray(0);
|
||||||
currentModelId = entry.modelId;
|
currentModelId = entry.modelId;
|
||||||
currentModel = &models.find(currentModelId)->second;
|
auto mdlIt = models.find(currentModelId);
|
||||||
|
if (mdlIt == models.end()) continue;
|
||||||
|
currentModel = &mdlIt->second;
|
||||||
glBindVertexArray(currentModel->vao);
|
glBindVertexArray(currentModel->vao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -676,7 +676,10 @@ void TerrainManager::processReadyTiles() {
|
||||||
if (pending) {
|
if (pending) {
|
||||||
TileCoord coord = pending->coord;
|
TileCoord coord = pending->coord;
|
||||||
finalizeTile(std::move(pending));
|
finalizeTile(std::move(pending));
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(queueMutex);
|
||||||
pendingTiles.erase(coord);
|
pendingTiles.erase(coord);
|
||||||
|
}
|
||||||
processed++;
|
processed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -694,16 +697,22 @@ void TerrainManager::processAllReadyTiles() {
|
||||||
if (pending) {
|
if (pending) {
|
||||||
TileCoord coord = pending->coord;
|
TileCoord coord = pending->coord;
|
||||||
finalizeTile(std::move(pending));
|
finalizeTile(std::move(pending));
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(queueMutex);
|
||||||
pendingTiles.erase(coord);
|
pendingTiles.erase(coord);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TerrainManager::unloadTile(int x, int y) {
|
void TerrainManager::unloadTile(int x, int y) {
|
||||||
TileCoord coord = {x, y};
|
TileCoord coord = {x, y};
|
||||||
|
|
||||||
// Also remove from pending if it was queued but not yet loaded
|
// Also remove from pending if it was queued but not yet loaded
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(queueMutex);
|
||||||
pendingTiles.erase(coord);
|
pendingTiles.erase(coord);
|
||||||
|
}
|
||||||
|
|
||||||
auto it = loadedTiles.find(coord);
|
auto it = loadedTiles.find(coord);
|
||||||
if (it == loadedTiles.end()) {
|
if (it == loadedTiles.end()) {
|
||||||
|
|
@ -750,14 +759,6 @@ void TerrainManager::unloadTile(int x, int y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedTiles.erase(it);
|
loadedTiles.erase(it);
|
||||||
|
|
||||||
// Clean up any models that are no longer referenced
|
|
||||||
if (m2Renderer) {
|
|
||||||
m2Renderer->cleanupUnusedModels();
|
|
||||||
}
|
|
||||||
if (wmoRenderer) {
|
|
||||||
wmoRenderer->cleanupUnusedModels();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerrainManager::unloadAll() {
|
void TerrainManager::unloadAll() {
|
||||||
|
|
@ -1091,6 +1092,14 @@ void TerrainManager::streamTiles() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tilesToUnload.empty()) {
|
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, ",
|
LOG_INFO("Unloaded ", tilesToUnload.size(), " distant tiles, ",
|
||||||
loadedTiles.size(), " remain");
|
loadedTiles.size(), " remain");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -852,8 +852,11 @@ void WMORenderer::render(const Camera& camera, const glm::mat4& view, const glm:
|
||||||
bool doFrustumCull = frustumCulling;
|
bool doFrustumCull = frustumCulling;
|
||||||
|
|
||||||
auto cullInstance = [&](size_t instIdx) -> InstanceDrawList {
|
auto cullInstance = [&](size_t instIdx) -> InstanceDrawList {
|
||||||
|
if (instIdx >= instances.size()) return InstanceDrawList{};
|
||||||
const auto& instance = instances[instIdx];
|
const auto& instance = instances[instIdx];
|
||||||
const ModelData& model = loadedModels.find(instance.modelId)->second;
|
auto mdlIt = loadedModels.find(instance.modelId);
|
||||||
|
if (mdlIt == loadedModels.end()) return InstanceDrawList{};
|
||||||
|
const ModelData& model = mdlIt->second;
|
||||||
|
|
||||||
InstanceDrawList result;
|
InstanceDrawList result;
|
||||||
result.instanceIndex = instIdx;
|
result.instanceIndex = instIdx;
|
||||||
|
|
@ -942,8 +945,11 @@ void WMORenderer::render(const Camera& camera, const glm::mat4& view, const glm:
|
||||||
|
|
||||||
// ── Phase 2: Sequential GL draw ────────────────────────────────
|
// ── Phase 2: Sequential GL draw ────────────────────────────────
|
||||||
for (const auto& dl : drawLists) {
|
for (const auto& dl : drawLists) {
|
||||||
|
if (dl.instanceIndex >= instances.size()) continue;
|
||||||
const auto& instance = instances[dl.instanceIndex];
|
const auto& instance = instances[dl.instanceIndex];
|
||||||
const ModelData& model = loadedModels.find(instance.modelId)->second;
|
auto modelIt = loadedModels.find(instance.modelId);
|
||||||
|
if (modelIt == loadedModels.end()) continue;
|
||||||
|
const ModelData& model = modelIt->second;
|
||||||
|
|
||||||
// Occlusion query pre-pass (GL calls — must be main thread)
|
// Occlusion query pre-pass (GL calls — must be main thread)
|
||||||
if (occlusionCulling && occlusionShader && bboxVao != 0) {
|
if (occlusionCulling && occlusionShader && bboxVao != 0) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue