From b34238f33fd49a82e9bc68380959335c890343ab Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 3 Nov 2026 21:45:57 +0100 Subject: [PATCH 1/7] fix(app): the log written to the end of the closing, each step of it with its time; the Discord thread idle outside the driver mode The log service stopped with applicationQuitOrder, so whatever was logged while the simulator closed was lost; it now runs until StopLogService() and writes out what came before. The closing logs each of its steps with the time since it began, to tell where a closing that hangs stops. Outside the driver mode the Discord thread spun a core in its loop, it now sleeps a second between the checks. --- application/application.cpp | 16 ++++++++++++++++ nodebank_previews_standalone.cpp | 4 ++-- utilities/Logs.cpp | 11 ++++++++++- utilities/Logs.h | 2 ++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/application/application.cpp b/application/application.cpp index 119cb021e..56a83c29c 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -270,7 +270,11 @@ void eu07_application::DiscordRPCService() } if (currentMode != mode::driver) + { + // the editor and the other modes: nothing to tell, without spinning a core on it + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); continue; + } // Discord RPC updater if (simulation::is_ready) @@ -690,19 +694,29 @@ void eu07_application::release_python_lock() void eu07_application::exit() { Global.applicationQuitOrder = true; + // each step of the closing in the log, to tell from it where a closing that hangs stops + auto const closing{std::chrono::steady_clock::now()}; + auto const step = [&](char const *What) { + WriteLog(std::string{"Closing: "} + What + " at " + std::to_string(std::chrono::duration_cast(std::chrono::steady_clock::now() - closing).count()) + " ms"); + }; + step("the modes"); for (auto &mode : m_modes) mode.reset(); + step("the renderer"); GfxRenderer->Shutdown(); m_network.reset(); // SafeDelete( simulation::Train ); + step("the scenery"); SafeDelete(simulation::Region); + step("the ui and the python workers"); ui_layer::shutdown(); // the python workers detach their GL context on the way out, so they have to be done before the windows they use are destroyed m_taskqueue.exit(); + step("the windows"); for (auto *window : m_windows) { glfwDestroyWindow(window); @@ -713,6 +727,8 @@ void eu07_application::exit() if (!Global.exec_on_exit.empty()) system(Global.exec_on_exit.c_str()); + step("done"); + StopLogService(); auto it = Global.threads.find("LogService"); if (it != Global.threads.end()) { diff --git a/nodebank_previews_standalone.cpp b/nodebank_previews_standalone.cpp index 39884fd38..d94eb13ed 100644 --- a/nodebank_previews_standalone.cpp +++ b/nodebank_previews_standalone.cpp @@ -290,9 +290,9 @@ int generate_nodebank_previews(int Argc, char *Argv[]) std::thread logservice(LogService); auto const finish = [&](int const Result) { - // give the log service time to write out what's left - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + // the log service writes out what's left before it stops Global.applicationQuitOrder = true; + StopLogService(); logservice.join(); return Result; }; diff --git a/utilities/Logs.cpp b/utilities/Logs.cpp index 38f8062b9..18020d6f1 100644 --- a/utilities/Logs.cpp +++ b/utilities/Logs.cpp @@ -73,12 +73,21 @@ std::deque ErrorStack; // lock for log stacks std::mutex logMutex; +// the log is written through the closing of the simulator, to the end +std::atomic logStop{false}; + +void StopLogService() +{ + logStop = true; +} void LogService() { - while (!Global.applicationQuitOrder) + // one more pass after the stop, so what came just before it gets written too + for (bool last{false}; false == last;) { + last = logStop; { // --- Obsługa InfoStack --- while (!InfoStack.empty()) diff --git a/utilities/Logs.h b/utilities/Logs.h index 93276d651..63727de9c 100644 --- a/utilities/Logs.h +++ b/utilities/Logs.h @@ -23,7 +23,9 @@ enum class logtype : unsigned int { traction = 1 << 9, powergrid = 1 << 10, }; +// writes the log until StopLogService(), and all of it that came before void LogService(); +void StopLogService(); void WriteLog( const char *str, logtype const Type = logtype::generic, bool isError = false ); void Error( const std::string &asMessage, bool box = false ); void Error( const char* &asMessage, bool box = false ); From e75d8496797732cb2463956f3986f022529bdb6b Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 3 Nov 2026 21:45:57 +0100 Subject: [PATCH 2/7] fix(app): a scenery file named in another case than given is found, a scenario that can't be opened goes back to the launcher The command line gives the name of the scenery lowercased, and the sceneries made on Windows name their files and includes in any case: on a case-sensitive filesystem they weren't found. FileCased() finds the file whose name differs only in case; the parser and the scenario loader use it. A scenario that can't be opened logs the reason and goes back to the launcher instead of ending with an exception. --- application/scenarioloadermode.cpp | 16 +++++++++++++++- utilities/parser.cpp | 9 +++++++++ utilities/utilities.cpp | 14 ++++++++++++++ utilities/utilities.h | 3 +++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/application/scenarioloadermode.cpp b/application/scenarioloadermode.cpp index dbdce6068..7afe0a442 100644 --- a/application/scenarioloadermode.cpp +++ b/application/scenarioloadermode.cpp @@ -41,11 +41,25 @@ bool scenarioloader_mode::update() { WriteLog("using simulation seed: " + std::to_string(Global.random_seed), logtype::generic); WriteLog("using simulation starting timestamp: " + std::to_string(Global.starting_timestamp), logtype::generic); + // the command line gives the name lowercased, the file may be named in another case, as a scenery made on Windows; + // the scenery is saved back to its own file then + if (auto const cased{FileCased(Global.asCurrentSceneryPath + Global.SceneryFile)}; cased.size() > Global.asCurrentSceneryPath.size()) + Global.SceneryFile = cased.substr(Global.asCurrentSceneryPath.size()); Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" ); WriteLog( "\nLoading scenario \"" + Global.SceneryFile + "\"..." ); timestart = std::chrono::system_clock::now(); - state = simulation::State.deserialize_begin(Global.SceneryFile); + try { + state = simulation::State.deserialize_begin(Global.SceneryFile); + } + catch (std::exception const &e) { + ErrorLog("Bad init: scenario \"" + Global.SceneryFile + "\" couldn't be opened (" + e.what() + ")"); + state.reset(); + Global.SceneryFile.clear(); + Application.pop_mode(); + Application.push_mode(eu07_application::mode::launcher); + return true; + } } try { diff --git a/utilities/parser.cpp b/utilities/parser.cpp index b64aee39b..ee5a9ce5b 100644 --- a/utilities/parser.cpp +++ b/utilities/parser.cpp @@ -63,6 +63,15 @@ cParser::cParser(std::string const &Stream, buffertype const Type, std::string P // the file is read in one go, tokens are then extracted straight from the memory std::ifstream file(Path, std::ios_base::binary); if (file.fail()) + { + // named in another case than it's on the disk + if (auto const cased{FileCased(Path)}; false == cased.empty()) + { + file.clear(); + file.open(cased, std::ios_base::binary); + } + } + if (file.fail()) { mFailBit = true; } diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index 02608262b..0c18e0027 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -471,6 +471,20 @@ bool FileExists(std::string const &Filename) return std::filesystem::exists(Filename); } +std::string FileCased(std::string const &Filename) +{ + std::error_code error; + if (std::filesystem::exists(Filename, error)) + return Filename; + std::filesystem::path const path{Filename}; + auto const directory{path.has_parent_path() ? path.parent_path() : std::filesystem::path{"."}}; + auto const wanted{ToLower(path.filename().string())}; + for (std::filesystem::directory_iterator entry{directory, error}, end; entry != end; entry.increment(error)) + if (ToLower(entry->path().filename().string()) == wanted) + return path.has_parent_path() ? (directory / entry->path().filename()).string() : entry->path().filename().string(); + return {}; +} + std::pair FileExists(std::vector const &Names, std::vector const &Extensions) { diff --git a/utilities/utilities.h b/utilities/utilities.h index 5392ba6c5..d98b7bc9a 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -217,6 +217,9 @@ template bool extract_value(Type_ &Variable, std::string const template <> bool extract_value(bool &Variable, std::string const &Key, std::string const &Input, std::string const &Default); bool FileExists(std::string const &Filename); +// the file as it's named on the disk when only the case of its name differs, as in the sceneries made on Windows; empty when +// there's none +std::string FileCased(std::string const &Filename); std::pair FileExists(std::vector const &Names, std::vector const &Extensions); From 0d69059448b84d9b545ce0a4812c823fc7f60ec9 Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 3 Nov 2026 21:45:58 +0100 Subject: [PATCH 3/7] fix(launcher): the scans of the sceneries and the vehicles go past folders they can't read, a vehicle dropped without its skin set isn't added A folder without the permission to read it or a name that can't be converted ended the scan with an exception. Ctrl+Delete with no trainset selected, a dropped skin whose vehicle is gone, and a description with a % in it no longer crash the launcher. --- launcher/scenery_list.cpp | 28 ++++++++++++++++------------ launcher/scenery_scanner.cpp | 16 ++++++++++++---- launcher/textures_scanner.cpp | 9 +++++---- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/launcher/scenery_list.cpp b/launcher/scenery_list.cpp index d95c962b5..195b0e2c3 100644 --- a/launcher/scenery_list.cpp +++ b/launcher/scenery_list.cpp @@ -23,7 +23,7 @@ bool ui::scenerylist_panel::on_key(int key, int action) if (it == keyboard.keytonamemap.end()) return false; - if (Global.ctrlState && it->second == "delete") + if (Global.ctrlState && it->second == "delete" && selected_trainset != nullptr) purge_selected_trainset(); return true; @@ -117,7 +117,7 @@ void ui::scenerylist_panel::draw_launch_box() { ImGui::NextColumn(); - ImGui::TextWrapped(selected_trainset->description.c_str()); + ImGui::TextWrapped("%s", selected_trainset->description.c_str()); ImGui::Checkbox(STR_C("Trainset turned around"), &selected_trainset->reversed); if (ImGui::IsItemHovered()) @@ -283,7 +283,8 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset) draw_droptarget(trainset, position++); ImGui::SameLine(15.0f); - for (auto &dyn_desc : trainset.vehicles) { + for (std::size_t index = 0; index < trainset.vehicles.size(); ++index) { + auto &dyn_desc = trainset.vehicles[index]; deferred_image *mini = nullptr; if (dyn_desc.skin && dyn_desc.skin->mini.get() != deferred_image::invalid_id) @@ -345,16 +346,19 @@ void ui::scenerylist_panel::draw_droptarget(trainset_desc &trainset, int positio if (payload) { skin_set *ptr = *reinterpret_cast(payload->Data); std::shared_ptr skin; - for (auto &s : ptr->vehicle.lock()->matching_skinsets) - if (s.get() == ptr) - skin = s; + if (auto const vehicle = ptr->vehicle.lock()) + for (auto &s : vehicle->matching_skinsets) + if (s.get() == ptr) + skin = s; - trainset.vehicles.emplace(trainset.vehicles.begin() + position); - dynamic_desc &desc = trainset.vehicles[position]; + if (skin) { + trainset.vehicles.emplace(trainset.vehicles.begin() + position); + dynamic_desc &desc = trainset.vehicles[position]; - desc.name = skin->skin + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); - desc.vehicle = skin->vehicle.lock(); - desc.skin = skin; + desc.name = skin->skin + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); + desc.vehicle = skin->vehicle.lock(); + desc.skin = skin; + } } payload = ImGui::AcceptDragDropPayload("vehicle_set"); @@ -368,7 +372,7 @@ void ui::scenerylist_panel::draw_droptarget(trainset_desc &trainset, int positio if (moved->position < position) offset = -1; } else { - desc_copy.name = desc_copy.skin->skin + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); + desc_copy.name = (desc_copy.skin ? desc_copy.skin->skin : desc_copy.name) + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); } trainset.vehicles.insert(trainset.vehicles.begin() + position + offset, desc_copy); diff --git a/launcher/scenery_scanner.cpp b/launcher/scenery_scanner.cpp index bcd18ac90..5f6a6d2f9 100644 --- a/launcher/scenery_scanner.cpp +++ b/launcher/scenery_scanner.cpp @@ -10,13 +10,21 @@ scenery_scanner::scenery_scanner(ui::vehicles_bank &bank) void scenery_scanner::scan() { - for (auto &f : std::filesystem::directory_iterator("scenery")) { - std::filesystem::path path(std::filesystem::relative(f.path(), "scenery/")); + std::error_code error; + for (std::filesystem::directory_iterator f{"scenery", std::filesystem::directory_options::skip_permission_denied, error}, end; f != end; f.increment(error)) { + std::filesystem::path path(std::filesystem::relative(f->path(), "scenery/", error)); + std::string name; + try { + name = path.string(); + } + catch (std::exception const &) { + continue; + } - if (*path.filename().string().begin() == '$') + if (name.empty() || path.filename().string().front() == '$') continue; - if (path.string().ends_with(".scn")) + if (name.ends_with(".scn")) scan_scn(path); } diff --git a/launcher/textures_scanner.cpp b/launcher/textures_scanner.cpp index 524a23ca6..741f978ca 100644 --- a/launcher/textures_scanner.cpp +++ b/launcher/textures_scanner.cpp @@ -4,12 +4,13 @@ void ui::vehicles_bank::scan_textures() { - for (auto &f : std::filesystem::recursive_directory_iterator("dynamic")) { - if (f.path().filename() != "textures.txt") + std::error_code error; + for (std::filesystem::recursive_directory_iterator f{"dynamic", std::filesystem::directory_options::skip_permission_denied, error}, end; f != end; f.increment(error)) { + if (f->path().filename() != "textures.txt") continue; - std::fstream stream(f.path(), std::ios_base::binary | std::ios_base::in); - ctx_path = std::filesystem::relative(f.path().parent_path(), "dynamic/"); + std::fstream stream(f->path(), std::ios_base::binary | std::ios_base::in); + ctx_path = std::filesystem::relative(f->path().parent_path(), "dynamic/", error); std::string line; while (std::getline(stream, line)) From e230d7b902831b9e6fdac4eebffd73087fb4b6cd Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 3 Nov 2026 21:45:58 +0100 Subject: [PATCH 4/7] fix: a deleted model is no longer the picked node, an orthophoto tile isn't swept as a grey stub, the ED brake arrays freed Deleting a model in the editor left the renderer holding it as the picked node, the next pick used freed memory: the renderer forgets it now (Forget_Node). A texture filled straight from memory (internalsrc:) can't be read again, released by the garbage sweep it came back grey; it stays now, and the units bound with the old name are rebound. The arrays of the ED brake force sharing are vectors, they leaked once per update. The scene hierarchy and the editor menu items go through the translation. Python caches ignored. --- .gitignore | 2 ++ application/uilayer.cpp | 10 +++++----- model/Texture.cpp | 10 ++++++++++ rendering/opengl33renderer.cpp | 7 +++++++ rendering/opengl33renderer.h | 2 ++ rendering/openglrenderer.cpp | 9 +++++++++ rendering/openglrenderer.h | 2 ++ rendering/renderer.h | 2 ++ simulation/simulation.cpp | 4 ++++ vehicle/DynObj.cpp | 16 ++++------------ 10 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 5186ceaee..70a6d1c68 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ vcpkg.json # Local Python (setup.bat / CMake) for embedding tools/python314/ +__pycache__/ +*.pyc diff --git a/application/uilayer.cpp b/application/uilayer.cpp index 2dd771bdd..d8417e4fe 100644 --- a/application/uilayer.cpp +++ b/application/uilayer.cpp @@ -567,7 +567,7 @@ void ui_layer::render_hierarchy(){ // fitted to the contents when it first appears; no auto resize, which a docked window can't do ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_FirstUseEver); ImGui::Begin((std::string(STR_C("Scene Hierarchy")) + "###scenehierarchy").c_str(), &m_editor_hierarchy); - ImGui::Text("Registered nodes: %zu", scene::Hierarchy.size()); + ImGui::Text(STR_C("Registered nodes: %zu"), scene::Hierarchy.size()); ImGui::BeginChild("hierarchy_list", ImVec2(500, 300), true); for (auto &entry : scene::Hierarchy) @@ -598,7 +598,7 @@ void ui_layer::render_hierarchy(){ if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("UUID: %s\nNode type: %s", uuid.c_str(), typeid(*node).name()); + ImGui::SetTooltip(STR_C("UUID: %s\nNode type: %s"),uuid.c_str(), typeid(*node).name()); } } } @@ -708,14 +708,14 @@ void ui_layer::render_menu_contents() GfxRenderer->Debug_Ui_State(ret); } if(EditorModeFlag){ - ImGui::MenuItem("Hierarchy", nullptr, &m_editor_hierarchy); + ImGui::MenuItem(STR_C("Scene Hierarchy"), nullptr, &m_editor_hierarchy); bool change_history_enabled = editor_mode::change_history(); - if (ImGui::MenuItem("Change History", nullptr, &change_history_enabled)) + if (ImGui::MenuItem(STR_C("Change History"), nullptr, &change_history_enabled)) { editor_mode::set_change_history(change_history_enabled); } bool settings_open = editor_mode::settings_open(); - if (ImGui::MenuItem("Editor Settings", nullptr, &settings_open)) + if (ImGui::MenuItem(STR_C("Editor Settings"), nullptr, &settings_open)) { editor_mode::set_settings_open(settings_open); } diff --git a/model/Texture.cpp b/model/Texture.cpp index 136846a33..4a7b24792 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -706,6 +706,16 @@ void opengl_texture::update_from_memory(size_t width, size_t height, const uint8 if (glGenerateMipmap) glGenerateMipmap(target); glFlush(); + // the content given straight to the gpu can't be read again: released by the garbage sweep, the texture would come back as + // the grey stub + if (type == "internalsrc:") + { + is_static = true; + is_ready = true; + } + // the name may be a new one, and the unit active took the texture past the unit cache + for (auto &unit : units) + unit = -1; } void diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index d20454c35..a5d1f3894 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -5410,6 +5410,13 @@ void opengl33_renderer::Pick_Node_Callback(std::functionis_busy()) diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 64574420f..0f17ede7f 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -108,6 +108,8 @@ class opengl33_renderer : public gfx_renderer { Pick_Control() const override { return m_pickcontrolitem; } scene::basic_node const * Pick_Node() const override { return m_picksceneryitem; } + void + Forget_Node( scene::basic_node const *Node ) override; glm::dvec3 Mouse_Position() const override { return m_worldmousecoordinates; } bool diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index 59152c64d..864d84141 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -1888,6 +1888,15 @@ opengl_renderer::Pick_Node_Callback( std::function m_node_pick_requests.emplace_back( Callback ); } +void +opengl_renderer::Forget_Node( scene::basic_node const *Node ) { + + if( m_picksceneryitem == Node ) { + m_picksceneryitem = nullptr; + } + std::replace_if( m_picksceneryitems.begin(), m_picksceneryitems.end(), [Node]( scene::basic_node const *Item ) { return Item == Node; }, nullptr ); +} + void opengl_renderer::Render( scene::basic_region *Region ) { diff --git a/rendering/openglrenderer.h b/rendering/openglrenderer.h index 78fffa40b..ada032808 100644 --- a/rendering/openglrenderer.h +++ b/rendering/openglrenderer.h @@ -103,6 +103,8 @@ public: Pick_Control() const override { return m_pickcontrolitem; } scene::basic_node const * Pick_Node() const override { return m_picksceneryitem; } + void + Forget_Node( scene::basic_node const *Node ) override; glm::dvec3 Mouse_Position() const override { return m_worldmousecoordinates; } glm::mat4 diff --git a/rendering/renderer.h b/rendering/renderer.h index 0b9eb60f4..e9dc8a47c 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -90,6 +90,8 @@ public: virtual void Pick_Node_Callback( std::function Callback ) = 0; virtual auto Pick_Control() const -> TSubModel const * = 0; virtual auto Pick_Node() const -> scene::basic_node const * = 0; + // a node deleted from the scene is no longer given as picked + virtual void Forget_Node( scene::basic_node const *Node ) {} virtual auto Mouse_Position() const -> glm::dvec3 = 0; virtual auto Mouse_Hit() const -> bool { return true; } diff --git a/simulation/simulation.cpp b/simulation/simulation.cpp index 70f9e3074..ea2177170 100644 --- a/simulation/simulation.cpp +++ b/simulation/simulation.cpp @@ -30,6 +30,7 @@ http://mozilla.org/MPL/2.0/. #include "scene/scenelayers.h" #include "vehicle/Train.h" #include "application/application.h" +#include "rendering/renderer.h" #include "utilities/Logs.h" #include "vehicle/Driver.h" @@ -561,6 +562,9 @@ std::pair state_manager::rebuild_include(scene::instance_handle const } void state_manager::delete_model(TAnimModel *model) { + if (GfxRenderer) { + GfxRenderer->Forget_Node(model); + } scene::Layers.count(model->layer(), scene::layer_item::model, -1); scene::Layers.forget(model); Region->erase(model); diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index c6793e454..2309c433b 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -3381,15 +3381,12 @@ bool TDynamicObject::Update(double dt, double dt1) && MoverParameters->eimc[eimc_p_abed] < 0.001) FzadED = 0; //pętla bezpieczeństwa - bez ED auto const FzadPN = Fzad - FrED * MoverParameters->MED_FrED_factor; - //np = 0; - // BUG: likely memory leak, allocation per inner loop, deleted only once outside - // TODO: sort this shit out - bool* PrzekrF = new bool[np]; + std::vector PrzekrF( np ); float nPrzekrF = 0; bool test = true; - float* FzED = new float[np]; - float* FzEP = new float[np]; - float* FmaxEP = new float[np]; + std::vector FzED( np ); + std::vector FzEP( np ); + std::vector FmaxEP( np ); // 3. ustaw pojazdom sile hamowania ED // - proporcjonalnie do mozliwosci @@ -3548,11 +3545,6 @@ bool TDynamicObject::Update(double dt, double dt1) } - delete[] PrzekrF; - delete[] FzED; - delete[] FzEP; - delete[] FmaxEP; - MED_oldFED = FzadED; } From ed0ff36a6994243d939160807c651376adecc7d2 Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 3 Nov 2026 21:53:52 +0100 Subject: [PATCH 5/7] fix(app): a scenario failing as it loads goes back to the launcher too, the files of other cases looked for only off Windows A scenario failing past its opening popped the loader mode and went on to pop it again and push the driver mode with the scenery half loaded; it goes back to the launcher as one that can't be opened. FileCased() goes through the folder only where the file system tells the case apart: on Windows a missing file was looked for in its whole folder for nothing, and a name outside the code page threw from the parser. The Discord thread sleeps a quarter of a second outside the driver mode, not to hold the closing up. --- application/application.cpp | 2 +- application/scenarioloadermode.cpp | 5 +++++ application/uilayer.cpp | 2 +- utilities/utilities.cpp | 7 +++++++ utilities/utilities.h | 2 +- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/application/application.cpp b/application/application.cpp index 56a83c29c..33f5c8a0e 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -272,7 +272,7 @@ void eu07_application::DiscordRPCService() if (currentMode != mode::driver) { // the editor and the other modes: nothing to tell, without spinning a core on it - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); continue; } diff --git a/application/scenarioloadermode.cpp b/application/scenarioloadermode.cpp index 7afe0a442..eaa034f7e 100644 --- a/application/scenarioloadermode.cpp +++ b/application/scenarioloadermode.cpp @@ -67,8 +67,13 @@ bool scenarioloader_mode::update() { return true; } catch (invalid_scenery_exception &e) { + // back to the launcher as when the scenario can't be opened, not on to the driver mode with the scenery half loaded ErrorLog( "Bad init: scenario loading failed" ); + state.reset(); + Global.SceneryFile.clear(); Application.pop_mode(); + Application.push_mode(eu07_application::mode::launcher); + return true; } // the loading state holds the scenario file open, which on some systems keeps the file from being replaced // when the scenery is saved in the editor. it'd also get in the way of another load diff --git a/application/uilayer.cpp b/application/uilayer.cpp index d8417e4fe..a188d4f0b 100644 --- a/application/uilayer.cpp +++ b/application/uilayer.cpp @@ -598,7 +598,7 @@ void ui_layer::render_hierarchy(){ if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(STR_C("UUID: %s\nNode type: %s"),uuid.c_str(), typeid(*node).name()); + ImGui::SetTooltip(STR_C("UUID: %s\nNode type: %s"), uuid.c_str(), typeid(*node).name()); } } } diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index 0c18e0027..b25c6b31d 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -476,6 +476,12 @@ std::string FileCased(std::string const &Filename) std::error_code error; if (std::filesystem::exists(Filename, error)) return Filename; +#ifdef _WIN32 + // the names there are found in any case already: no folder to go through for every file missing, nor names outside of the + // code page to throw on + return {}; +#else + // only the name of the file itself, a folder named in another case isn't found std::filesystem::path const path{Filename}; auto const directory{path.has_parent_path() ? path.parent_path() : std::filesystem::path{"."}}; auto const wanted{ToLower(path.filename().string())}; @@ -483,6 +489,7 @@ std::string FileCased(std::string const &Filename) if (ToLower(entry->path().filename().string()) == wanted) return path.has_parent_path() ? (directory / entry->path().filename()).string() : entry->path().filename().string(); return {}; +#endif } std::pair FileExists(std::vector const &Names, std::vector const &Extensions) diff --git a/utilities/utilities.h b/utilities/utilities.h index d98b7bc9a..f16431dde 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -218,7 +218,7 @@ template <> bool extract_value(bool &Variable, std::string const &Key, std::stri bool FileExists(std::string const &Filename); // the file as it's named on the disk when only the case of its name differs, as in the sceneries made on Windows; empty when -// there's none +// there's none. the folders have to be named in their case; on Windows, finding the names in any case itself, the file given or none std::string FileCased(std::string const &Filename); std::pair FileExists(std::vector const &Names, std::vector const &Extensions); From 85233160069bd0d5b79dcb34800363431eebb61e Mon Sep 17 00:00:00 2001 From: maj00r Date: Tue, 3 Nov 2026 23:03:52 +0100 Subject: [PATCH 6/7] fix(track): a tributary loads as a river of its first path keeping the second, a track of an unknown type is left out, where both made the simulator fall over as it loaded On master neither got a segment: update_location() dereferenced the missing one of a track of an unknown type, and a tributary went to Switch(0), which logged "Cannot switch normal track", with no segment under it either. A tributary is now a river of its first path. Its second path is kept in m_paths, isn't drawn nor run on, and is written back with the type "tributary". A track of an unknown type is logged and left out by the loader; as it is no node, its text stays in the file when the editor saves the scenery. --- simulation/simulationstateserializer.cpp | 6 ++++++ world/Track.cpp | 18 ++++++++++++++++-- world/Track.h | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/simulation/simulationstateserializer.cpp b/simulation/simulationstateserializer.cpp index 1ff771d1d..78955c828 100644 --- a/simulation/simulationstateserializer.cpp +++ b/simulation/simulationstateserializer.cpp @@ -543,6 +543,12 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch else if( nodedata.type == "track" ) { auto *path { deserialize_path( Input, Scratchpad, nodedata ) }; + if( path->eType == tt_Unknown ) { + // with no path made it can't be joined nor drawn, and nothing could run on it + ErrorLog( "Bad scenario: track \"" + path->name() + "\" of an unknown type defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ") left out" ); + delete path; + return; + } // duplicates of named tracks are currently experimentally allowed if( false == simulation::Paths.insert( path ) ) { ErrorLog( "Bad scenario: duplicate track name \"" + path->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); diff --git a/world/Track.cpp b/world/Track.cpp index f1a29798c..14998ce7c 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -427,11 +427,15 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) } else if (str == "tributary") { - eType = tt_Tributary; + // dopływ: no path of it was ever made, the simulator falling over it as it loaded. a river of its first path now, the + // second one kept and written back + ErrorLog("Bad track: tributary \"" + m_name + "\" in file \"" + parser->Name() + "\" (line " + std::to_string(parser->Line()) + ") is drawn and run on as a river of its first path"); + eType = tt_Normal; iCategoryFlag = 4; + m_tributary = true; } else - eType = tt_Unknown; + eType = tt_Unknown; // left out by the loader if (Global.iWriteLogEnabled & 4) WriteLog(str); parser->getTokens(4); @@ -517,6 +521,11 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) break; } } + if( m_tributary ) { + // the second path of a tributary, kept to be written back + m_paths.emplace_back(); + m_paths.back().deserialize( *parser, pOrigin ); + } init_segments( true ); @@ -681,6 +690,10 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) m_name = m_name.substr(0, i - 1); // usunięcie z nazwy } + if( CurrentSegment() == nullptr ) { + // of an unknown type, no path of it is made + return; + } update_location(); // sleeper transforms are baked later in create_geometry(), once the owning cell has // assigned this track its m_origin (otherwise the local-space matrices would be relative @@ -2314,6 +2327,7 @@ TTrack::export_as_text_( std::ostream &Output ) const { // type Output << ( eType == tt_Normal ? ( + m_tributary ? "tributary" : iCategoryFlag == 1 ? "normal" : iCategoryFlag == 2 ? "road" : iCategoryFlag == 4 ? "river" : diff --git a/world/Track.h b/world/Track.h index 72a6b7ff4..5952da6a7 100644 --- a/world/Track.h +++ b/world/Track.h @@ -187,6 +187,7 @@ public: std::vector m_paths; // source data for owned paths bool m_editorremoved { false }; + bool m_tributary { false }; // loaded as a tributary: a river of its first path, the second one kept to be written back scene::basic_node *m_road { nullptr }; // road or road junction which generated this path. such path is one-way, and isn't saved nor edited on its own std::array m_routeweights { 0.f, 0.f, 0.f }; // odds of each route through a crossroads being picked by a driver, by the number of the route. all zero: even odds int iterate_stamp = 0; From a6778c9fe24f8bee37ce045cf99e797a943da0d0 Mon Sep 17 00:00:00 2001 From: maj00r Date: Fri, 6 Nov 2026 19:54:54 +0100 Subject: [PATCH 7/7] refactor: the Sonar remarks on the small fixes taken care of - the log stops through a flag kept in a function, not a global one - the messages are put together with std::format - a vehicle dropped on a trainset in the launcher goes in once the trainset is drawn, so the vehicles are gone through with a plain loop again; the drop split into a new vehicle and a moved one - the scenario that can't be opened is caught as the runtime error it is, a file name outside of the code page as the system error it is - a track of an unknown type is left out through the pointer owning it - the keyword of a track type written out by a function of its own - the picked node forgotten with ranges::replace_if, the empty Forget_Node of the base renderer told why it is empty - FileCased takes the path it works on --- application/application.cpp | 4 +- application/scenarioloadermode.cpp | 7 +- launcher/scenery_list.cpp | 103 +++++++++++++++-------- launcher/scenery_list.h | 4 + launcher/scenery_scanner.cpp | 5 +- rendering/opengl33renderer.cpp | 2 +- rendering/openglrenderer.cpp | 2 +- rendering/renderer.h | 4 +- simulation/simulationstateserializer.cpp | 12 +-- utilities/Logs.cpp | 15 +++- utilities/utilities.cpp | 11 ++- utilities/utilities.h | 2 +- world/Track.cpp | 42 ++++++--- world/Track.h | 2 + 14 files changed, 140 insertions(+), 75 deletions(-) diff --git a/application/application.cpp b/application/application.cpp index 33f5c8a0e..9b7b82853 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -696,8 +696,8 @@ void eu07_application::exit() Global.applicationQuitOrder = true; // each step of the closing in the log, to tell from it where a closing that hangs stops auto const closing{std::chrono::steady_clock::now()}; - auto const step = [&](char const *What) { - WriteLog(std::string{"Closing: "} + What + " at " + std::to_string(std::chrono::duration_cast(std::chrono::steady_clock::now() - closing).count()) + " ms"); + auto const step = [closing](char const *What) { + WriteLog(std::format("Closing: {} at {} ms", What, std::chrono::duration_cast(std::chrono::steady_clock::now() - closing).count())); }; step("the modes"); for (auto &mode : m_modes) diff --git a/application/scenarioloadermode.cpp b/application/scenarioloadermode.cpp index eaa034f7e..f8f06ac93 100644 --- a/application/scenarioloadermode.cpp +++ b/application/scenarioloadermode.cpp @@ -21,6 +21,8 @@ http://mozilla.org/MPL/2.0/. #include "utilities/translation.h" #include "audio/audiorenderer.h" +#include + scenarioloader_mode::scenarioloader_mode() { m_userinterface = std::make_shared(); } @@ -52,8 +54,9 @@ bool scenarioloader_mode::update() { try { state = simulation::State.deserialize_begin(Global.SceneryFile); } - catch (std::exception const &e) { - ErrorLog("Bad init: scenario \"" + Global.SceneryFile + "\" couldn't be opened (" + e.what() + ")"); + // the file it can't read, as the parser and the scenery opening it report it + catch (std::runtime_error const &e) { + ErrorLog(std::format("Bad init: scenario \"{}\" couldn't be opened ({})", Global.SceneryFile, e.what())); state.reset(); Global.SceneryFile.clear(); Application.pop_mode(); diff --git a/launcher/scenery_list.cpp b/launcher/scenery_list.cpp index 195b0e2c3..6a57db54b 100644 --- a/launcher/scenery_list.cpp +++ b/launcher/scenery_list.cpp @@ -8,6 +8,28 @@ #include "utilities/translation.h" #include +#include + +namespace +{ + +// the skin of the bank of vehicles as it's shared, the one a skin dragged out of the bank points at; none for one gone since +std::shared_ptr shared_skin(ui::skin_set const *Skin) +{ + auto const vehicle{Skin->vehicle.lock()}; + if (vehicle == nullptr) + return {}; + auto const found{std::ranges::find_if(vehicle->matching_skinsets, [Skin](std::shared_ptr const &Set) { return Set.get() == Skin; })}; + return found != vehicle->matching_skinsets.end() ? *found : nullptr; +} + +// the name of a vehicle put in a trainset, with a number of its own +std::string numbered(std::string const &Name) +{ + return std::format("{}_{}", Name, static_cast(LocalRandom(0.0, 10000000.0))); +} + +} // namespace ui::scenerylist_panel::scenerylist_panel(scenery_scanner &scanner) : ui_panel(STR("Scenario list"), false), scanner(scanner), placeholder_mini("textures/mini/other") @@ -283,8 +305,8 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset) draw_droptarget(trainset, position++); ImGui::SameLine(15.0f); - for (std::size_t index = 0; index < trainset.vehicles.size(); ++index) { - auto &dyn_desc = trainset.vehicles[index]; + // a vehicle dropped between them goes in once they're all drawn, so they're gone through unchanged + for (auto &dyn_desc : trainset.vehicles) { deferred_image *mini = nullptr; if (dyn_desc.skin && dyn_desc.skin->mini.get() != deferred_image::invalid_id) @@ -333,6 +355,10 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset) ImGui::PopID(); } + if (m_drop) { + m_drop(); + m_drop = nullptr; + } ImGui::NewLine(); ImGui::PopID(); @@ -341,44 +367,47 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset) void ui::scenerylist_panel::draw_droptarget(trainset_desc &trainset, int position) { ImGui::Dummy(ImVec2(15, 30)); - if (ImGui::BeginDragDropTarget()) { - const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("vehicle_pure"); - if (payload) { - skin_set *ptr = *reinterpret_cast(payload->Data); - std::shared_ptr skin; - if (auto const vehicle = ptr->vehicle.lock()) - for (auto &s : vehicle->matching_skinsets) - if (s.get() == ptr) - skin = s; + if (false == ImGui::BeginDragDropTarget()) + return; + if (auto const *payload{ImGui::AcceptDragDropPayload("vehicle_pure")}) + drop_new_vehicle(trainset, position, *static_cast(payload->Data)); + if (auto const *payload{ImGui::AcceptDragDropPayload("vehicle_set")}) + drop_moved_vehicle(trainset, position, *static_cast(payload->Data)); + ImGui::EndDragDropTarget(); +} - if (skin) { - trainset.vehicles.emplace(trainset.vehicles.begin() + position); - dynamic_desc &desc = trainset.vehicles[position]; +// a vehicle of the bank dropped on a gap of a trainset, put in there as a new one of it +void ui::scenerylist_panel::drop_new_vehicle(trainset_desc &trainset, int const position, skin_set const *Skin) +{ + auto skin{shared_skin(Skin)}; + if (skin == nullptr) + return; + m_drop = [&trainset, position, skin] { + auto &desc{*trainset.vehicles.emplace(trainset.vehicles.begin() + position)}; + desc.name = numbered(skin->skin); + desc.vehicle = skin->vehicle.lock(); + desc.skin = skin; + }; +} - desc.name = skin->skin + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); - desc.vehicle = skin->vehicle.lock(); - desc.skin = skin; - } +// a vehicle of a trainset dropped on a gap of it or of another one: moved there within it, from another one copied in with a +// name of its own +void ui::scenerylist_panel::drop_moved_vehicle(trainset_desc &trainset, int const position, vehicle_moved const &Moved) +{ + auto copy{Moved.dynamic}; + auto &source{Moved.source}; + auto const from{Moved.position}; + if (&trainset != &source) + copy.name = numbered(copy.skin ? copy.skin->skin : copy.name); + m_drop = [&trainset, &source, from, position, copy] { + auto offset{0}; + if (&trainset == &source) { + trainset.vehicles.erase(trainset.vehicles.begin() + from); + if (from < position) + offset = -1; } - - payload = ImGui::AcceptDragDropPayload("vehicle_set"); - if (payload) { - vehicle_moved *moved = reinterpret_cast(payload->Data); - - dynamic_desc desc_copy = moved->dynamic; - int offset = 0; - if (&trainset == &moved->source) { - trainset.vehicles.erase(trainset.vehicles.begin() + moved->position); - if (moved->position < position) - offset = -1; - } else { - desc_copy.name = (desc_copy.skin ? desc_copy.skin->skin : desc_copy.name) + "_" + std::to_string((int)LocalRandom(0.0, 10000000.0)); - } - - trainset.vehicles.insert(trainset.vehicles.begin() + position + offset, desc_copy); - } - ImGui::EndDragDropTarget(); - } + trainset.vehicles.insert(trainset.vehicles.begin() + position + offset, copy); + }; } void ui::scenerylist_panel::purge_selected_trainset() diff --git a/launcher/scenery_list.h b/launcher/scenery_list.h index 957f1b5ee..0bc91c452 100644 --- a/launcher/scenery_list.h +++ b/launcher/scenery_list.h @@ -54,6 +54,8 @@ private: scenery_desc *selected_scenery = nullptr; trainset_desc *selected_trainset = nullptr; deferred_image placeholder_mini; + // a vehicle dropped on a gap between the vehicles of a trainset: put in there once they're all drawn, not while they are + std::function m_drop; void draw_scenery_list(); void draw_scenery_info(); @@ -67,6 +69,8 @@ private: void open_link(const std::string &link); void draw_trainset(trainset_desc &trainset); void draw_droptarget(trainset_desc &trainset, int position); + void drop_new_vehicle(trainset_desc &trainset, int position, skin_set const *Skin); + void drop_moved_vehicle(trainset_desc &trainset, int position, vehicle_moved const &Moved); void purge_selected_trainset(); }; } // namespace ui diff --git a/launcher/scenery_scanner.cpp b/launcher/scenery_scanner.cpp index 5f6a6d2f9..6e258425e 100644 --- a/launcher/scenery_scanner.cpp +++ b/launcher/scenery_scanner.cpp @@ -2,6 +2,8 @@ #include "scenery_scanner.h" #include "utilities/Logs.h" +#include + scenery_scanner::scenery_scanner(ui::vehicles_bank &bank) : bank(bank) { @@ -17,7 +19,8 @@ void scenery_scanner::scan() try { name = path.string(); } - catch (std::exception const &) { + catch (std::system_error const &) { + // a name outside of the code page, which the path can't give as a string on Windows continue; } diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index a5d1f3894..a8a1b2139 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -5414,7 +5414,7 @@ void opengl33_renderer::Forget_Node(scene::basic_node const *Node) { if (m_picksceneryitem == Node) m_picksceneryitem = nullptr; - std::replace_if(m_picksceneryitems.begin(), m_picksceneryitems.end(), [Node](scene::basic_node const *Item) { return Item == Node; }, nullptr); + std::ranges::replace_if(m_picksceneryitems, [Node](scene::basic_node const *Item) { return Item == Node; }, nullptr); } glm::dvec3 opengl33_renderer::Update_Mouse_Position() diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index 864d84141..6313aa55f 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -1894,7 +1894,7 @@ opengl_renderer::Forget_Node( scene::basic_node const *Node ) { if( m_picksceneryitem == Node ) { m_picksceneryitem = nullptr; } - std::replace_if( m_picksceneryitems.begin(), m_picksceneryitems.end(), [Node]( scene::basic_node const *Item ) { return Item == Node; }, nullptr ); + std::ranges::replace_if( m_picksceneryitems, [Node]( scene::basic_node const *Item ) { return Item == Node; }, nullptr ); } void diff --git a/rendering/renderer.h b/rendering/renderer.h index e9dc8a47c..5850e1754 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -91,7 +91,9 @@ public: virtual auto Pick_Control() const -> TSubModel const * = 0; virtual auto Pick_Node() const -> scene::basic_node const * = 0; // a node deleted from the scene is no longer given as picked - virtual void Forget_Node( scene::basic_node const *Node ) {} + virtual void Forget_Node( scene::basic_node const * /*Node*/ ) { + // a renderer which keeps no node picked has none to forget + } virtual auto Mouse_Position() const -> glm::dvec3 = 0; virtual auto Mouse_Hit() const -> bool { return true; } diff --git a/simulation/simulationstateserializer.cpp b/simulation/simulationstateserializer.cpp index 78955c828..664f3b955 100644 --- a/simulation/simulationstateserializer.cpp +++ b/simulation/simulationstateserializer.cpp @@ -36,6 +36,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/utilities.h" #include "editor/editorTerrainStreamer.hpp" +#include namespace simulation { @@ -542,13 +543,14 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch } else if( nodedata.type == "track" ) { - auto *path { deserialize_path( Input, Scratchpad, nodedata ) }; - if( path->eType == tt_Unknown ) { - // with no path made it can't be joined nor drawn, and nothing could run on it - ErrorLog( "Bad scenario: track \"" + path->name() + "\" of an unknown type defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ") left out" ); - delete path; + std::unique_ptr made { deserialize_path( Input, Scratchpad, nodedata ) }; + if( made->eType == tt_Unknown ) { + // with no path made it can't be joined nor drawn, and nothing could run on it: it goes with its holder + ErrorLog( std::format( "Bad scenario: track \"{}\" of an unknown type defined in file \"{}\" (line {}) left out", made->name(), Input.Name(), inputline ) ); return; } + // from here on the scene owns it + auto *path { made.release() }; // duplicates of named tracks are currently experimentally allowed if( false == simulation::Paths.insert( path ) ) { ErrorLog( "Bad scenario: duplicate track name \"" + path->name() + "\" defined in file \"" + Input.Name() + "\" (line " + std::to_string( inputline ) + ")" ); diff --git a/utilities/Logs.cpp b/utilities/Logs.cpp index 18020d6f1..38094d72e 100644 --- a/utilities/Logs.cpp +++ b/utilities/Logs.cpp @@ -73,12 +73,19 @@ std::deque ErrorStack; // lock for log stacks std::mutex logMutex; -// the log is written through the closing of the simulator, to the end -std::atomic logStop{false}; +namespace +{ +// the log is written through the closing of the simulator, to the end: set once the closing has nothing more to tell +std::atomic &log_stopped() +{ + static std::atomic stopped{false}; + return stopped; +} +} // namespace void StopLogService() { - logStop = true; + log_stopped() = true; } @@ -87,7 +94,7 @@ void LogService() // one more pass after the stop, so what came just before it gets written too for (bool last{false}; false == last;) { - last = logStop; + last = log_stopped(); { // --- Obsługa InfoStack --- while (!InfoStack.empty()) diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index b25c6b31d..cf9003f4d 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -471,23 +471,22 @@ bool FileExists(std::string const &Filename) return std::filesystem::exists(Filename); } -std::string FileCased(std::string const &Filename) +std::string FileCased(std::filesystem::path const &Filename) { std::error_code error; if (std::filesystem::exists(Filename, error)) - return Filename; + return Filename.string(); #ifdef _WIN32 // the names there are found in any case already: no folder to go through for every file missing, nor names outside of the // code page to throw on return {}; #else // only the name of the file itself, a folder named in another case isn't found - std::filesystem::path const path{Filename}; - auto const directory{path.has_parent_path() ? path.parent_path() : std::filesystem::path{"."}}; - auto const wanted{ToLower(path.filename().string())}; + auto const directory{Filename.has_parent_path() ? Filename.parent_path() : std::filesystem::path{"."}}; + auto const wanted{ToLower(Filename.filename().string())}; for (std::filesystem::directory_iterator entry{directory, error}, end; entry != end; entry.increment(error)) if (ToLower(entry->path().filename().string()) == wanted) - return path.has_parent_path() ? (directory / entry->path().filename()).string() : entry->path().filename().string(); + return Filename.has_parent_path() ? (directory / entry->path().filename()).string() : entry->path().filename().string(); return {}; #endif } diff --git a/utilities/utilities.h b/utilities/utilities.h index f16431dde..cc4056cae 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -219,7 +219,7 @@ template <> bool extract_value(bool &Variable, std::string const &Key, std::stri bool FileExists(std::string const &Filename); // the file as it's named on the disk when only the case of its name differs, as in the sceneries made on Windows; empty when // there's none. the folders have to be named in their case; on Windows, finding the names in any case itself, the file given or none -std::string FileCased(std::string const &Filename); +std::string FileCased(std::filesystem::path const &Filename); std::pair FileExists(std::vector const &Names, std::vector const &Extensions); diff --git a/world/Track.cpp b/world/Track.cpp index 14998ce7c..c6339a424 100644 --- a/world/Track.cpp +++ b/world/Track.cpp @@ -29,6 +29,8 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Logs.h" #include "rendering/renderer.h" +#include + // 101206 Ra: trapezoidalne drogi i tory // 110720 Ra: rozprucie zwrotnicy i odcinki izolowane @@ -429,7 +431,7 @@ void TTrack::Load(cParser *parser, glm::dvec3 const &pOrigin) { // dopływ: no path of it was ever made, the simulator falling over it as it loaded. a river of its first path now, the // second one kept and written back - ErrorLog("Bad track: tributary \"" + m_name + "\" in file \"" + parser->Name() + "\" (line " + std::to_string(parser->Line()) + ") is drawn and run on as a river of its first path"); + ErrorLog(std::format("Bad track: tributary \"{}\" in file \"{}\" (line {}) is drawn and run on as a river of its first path", m_name, parser->Name(), parser->Line())); eType = tt_Normal; iCategoryFlag = 4; m_tributary = true; @@ -2319,25 +2321,37 @@ TTrack::deserialize_( std::istream &Input ) { // TODO: implement } +// the keyword the track is written with, by its type and for a plain one by what runs on it +char const * +TTrack::type_keyword() const { + + if( eType == tt_Normal && m_tributary ) { + return "tributary"; + } + if( eType == tt_Normal ) { + switch( iCategoryFlag ) { + case 1: { return "normal"; } + case 2: { return "road"; } + case 4: { return "river"; } + default: { return "none"; } + } + } + switch( eType ) { + case tt_Switch: { return "switch"; } + case tt_Cross: { return "cross"; } + case tt_Table: { return "turn"; } + case tt_Tributary: { return "tributary"; } + default: { return "none"; } + } +} + // export() subclass details, sends basic content of the class in legacy (text) format to provided stream void TTrack::export_as_text_( std::ostream &Output ) const { // header Output << "track "; // type - Output << ( - eType == tt_Normal ? ( - m_tributary ? "tributary" : - iCategoryFlag == 1 ? "normal" : - iCategoryFlag == 2 ? "road" : - iCategoryFlag == 4 ? "river" : - "none" ) : - eType == tt_Switch ? "switch" : - eType == tt_Cross ? "cross" : - eType == tt_Table ? "turn" : - eType == tt_Tributary ? "tributary" : - "none" ) - << ' '; + Output << type_keyword() << ' '; // basic attributes Output << Length() << ' ' diff --git a/world/Track.h b/world/Track.h index 5952da6a7..70431072a 100644 --- a/world/Track.h +++ b/world/Track.h @@ -358,6 +358,8 @@ private: void deserialize_( std::istream &Input ); // export() subclass details, sends basic content of the class in legacy (text) format to provided stream void export_as_text_( std::ostream &Output ) const; + // the keyword the track is written with, by its type and for a plain one by what runs on it + char const *type_keyword() const; // locates specified profile in the profile database, potentially loading it from a file static std::pair fetch_track_rail_profile( std::string const &Profile ); // loads content of specified file and converts it into a vertex array