From 0742abfe94d387b0dd1732949cf5d89c5c6babc8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 07:13:24 -0700 Subject: [PATCH] fix(editor): separate NPC marker updates from M2 rebuild cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NPC placement now only updates cheap marker geometry (no M2 reload) - Full M2 rebuild only triggers when PLACED OBJECT count changes (not NPC count — NPCs use markers, not M2 instances for now) - Split lastObjectCount_ into lastObjCount_ + lastNpcCount_ to track objects and NPCs independently - Prevents the destructive clear+reload cycle that caused GPU crashes when rapidly placing multiple NPCs --- tools/editor/editor_app.cpp | 32 +++++++++++++++++++++----------- tools/editor/editor_app.hpp | 3 ++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index 277ea4ae..4b2c2151 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -98,19 +98,27 @@ void EditorApp::run() { // Refresh dirty terrain chunks refreshDirtyChunks(); - // Update NPC markers (cheap — just vertex buffer, no M2 reload) - size_t objCount = objectPlacer_.objectCount() + npcSpawner_.spawnCount(); - if (objectsDirty_ || objCount != lastObjectCount_) { - objectsDirty_ = false; - bool countChanged = (objCount != lastObjectCount_); - lastObjectCount_ = objCount; - // Only update NPC position markers (always cheap) + // Track object and NPC counts separately + size_t objCount = objectPlacer_.objectCount(); + size_t npcCount = npcSpawner_.spawnCount(); + bool objChanged = (objCount != lastObjCount_); + bool npcChanged = (npcCount != lastNpcCount_) || objectsDirty_; + + if (npcChanged) { + // NPC markers are cheap — always update viewport_.updateNpcMarkers(npcSpawner_.getSpawns()); - // Full M2 rebuild only when explicitly requested (not on every click) - if (countChanged && objCount > 0) { + lastNpcCount_ = npcCount; + } + + if (objChanged || objectsDirty_) { + // Full M2 rebuild only when placed objects change + objectsDirty_ = false; + lastObjCount_ = objCount; + if (objCount > 0 || npcCount > 0) { vkDeviceWaitIdle(vkCtx->getDevice()); viewport_.rebuildObjects(objectPlacer_.getObjects(), npcSpawner_.getSpawns()); } + lastNpcCount_ = npcCount; // sync after rebuild } // Show gizmo arrows on selected object @@ -622,7 +630,8 @@ void EditorApp::createNewTerrain(const std::string& mapName, int tileX, int tile loadedMap_ = mapName; loadedTileX_ = tileX; loadedTileY_ = tileY; - lastObjectCount_ = 0; + lastObjCount_ = 0; + lastNpcCount_ = 0; objectsDirty_ = false; float centerX = (32.0f - tileY) * 533.33333f - 8.0f * 533.33333f / 16.0f; @@ -815,7 +824,8 @@ void EditorApp::clearAllObjects() { viewport_.clearObjects(); viewport_.updateNpcMarkers({}); terrainEditor_.history().clear(); - lastObjectCount_ = 0; + lastObjCount_ = 0; + lastNpcCount_ = 0; objectsDirty_ = false; showToast("All objects and NPCs cleared"); } diff --git a/tools/editor/editor_app.hpp b/tools/editor/editor_app.hpp index 691f1084..64de4a27 100644 --- a/tools/editor/editor_app.hpp +++ b/tools/editor/editor_app.hpp @@ -136,7 +136,8 @@ public: const std::vector& getToasts() const { return toasts_; } void updateToasts(float dt); private: - size_t lastObjectCount_ = 0; + size_t lastObjCount_ = 0; + size_t lastNpcCount_ = 0; EditorMode mode_ = EditorMode::Sculpt; float waterHeight_ = 100.0f; uint16_t waterType_ = 0;