mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-08 02:58:37 +01:00
Merge pull request #244 from MaSzyna-EU07/split/1-small-fixes
Split/1 small fixes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,3 +24,5 @@ vcpkg.json
|
||||
|
||||
# Local Python (setup.bat / CMake) for embedding
|
||||
tools/python314/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
@@ -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(250));
|
||||
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 = [closing](char const *What) {
|
||||
WriteLog(std::format("Closing: {} at {} ms", What, std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - closing).count()));
|
||||
};
|
||||
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())
|
||||
{
|
||||
|
||||
@@ -21,6 +21,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "utilities/translation.h"
|
||||
#include "audio/audiorenderer.h"
|
||||
|
||||
#include <format>
|
||||
|
||||
scenarioloader_mode::scenarioloader_mode() {
|
||||
m_userinterface = std::make_shared<scenarioloader_ui>();
|
||||
}
|
||||
@@ -41,11 +43,26 @@ 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);
|
||||
}
|
||||
// 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();
|
||||
Application.push_mode(eu07_application::mode::launcher);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -53,8 +70,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
|
||||
|
||||
@@ -569,7 +569,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)
|
||||
@@ -600,7 +600,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -710,14 +710,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);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,28 @@
|
||||
#include "utilities/translation.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
|
||||
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<ui::skin_set> 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<ui::skin_set> 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<int>(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")
|
||||
@@ -23,7 +45,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 +139,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,6 +305,7 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset)
|
||||
draw_droptarget(trainset, position++);
|
||||
ImGui::SameLine(15.0f);
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -332,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();
|
||||
@@ -340,41 +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<skin_set **>(payload->Data);
|
||||
std::shared_ptr<skin_set> skin;
|
||||
for (auto &s : ptr->vehicle.lock()->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<skin_set *const *>(payload->Data));
|
||||
if (auto const *payload{ImGui::AcceptDragDropPayload("vehicle_set")})
|
||||
drop_moved_vehicle(trainset, position, *static_cast<vehicle_moved const *>(payload->Data));
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
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<vehicle_moved*>(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->skin + "_" + 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()
|
||||
|
||||
@@ -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<void()> 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
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "scenery_scanner.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
scenery_scanner::scenery_scanner(ui::vehicles_bank &bank)
|
||||
: bank(bank)
|
||||
{
|
||||
@@ -10,13 +12,22 @@ 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::system_error const &) {
|
||||
// a name outside of the code page, which the path can't give as a string on Windows
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -5410,6 +5410,13 @@ void opengl33_renderer::Pick_Node_Callback(std::function<void(scene::basic_node
|
||||
m_node_pick_requests.push_back(callback);
|
||||
}
|
||||
|
||||
void opengl33_renderer::Forget_Node(scene::basic_node const *Node)
|
||||
{
|
||||
if (m_picksceneryitem == Node)
|
||||
m_picksceneryitem = nullptr;
|
||||
std::ranges::replace_if(m_picksceneryitems, [Node](scene::basic_node const *Item) { return Item == Node; }, nullptr);
|
||||
}
|
||||
|
||||
glm::dvec3 opengl33_renderer::Update_Mouse_Position()
|
||||
{
|
||||
if (!m_depth_pointer_pbo->is_busy())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1888,6 +1888,15 @@ opengl_renderer::Pick_Node_Callback( std::function<void( scene::basic_node * )>
|
||||
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::ranges::replace_if( m_picksceneryitems, [Node]( scene::basic_node const *Item ) { return Item == Node; }, nullptr );
|
||||
}
|
||||
|
||||
void
|
||||
opengl_renderer::Render( scene::basic_region *Region ) {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -90,6 +90,10 @@ public:
|
||||
virtual void Pick_Node_Callback( std::function<void( scene::basic_node * )> 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*/ ) {
|
||||
// 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; }
|
||||
|
||||
@@ -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<int, int> 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);
|
||||
|
||||
@@ -36,6 +36,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "utilities/utilities.h"
|
||||
#include "editor/editorTerrainStreamer.hpp"
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace simulation {
|
||||
|
||||
@@ -542,7 +543,14 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|
||||
}
|
||||
else if( nodedata.type == "track" ) {
|
||||
|
||||
auto *path { deserialize_path( Input, Scratchpad, nodedata ) };
|
||||
std::unique_ptr<TTrack> 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 ) + ")" );
|
||||
|
||||
@@ -73,12 +73,28 @@ std::deque<std::string> ErrorStack;
|
||||
|
||||
// lock for log stacks
|
||||
std::mutex logMutex;
|
||||
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<bool> &log_stopped()
|
||||
{
|
||||
static std::atomic<bool> stopped{false};
|
||||
return stopped;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void StopLogService()
|
||||
{
|
||||
log_stopped() = 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 = log_stopped();
|
||||
{
|
||||
// --- Obsługa InfoStack ---
|
||||
while (!InfoStack.empty())
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -474,6 +474,26 @@ bool FileExists(std::string const &Filename)
|
||||
return std::filesystem::exists(Filename);
|
||||
}
|
||||
|
||||
std::string FileCased(std::filesystem::path const &Filename)
|
||||
{
|
||||
std::error_code error;
|
||||
if (std::filesystem::exists(Filename, error))
|
||||
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
|
||||
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 Filename.has_parent_path() ? (directory / entry->path().filename()).string() : entry->path().filename().string();
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> FileExists(std::vector<std::string> const &Names, std::vector<std::string> const &Extensions)
|
||||
{
|
||||
|
||||
|
||||
@@ -217,6 +217,9 @@ template <typename Type_> 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. 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::filesystem::path const &Filename);
|
||||
|
||||
std::pair<std::string, std::string> FileExists(std::vector<std::string> const &Names, std::vector<std::string> const &Extensions);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "utilities/Logs.h"
|
||||
#include "rendering/renderer.h"
|
||||
|
||||
#include <format>
|
||||
|
||||
// 101206 Ra: trapezoidalne drogi i tory
|
||||
// 110720 Ra: rozprucie zwrotnicy i odcinki izolowane
|
||||
|
||||
@@ -432,11 +434,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(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;
|
||||
}
|
||||
else
|
||||
eType = tt_Unknown;
|
||||
eType = tt_Unknown; // left out by the loader
|
||||
if (Global.iWriteLogEnabled & 4)
|
||||
WriteLog(str);
|
||||
parser->getTokens(4);
|
||||
@@ -522,6 +528,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 );
|
||||
|
||||
@@ -697,6 +708,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
|
||||
@@ -2336,24 +2351,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 ? (
|
||||
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() << ' '
|
||||
|
||||
@@ -188,6 +188,7 @@ public:
|
||||
|
||||
std::vector<segment_data> 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<float, 3> 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;
|
||||
@@ -382,6 +383,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<std::string, int> fetch_track_rail_profile( std::string const &Profile );
|
||||
// loads content of specified file and converts it into a vertex array
|
||||
|
||||
Reference in New Issue
Block a user