From 6f066dee48e06cadf1f307eb7433687a108282d6 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 23:59:51 -0700 Subject: [PATCH] fix(editor): debounce M2 rebuild to prevent clear+reload loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ROOT CAUSE of NPC models not rendering: every NPC placement triggered an immediate full clear+rebuild of ALL M2 models. During rapid clicking, this created a destroy-reload cycle where models were cleared faster than they could render — the log showed rebuild firing every ~200ms with models loading OK but being destroyed before the next frame. Fix: debounce rebuilds with a 0.5s timer. Multiple rapid placements reset the timer, so the rebuild only fires once after the user stops clicking. Models stay loaded and visible between placements. Before: click → clear all → reload all → click → clear all → reload... After: click → click → click → (0.5s pause) → single rebuild --- tools/editor/editor_app.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index 4e388bd8..989199e9 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -138,15 +138,25 @@ void EditorApp::run() { if (cmd == VK_NULL_HANDLE) continue; // Rebuild objects AFTER beginFrame so instance SSBO uses correct frame index + // Debounce: wait 0.5s after last change before rebuilding to avoid + // clear+reload cycle on every click during rapid NPC placement + static float rebuildTimer = 0.0f; if (objChanged || objectsDirty_) { + rebuildTimer = 0.5f; objectsDirty_ = false; lastObjCount_ = objCount; - if (objCount > 0 || npcCount > 0) { - vkDeviceWaitIdle(vkCtx->getDevice()); - viewport_.rebuildObjects(objectPlacer_.getObjects(), npcSpawner_.getSpawns()); - } lastNpcCount_ = npcCount; } + if (rebuildTimer > 0.0f) { + rebuildTimer -= dt; + if (rebuildTimer <= 0.0f) { + rebuildTimer = 0.0f; + if (objectPlacer_.objectCount() > 0 || npcSpawner_.spawnCount() > 0) { + vkDeviceWaitIdle(vkCtx->getDevice()); + viewport_.rebuildObjects(objectPlacer_.getObjects(), npcSpawner_.getSpawns()); + } + } + } // Update M2 animations AFTER beginFrame (so getCurrentFrame is correct) viewport_.update(dt);