diff --git a/EU07.cpp b/EU07.cpp index aac7cae1..ec3e972e 100644 --- a/EU07.cpp +++ b/EU07.cpp @@ -23,6 +23,7 @@ Stele, firleju, szociu, hunter, ZiomalCl, OLI_EU and others #include "utilities/Globals.h" #include "utilities/Logs.h" #include +#include #ifdef WITHDUMPGEN #ifdef _WIN32 #include @@ -150,7 +151,7 @@ int main(int argc, char *argv[]) #ifdef _WIN32 catch (std::runtime_error const &Error) { - ErrorLog("Critical error: " + std::string(Error.what())); + ErrorLog(std::format("Critical error: {}", Error.what())); // in test mode nobody is there to close the box, the exit code tells of the error if (false == Global.test.enabled) { @@ -167,5 +168,5 @@ int main(int argc, char *argv[]) #endif // skip destructors, there are ordering errors which causes segfaults // a test run (--test) says its result in the exit code - std::_Exit(Global.test.enabled ? simulation::Test.exit_code() : 0); + std::_Exit(Global.test.enabled ? simulation::test().exit_code() : 0); } diff --git a/application/application.cpp b/application/application.cpp index 37afff67..439c01d9 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -1137,6 +1137,38 @@ void eu07_application::init_files() } namespace fs = std::filesystem; +namespace +{ + +// the settings of the test mode (--test), over whatever the settings files say +void apply_test_mode_settings() +{ + // nobody watches nor listens: no player vehicle, nothing drawn, no sound, no window in the way + Global.editor_session = false; + Global.local_start_vehicle = "ghostview"; + Global.GfxRenderer = "null"; + Global.NvRenderer = false; + Global.bSoundEnabled = false; + Global.python_enabled = false; + Global.bInactivePause = false; + Global.bFullScreen = false; + Global.fullscreen_windowed = false; + Global.VSync = false; + Global.iMultisampling = 0; + Global.gfx_usegles = false; + Global.minframetime = std::chrono::duration(0.0f); + Global.extra_viewports.clear(); + Global.headtrack_conf.joy.clear(); + // a test runs on its own + Global.network_servers.clear(); + Global.network_client.reset(); + // and the same way every time, unless given another seed (-seed, or randomseed in the settings) + if (!Global.random_seed) + Global.random_seed = seed_of("test"); +} + +} // namespace + int eu07_application::init_settings(int Argc, char *Argv[]) { Global.asVersion = VERSION_INFO; @@ -1241,28 +1273,7 @@ int eu07_application::init_settings(int Argc, char *Argv[]) ErrorLog("test: --test needs a scenery given with -s"); return -1; } - // nobody watches nor listens: no player vehicle, nothing drawn, no sound, no window in the way - Global.editor_session = false; - Global.local_start_vehicle = "ghostview"; - Global.GfxRenderer = "null"; - Global.NvRenderer = false; - Global.bSoundEnabled = false; - Global.python_enabled = false; - Global.bInactivePause = false; - Global.bFullScreen = false; - Global.fullscreen_windowed = false; - Global.VSync = false; - Global.iMultisampling = 0; - Global.gfx_usegles = false; - Global.minframetime = std::chrono::duration(0.0f); - Global.extra_viewports.clear(); - Global.headtrack_conf.joy.clear(); - // a test runs on its own - Global.network_servers.clear(); - Global.network_client.reset(); - // and the same way every time, unless given another seed (-seed, or randomseed in the settings) - if (!Global.random_seed) - Global.random_seed = seed_of("test"); + apply_test_mode_settings(); } return 0; diff --git a/application/drivermode.cpp b/application/drivermode.cpp index 1b3a44d6..deb906a4 100644 --- a/application/drivermode.cpp +++ b/application/drivermode.cpp @@ -309,7 +309,7 @@ bool driver_mode::update() if (Global.test.enabled && simulation::is_ready) { - simulation::Test.update(deltatime); + simulation::test().update(deltatime); } } @@ -488,7 +488,7 @@ void driver_mode::enter() { // the test runs as fast as it can compute, in steps of fixed length, so that runs on different machines agree Timer::set_fixed_step(simulation::test_manager::frame_step); - simulation::Test.start(); + simulation::test().start(); } set_picking(!Global.captureonstart); diff --git a/application/scenarioloadermode.cpp b/application/scenarioloadermode.cpp index b84c1912..20fa46d3 100644 --- a/application/scenarioloadermode.cpp +++ b/application/scenarioloadermode.cpp @@ -60,7 +60,7 @@ bool scenarioloader_mode::update() { ErrorLog(std::format("Bad init: scenario \"{}\" couldn't be opened ({})", Global.SceneryFile, e.what())); state.reset(); if (Global.test.enabled) { - simulation::Test.finish(simulation::test_result::error, "scenario couldn't be opened"); + simulation::test().finish(simulation::test_result::error, "scenario couldn't be opened"); return true; } Global.SceneryFile.clear(); @@ -80,7 +80,7 @@ bool scenarioloader_mode::update() { state.reset(); if( Global.test.enabled ) { // there's nothing to test; the exit code says the run went wrong - simulation::Test.finish( simulation::test_result::error, "scenario loading failed" ); + simulation::test().finish( simulation::test_result::error, "scenario loading failed" ); return true; } Global.SceneryFile.clear(); diff --git a/rendering/nullrenderer.cpp b/rendering/nullrenderer.cpp index f193ad3e..bc466e97 100644 --- a/rendering/nullrenderer.cpp +++ b/rendering/nullrenderer.cpp @@ -7,8 +7,9 @@ void null_imgui_renderer::BeginFrame() { auto &io { ImGui::GetIO() }; if( false == io.Fonts->IsBuilt() ) { - unsigned char *pixels; - int width, height; + unsigned char *pixels { nullptr }; + int width { 0 }; + int height { 0 }; io.Fonts->GetTexDataAsAlpha8( &pixels, &width, &height ); } } diff --git a/simulation/simulation.cpp b/simulation/simulation.cpp index 59635c38..d10d555a 100644 --- a/simulation/simulation.cpp +++ b/simulation/simulation.cpp @@ -111,7 +111,7 @@ state_manager::init_scripting_interface() { simulation::Region->insert( memorycell ); } // the cell a test scenery reports its result to - simulation::Test.init_result_cell(); + simulation::test().init_result_cell(); } // legacy method, calculates changes in simulation state over specified time diff --git a/simulation/simulationtest.cpp b/simulation/simulationtest.cpp index 4dbe4c24..439f295f 100644 --- a/simulation/simulationtest.cpp +++ b/simulation/simulationtest.cpp @@ -19,9 +19,29 @@ http://mozilla.org/MPL/2.0/. #include "world/Event.h" #include "world/MemCell.h" +#include +#include +#include +#include +#include + +namespace { + +// the cell holds what the scenery wrote, so the values are whole numbers; the margin only keeps a stray fraction out +bool holds(double const Value, double const Expected) +{ + return std::abs(Value - Expected) < 1e-6; +} + +} // namespace + namespace simulation { -test_manager Test; +test_manager &test() +{ + static test_manager instance; + return instance; +} std::string_view to_string(test_result const Result) { @@ -39,11 +59,12 @@ std::string_view to_string(test_result const Result) void test_manager::init_result_cell() { - auto *cell = new TMemCell({0, -1, std::string(result_cell), "memcell"}); + auto cell { std::make_unique(scene::node_data{0, -1, std::string(result_cell), "memcell"}) }; // neither of the reported values, so that a cell nobody wrote to says nothing cell->UpdateValues("", -1.0, 0.0, basic_event::flags::value1 | basic_event::flags::value2); - simulation::Memory.insert(cell); - simulation::Region->insert(cell); + simulation::Region->insert(cell.get()); + // the table owns its cells + simulation::Memory.insert(cell.release()); } void test_manager::start() @@ -76,21 +97,21 @@ void test_manager::update(double const Deltatime) if (auto const *cell = simulation::Memory.find(std::string(result_cell))) { auto const value { cell->Value1() }; - auto const reason { cell->Text().empty() ? std::string() : " (" + cell->Text() + ")" }; - if (value == 0.0) + auto const reason { cell->Text().empty() ? std::string() : std::format(" ({})", cell->Text()) }; + if (holds(value, 0.0)) { - finish(test_result::fail, "__test.result set to 0" + reason); + finish(test_result::fail, std::format("__test.result set to 0{}", reason)); return; } - if (value == 1.0 && false == m_passrequested) + if (holds(value, 1.0) && false == m_passrequested) { m_passrequested = true; WriteLog(std::format("test: pass requested at {:.1f} s{}, waiting for every vehicle to stop", m_time, reason)); } } - auto const atrest = std::none_of(simulation::Vehicles.sequence().begin(), simulation::Vehicles.sequence().end(), - [](TDynamicObject const *Vehicle) { return std::abs(Vehicle->GetVelocity()) > 0.01; }); + auto const atrest { std::ranges::none_of(simulation::Vehicles.sequence(), + [](TDynamicObject const *Vehicle) { return std::abs(Vehicle->GetVelocity()) > 0.01; }) }; m_resttime = atrest ? m_resttime + Deltatime : 0.0; if (m_passrequested && m_resttime >= pass_settle_time) @@ -111,9 +132,9 @@ void test_manager::update(double const Deltatime) } } -void test_manager::signal_passed_at_danger(std::string const &Vehicle, std::string const &Signal) +void test_manager::signal_passed_at_danger(std::string_view const Vehicle, std::string_view const Signal) { - auto const reason { Vehicle + " passed signal " + Signal + " at danger" }; + auto const reason { std::format("{} passed signal {} at danger", Vehicle, Signal) }; if (false == Global.test.enabled) { WriteLog(reason, logtype::generic); @@ -127,7 +148,7 @@ void test_manager::signal_passed_at_danger(std::string const &Vehicle, std::stri finish(test_result::signal_passed_at_danger, reason); } -void test_manager::finish(test_result const Result, std::string const &Reason) +void test_manager::finish(test_result const Result, std::string_view const Reason) { if (m_finished || false == Global.test.enabled) return; diff --git a/simulation/simulationtest.h b/simulation/simulationtest.h index c9bc5437..f47f408b 100644 --- a/simulation/simulationtest.h +++ b/simulation/simulationtest.h @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include +#include // integration test mode (eu07 --test -s , eu07/maszyna#541): the scenery runs unattended in a tight // fixed-step loop and the simulator quits with an exit code saying how the test went @@ -44,9 +45,9 @@ public: // checks the end conditions after a simulation frame of Deltatime seconds void update(double Deltatime); // reported by a driver whose train went past a signal showing stop - void signal_passed_at_danger(std::string const &Vehicle, std::string const &Signal); + void signal_passed_at_danger(std::string_view Vehicle, std::string_view Signal); // ends the test with given result; the first call wins - void finish(test_result Result, std::string const &Reason); + void finish(test_result Result, std::string_view Reason); bool finished() const { return m_finished; } // exit code of the process: the result, or error while the test hasn't finished @@ -61,6 +62,7 @@ private: double m_resttime { 0.0 }; // how long every vehicle has been standing [s] }; -extern test_manager Test; +// the test of the run +test_manager &test(); } // namespace simulation diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index e3622317..5cdb3ef2 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1779,6 +1779,10 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.shadowmap.enabled", gfx_shadowmap_enabled ); // TODO: export fpslimit export_as_text( Output, "randomseed", Global.random_seed ); + export_as_text( Output, "test.timeout", test.timeout ); + export_as_text( Output, "test.standstill", test.standstill ); + export_as_text( Output, "test.pass_on_standstill", test.pass_on_standstill ); + export_as_text( Output, "test.fail_if_stop_overran", test.fail_if_stop_overran ); export_as_text( Output, "gfx.envmap.enabled", gfx_envmap_enabled ); export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled ); export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter ); diff --git a/utilities/Timer.cpp b/utilities/Timer.cpp index 388330bb..eacd2ba0 100644 --- a/utilities/Timer.cpp +++ b/utilities/Timer.cpp @@ -26,7 +26,17 @@ double fRenderTime{ 0.0 }; double fSoundTimer{ 0.0 }; double fSinceStart{ 0.0 }; double override_delta = -1.0f; -double fixed_step = 0.0; + +namespace { + +// when set (test mode), every frame lasts this long, see set_fixed_step() +double &fixed_step() +{ + static double step{0.0}; + return step; +} + +} // namespace double GetTime() { @@ -56,7 +66,7 @@ void set_delta_override(double t) void set_fixed_step(double t) { - fixed_step = t; + fixed_step() = t; } void ResetTimers() @@ -82,15 +92,16 @@ void UpdateTimers(bool pause) fr = 1000000000; #endif DeltaRenderTime = double(count - oldCount) / double(fr); - if (fixed_step > 0.0) + auto const fixedstep{fixed_step()}; + if (fixedstep > 0.0) { // the frame lasts exactly as long as said, however long it took to compute; the time speed doesn't apply - DeltaRenderTime = fixed_step; + DeltaRenderTime = fixedstep; } fRenderTime += DeltaRenderTime; if (!pause) { - DeltaTime = (fixed_step > 0.0 ? 1.0 : Global.fTimeSpeed) * DeltaRenderTime; + DeltaTime = (fixedstep > 0.0 ? 1.0 : Global.fTimeSpeed) * DeltaRenderTime; fSoundTimer += DeltaTime; if (fSoundTimer > 0.1) fSoundTimer = 0.0; diff --git a/vehicle/Driver.cpp b/vehicle/Driver.cpp index 5f1bde20..c0614923 100644 --- a/vehicle/Driver.cpp +++ b/vehicle/Driver.cpp @@ -768,12 +768,7 @@ void TController::TableCheck(double fDistance) { // aktualizacja rekordów z wyjątkiem ostatniego if (sSpeedTable[i].iFlags & spEnabled) // jeśli pozycja istotna { - auto const waspassed { TestFlag( sSpeedTable[ i ].iFlags, spElapsed ) }; - auto const switchchanged { sSpeedTable[ i ].Update() }; - if( false == waspassed ) { - CheckSignalPassedAtDanger( sSpeedTable[ i ] ); - } - if (switchchanged) + if (UpdateSpeedPosition(sSpeedTable[i])) { if( Global.iWriteLogEnabled & 8 ) { WriteLog( "Speed table for " + OwnerName() + " detected switch change at " + sSpeedTable[ i ].trTrack->name() + " (generating fresh trace)" ); @@ -823,11 +818,7 @@ void TController::TableCheck(double fDistance) } } // aktualizacja ostatniego; a signal at stop often is the last, the scan doesn't go past it - auto const lastwaspassed { TestFlag( sSpeedTable[ iLast ].iFlags, spElapsed ) }; - sSpeedTable[iLast].Update(); - if( false == lastwaspassed ) { - CheckSignalPassedAtDanger( sSpeedTable[ iLast ] ); - } + UpdateSpeedPosition(sSpeedTable[iLast]); // WriteLog("TableCheck: Upate last track. Dist=" + AnsiString(sSpeedTable[iLast].fDist)); if( sSpeedTable[ iLast ].fDist < fDistance ) { TableTraceRoute( fDistance, pVehicles[ end::rear ] ); // doskanowanie dalszego odcinka @@ -838,18 +829,31 @@ void TController::TableCheck(double fDistance) } }; -void TController::CheckSignalPassedAtDanger( TSpeedPos const &Point ) const +namespace { + +// the position is a signal showing stop (SetVelocity 0 0). SetVelocity 0 x on automatic block isn't: it may be passed after a stop +bool is_signal_at_stop( TSpeedPos const &Point ) { - if( false == TestFlag( Point.iFlags, spElapsed ) - || false == TestFlag( Point.iFlags, spEvent ) - || false == TestFlag( Point.iFlags, spSemaphor ) - || true == TestFlag( Point.iFlags, spStopOnSBL ) // SetVelocity 0 x on automatic block: may go on after a stop - || Point.fVelNext != 0.0 - || Point.evEvent == nullptr - || ( mvOccupied->CategoryFlag & 1 ) == 0 ) { // railway only - return; + return TestFlag( Point.iFlags, spEvent ) + && TestFlag( Point.iFlags, spSemaphor ) + && false == TestFlag( Point.iFlags, spStopOnSBL ) + && std::abs( Point.fVelNext ) <= 0.0 + && Point.evEvent != nullptr; +} + +} // namespace + +bool TController::UpdateSpeedPosition( TSpeedPos &Point ) const +{ + auto const passedbefore { TestFlag( Point.iFlags, spElapsed ) }; + auto const switchchanged { Point.Update() }; + if( false == passedbefore + && TestFlag( Point.iFlags, spElapsed ) + && is_signal_at_stop( Point ) + && ( mvOccupied->CategoryFlag & 1 ) != 0 ) { // railway only + simulation::test().signal_passed_at_danger( OwnerName(), Point.evEvent->m_name ); } - simulation::Test.signal_passed_at_danger( OwnerName(), Point.evEvent->m_name ); + return switchchanged; } auto const passengerstopmaxdistance { 400.0 }; // maximum allowed distance between passenger stop point and consist head diff --git a/vehicle/Driver.h b/vehicle/Driver.h index 8c12087f..8f2015f2 100644 --- a/vehicle/Driver.h +++ b/vehicle/Driver.h @@ -519,8 +519,9 @@ private: bool TableNotFound( basic_event const *Event, double const Distance ) const; void TableTraceRoute( double fDistance, TDynamicObject *pVehicle ); void TableCheck( double fDistance ); - // reports the train going past a signal showing stop (SetVelocity 0 0); called once the head passed Point - void CheckSignalPassedAtDanger( TSpeedPos const &Point ) const; + // updates a position of the speed table, returns true when a switch on it changed (TSpeedPos::Update). + // reports the head of the train going past it, if it's a signal showing stop + bool UpdateSpeedPosition( TSpeedPos &Point ) const; TCommandType TableUpdate( double &fVelDes, double &fDist, double &fNext, double &fAcc ); bool TableUpdateStopPoint( TCommandType &Command, TSpeedPos &Point, double const Signaldistance ); bool TableUpdateEvent( double &Velocity, TCommandType &Command, TSpeedPos &Point, double &Signaldistance, int const Pointindex );