From c0b42d1b88cff176401deb0c40a6e48349de608c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Labarca?= Date: Sat, 4 Jul 2026 06:41:12 -0400 Subject: [PATCH 1/2] fix(domain): Copy registry key before moving object in JSON loaders std::move on the object argument can be evaluated before the id sub-expression, leaving the key empty and causing all JSON entries to overwrite each other. Copy the key explicitly before the move. --- src/domain/materials/MaterialRegistry.cpp | 3 ++- src/domain/physics/ParticleRegistry.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/domain/materials/MaterialRegistry.cpp b/src/domain/materials/MaterialRegistry.cpp index 757d247..f65913b 100644 --- a/src/domain/materials/MaterialRegistry.cpp +++ b/src/domain/materials/MaterialRegistry.cpp @@ -37,7 +37,8 @@ void MaterialRegistry::registerMaterialsFromJson(const std::string& jsonPath) for (const auto& item : json) { Material m = item.get(); - registerMaterial(m.id, std::move(m)); + auto id = m.id; // copy key before moving the object + registerMaterial(id, std::move(m)); } } catch (const std::exception& e) { std::cerr << "[MaterialRegistry] Error parsing " << jsonPath diff --git a/src/domain/physics/ParticleRegistry.cpp b/src/domain/physics/ParticleRegistry.cpp index 185e680..71dc164 100644 --- a/src/domain/physics/ParticleRegistry.cpp +++ b/src/domain/physics/ParticleRegistry.cpp @@ -34,7 +34,8 @@ void ParticleRegistry::registerParticlesFromJson(const std::string& jsonPath) } for (const auto& item : json) { Particle p = item.get(); - registerParticle(p.id, std::move(p)); + auto id = p.id; // copy key before moving the object + registerParticle(id, std::move(p)); } } catch (const std::exception& e) { std::cerr << "[ParticleRegistry] Error parsing " << jsonPath From 7fe5fc4e56cc20c8b60829181300e49bde86dbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Labarca?= Date: Sat, 4 Jul 2026 06:52:47 -0400 Subject: [PATCH 2/2] fix(threadpool): Increment active counter while holding queue lock The active counter was incremented after popping the task and releasing the queue mutex. This allowed waitAll() to observe an empty queue and zero active threads while a worker was about to start a task, causing runAll() to return and destroy the result mutex before the worker used it. Increment active_ under the lock before releasing the task, so waitAll() only returns when no worker owns an in-flight task. Co-Authored-By: Claude --- src/services/analysis/ThreadPool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/analysis/ThreadPool.cpp b/src/services/analysis/ThreadPool.cpp index 91865b5..dccec22 100644 --- a/src/services/analysis/ThreadPool.cpp +++ b/src/services/analysis/ThreadPool.cpp @@ -80,10 +80,10 @@ void ThreadPool::workerLoop() return; } + ++active_; task = std::move(tasks_.front()); tasks_.pop(); } - ++active_; task(); --active_; }