From e3ea6a766e3dfaf4af3a750e03c416cee370be48 Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 19 Oct 2026 19:25:25 +0200 Subject: [PATCH] fix(editor): putting the scenery onto the terrain model no longer leaves deleted models in the scene The lift moved the models of the includes it remade a moment later, so the cells kept the old ones after they were destroyed and the next look at the ground there (a zoom) crashed. They stay as they are now, and the models already waiting to be destroyed are skipped too. The self-test gets danglers, which counts the models the cells hold past their end. --- application/editormodenmt.cpp | 15 +++++++++++---- application/editormodeselftestterrain.cpp | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/application/editormodenmt.cpp b/application/editormodenmt.cpp index 4310d6c8..a540e010 100644 --- a/application/editormodenmt.cpp +++ b/application/editormodenmt.cpp @@ -1030,13 +1030,18 @@ void editor_mode::nmt_lift_scenery(double const Offset) item.yaw = standing.yaw; moves.emplace_back(standing, item); } - if (move_templates(moves) > 0 && m_history.size() >= 2) - m_history.back().joined = true; - // the models, a group as a whole + // the includes moved by the place in their parameters are made anew there: their models aren't moved on their own, nor are + // the models left behind by earlier changes, deleted a moment later: moved, they'd get into the scene again + std::set remade; + for (auto const &move : moves) + if (scene::Layers.tracked(move.first.instance) && scene::Layers.removable(move.first.instance)) + remade.insert(move.first.instance); + // the models, a group as a whole, ahead of the includes, which retire theirs std::set groups; for (auto *instance : simulation::Instances.sequence()) { - if (instance == nullptr) + if (instance == nullptr || remade.contains(instance->m_instance) || + std::any_of(m_retired.begin(), m_retired.end(), [&](std::pair const &Retired) { return Retired.first == instance; })) continue; if (instance->group() > 1 && false == groups.insert(instance->group()).second) continue; @@ -1044,6 +1049,8 @@ void editor_mode::nmt_lift_scenery(double const Offset) m_history.back().joined = true; m_editor.translate(instance, instance->location() + shift, true); } + if (move_templates(moves) > 0 && m_history.size() >= 2) + m_history.back().joined = true; WriteLog(format("Editor: NMT - the scenery moved %+.2f m: %zu tracks, %zu includes, the models", Offset, lifted, moves.size()), logtype::generic); } diff --git a/application/editormodeselftestterrain.cpp b/application/editormodeselftestterrain.cpp index 2b49a7bc..8f2c66ca 100644 --- a/application/editormodeselftestterrain.cpp +++ b/application/editormodeselftestterrain.cpp @@ -231,6 +231,29 @@ editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &co WriteLog("SELFTEST nmtwait: " + m_nmt_state.status + (m_nmt_state.error.empty() ? std::string{} : " (" + m_nmt_state.error + ")")); return selftest_result::handled; } + if (command == "danglers") + { + // models the cells hold which aren't in the scene any more, or are about to be destroyed + std::set const alive{simulation::Instances.sequence().begin(), simulation::Instances.sequence().end()}; + std::size_t held{0}, gone{0}, retired{0}; + for (auto const *section : simulation::Region->m_sections) + { + if (section == nullptr) + continue; + for (auto const &cell : section->m_cells) + for (auto const *sequence : {&cell.m_instancesopaque, &cell.m_instancetranslucent}) + for (auto const *instance : *sequence) + { + ++held; + if (false == alive.contains(instance)) + ++gone; + else if (std::any_of(m_retired.begin(), m_retired.end(), [&](std::pair const &Retired) { return Retired.first == instance; })) + ++retired; + } + } + WriteLog(format("SELFTEST danglers: %zu held, %zu gone, %zu retired, %zu waiting", held, gone, retired, m_retired.size())); + return selftest_result::handled; + } if (command == "terrainmode") { m_terrain_open = true;