From f17462b18639f20de75c7a3040eb35be6804ee1d Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Fri, 23 Oct 2026 18:47:01 +0200 Subject: [PATCH] perf(physics): road vehicles farther than 1 km from the camera go by one step of the physics in a frame (physics.roadlod); count what resets the switching time of the vehicles (compressor, mains, diesel, physics woken) in the debug panel --- McZapkie/Mover.cpp | 8 ++++ application/drivermode.cpp | 1 + application/driveruipanels.cpp | 1 + rendering/opengl46/opengl46renderer.cpp | 12 +++++ tests/unit/engine_stubs.cpp | 7 +++ utilities/Globals.cpp | 7 +++ utilities/Globals.h | 1 + utilities/Timer.h | 63 +++++++++++++++++++++++++ vehicle/DynObj.cpp | 23 ++++++--- 9 files changed, 117 insertions(+), 6 deletions(-) diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index fed027ed..1f89c8c2 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/. #include "Oerlikon_ESt.h" #include "utilities/Globals.h" #include "utilities/Logs.h" +#include "utilities/Timer.h" #include "utilities/parser.h" #include "simulation/simulation.h" //--------------------------------------------------------------------------- @@ -3608,6 +3609,7 @@ void TMoverParameters::MainSwitch_(bool const State) if (Mains != initialstate) { LastSwitchingTime = 0; + Timer::subsystem.switching.note(Timer::switching_counter::mains, this, &TypeName, &Name); } } } @@ -4363,6 +4365,10 @@ void TMoverParameters::CompressorCheck(double dt) // jeśli została załączona to trzeba ograniczyć ponowne włączenie LastSwitchingTime = 0; } + if (CompressorFlag != compressorflag) + { + Timer::subsystem.switching.note(CompressorFlag ? Timer::switching_counter::compressor_on : Timer::switching_counter::compressor_off, this, &TypeName, &Name); + } if (false == CompressorFlag) { @@ -7804,6 +7810,7 @@ bool TMoverParameters::dizel_Update(double dt) dizel_ignition = false; // TODO: split engine and main circuit state indicator in two separate flags LastSwitchingTime = 0; + Timer::subsystem.switching.note(Timer::switching_counter::diesel, this, &TypeName, &Name); Mains = true; dizel_spinup = true; enrot = std::max(enrot, @@ -8945,6 +8952,7 @@ bool TMoverParameters::switch_physics(bool const State) // DO PRZETLUMACZENIA NA if (true == State) { LastSwitchingTime = 0; + Timer::subsystem.switching.note(Timer::switching_counter::wake, this, &TypeName, &Name); } return true; diff --git a/application/drivermode.cpp b/application/drivermode.cpp index 3979927a..dcd11960 100644 --- a/application/drivermode.cpp +++ b/application/drivermode.cpp @@ -436,6 +436,7 @@ bool driver_mode::update() Timer::subsystem.sim_total.stop(); // the parts of the vehicle update made in the frame, none when paused Timer::subsystem.commit_frame(); + Timer::subsystem.switching.update(); Timer::subsystem.loop_audio.start(); simulation::Region->update_sounds(); diff --git a/application/driveruipanels.cpp b/application/driveruipanels.cpp index c7215a23..94195a90 100644 --- a/application/driveruipanels.cpp +++ b/application/driveruipanels.cpp @@ -1515,6 +1515,7 @@ debug_panel::render_section_renderer() { ImGui::PopStyleColor(); // the physics of the vehicles, timed in the section of the main loop ImGui::Checkbox( "let vehicles standing still and held by their brakes sleep (physics.sleepheld)", &Global.physics_sleepheld ); + ImGui::Checkbox( "one step of the physics in a frame for road vehicles farther than 1 km (physics.roadlod)", &Global.physics_roadlod ); return true; } diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index b073e31a..ebfb66ef 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -876,6 +876,18 @@ void opengl46_renderer::SwapBuffers() + ", acceleration " + std::to_string( awake.acceleration ) + ", switching " + std::to_string( awake.switching ) + ", ezt/dmu " + std::to_string( awake.multipleunit ) + ", neighbour " + std::to_string( awake.neighbour ), "" ); + // what resets the switching time of the vehicles, in the last second + auto const &switching { timers.switching.last() }; + using source = Timer::switching_counter::source; + text += timeline( + 3, + "switching per second: compressor on " + std::to_string( switching.events[ source::compressor_on ] ) + ", off " + + std::to_string( switching.events[ source::compressor_off ] ) + ", mains " + std::to_string( switching.events[ source::mains ] ) + ", diesel " + + std::to_string( switching.events[ source::diesel ] ) + ", physics woken " + std::to_string( switching.events[ source::wake ] ), + "" ); + if( switching.busiestevents > 0 ) { + text += timeline( 3, "most often: " + switching.busiest + " (" + std::to_string( switching.busiestevents ) + ")", "" ); + } } text += timeline( 2, "secondary steps", ms( timers.sim_secondary.average() ) ); text += timeline( 2, "trains (cabs)", ms( timers.sim_trains.average() ) ); diff --git a/tests/unit/engine_stubs.cpp b/tests/unit/engine_stubs.cpp index 96760f43..d53ffe41 100644 --- a/tests/unit/engine_stubs.cpp +++ b/tests/unit/engine_stubs.cpp @@ -6,6 +6,7 @@ #include "stdafx.h" #include "utilities/Globals.h" #include "utilities/Logs.h" +#include "utilities/Timer.h" #include "scene/scenenodegroups.h" #include "scene/scenelayers.h" @@ -20,6 +21,12 @@ global_settings &GetGlobalSettings() { return settings; } +// timers of the debug panel, which the physics counts its switching in --------------------------------- + +namespace Timer { +subsystem_stopwatches subsystem; // NOSONAR the engine's extern global, utilities/Timer.h +} + // logs: quiet, unless EU07_UNIT_TESTS_LOG is set -------------------------------------------------- namespace { diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 3557a8c2..f0350675 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -485,6 +485,12 @@ bool global_settings::ConfigParseSimulation(cParser& Parser, const std::string& return true; } + if (token == "physics.roadlod") + { + ParseOne(Parser, physics_roadlod); + return true; + } + if (token == "enabletraction") { ParseOne(Parser, bEnableTraction); @@ -1894,6 +1900,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "physicslog", WriteLogFlag ); export_as_text( Output, "fullphysics", FullPhysics ); export_as_text( Output, "physics.sleepheld", physics_sleepheld ); + export_as_text( Output, "physics.roadlod", physics_roadlod ); export_as_text( Output, "debuglog", iWriteLogEnabled ); export_as_text( Output, "multiplelogs", MultipleLogs ); export_as_text( Output, "showsystemconsole", ShowSystemConsole ); diff --git a/utilities/Globals.h b/utilities/Globals.h index 38a73dd3..4bade9c6 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -115,6 +115,7 @@ struct global_settings { std::string Period{}; // time of the day, based on sun position bool FullPhysics{ true }; // full calculations performed for each simulation step bool physics_sleepheld{ true }; // vehicles standing still and held by their brakes let their physics sleep, see TMoverParameters::accelerating() + bool physics_roadlod{ true }; // road vehicles far from the camera go by one step of the physics in a frame, see vehicle_table::update() bool bnewAirCouplers{ true }; float fMoveLight{ 0.f }; // numer dnia w roku albo -1 bool FakeLight{ false }; // toggle between fixed and dynamic daylight diff --git a/utilities/Timer.h b/utilities/Timer.h index 505f414b..b9dd2018 100644 --- a/utilities/Timer.h +++ b/utilities/Timer.h @@ -9,6 +9,11 @@ http://mozilla.org/MPL/2.0/. #pragma once +#include +#include +#include +#include + namespace Timer { double GetTime(); @@ -79,6 +84,63 @@ private: float m_accumulator { 0.f }; }; +// what resets TMoverParameters::LastSwitchingTime, which keeps the physics of a vehicle on for 5 s: counted over a second, +// with the vehicle which did it most often, for the debug panel +class switching_counter { + +public: + enum source : std::size_t { + compressor_on = 0, + compressor_off, + mains, + diesel, + wake, // switch_physics( true ) of a vehicle whose physics was off + count + }; + struct result { + std::array events {}; + std::string busiest; // type and name of the vehicle with the most events + unsigned int busiestevents { 0 }; + }; + // Owner: the vehicle, Label: its type and name, read only when the second ends + void + note( source const Source, void const *Owner, std::string const *Type, std::string const *Name ) { + ++m_events[ Source ]; + auto &owner { m_owners[ Owner ] }; + ++owner.events; + owner.type = Type; + owner.name = Name; } + // ends the second when it's over. to be called once a frame, while the vehicles noted in it still exist + void + update() { + auto const now { std::chrono::steady_clock::now() }; + if( now - m_start < std::chrono::seconds( 1 ) ) { return; } + m_last = {}; + m_last.events = m_events; + for( auto const &owner : m_owners ) { + if( owner.second.events > m_last.busiestevents ) { + m_last.busiestevents = owner.second.events; + m_last.busiest = ( owner.second.type ? *owner.second.type : std::string{} ) + " " + ( owner.second.name ? *owner.second.name : std::string{} ); } } + m_events = {}; + m_owners.clear(); + m_start = now; } + // of the last whole second + result const & + last() const { + return m_last; } + +private: + struct owner_data { + unsigned int events { 0 }; + std::string const *type { nullptr }; + std::string const *name { nullptr }; + }; + std::chrono::time_point m_start { std::chrono::steady_clock::now() }; + std::array m_events {}; + std::unordered_map m_owners; + result m_last; +}; + struct subsystem_stopwatches { stopwatch gfx_total; stopwatch gfx_color; @@ -139,6 +201,7 @@ struct subsystem_stopwatches { } awake; }; vehicle_counts vehicles; // of the frame under way + switching_counter switching; vehicle_counts vehicleslast; // of the last frame // ends the frame of the frame_stopwatches and of the counts void diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index 541668e9..fb961262 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -8620,12 +8620,17 @@ vehicle_table::update( double Deltatime, int Iterationcount ) { // kind, measured from the end of the one before it static std::vector kinds; kinds.resize( m_items.size() ); + // road vehicles far from the camera with nobody inside go by one step of the physics in the frame, as long as all the steps + // together (Global.physics_roadlod): their movement goes on, in coarser steps nobody is close enough to tell + static std::vector coarse; + coarse.assign( m_items.size(), 0 ); auto const camera { Global.pCamera.Pos }; for( std::size_t index = 0; index < m_items.size(); ++index ) { auto const *vehicle { m_items[ index ] }; auto const road { ( vehicle->MoverParameters->CategoryFlag & 2 ) != 0 }; auto const distant { glm::length2( vehicle->GetPosition() - camera ) > Timer::subsystem.VEHICLE_NEAR * Timer::subsystem.VEHICLE_NEAR }; kinds[ index ] = static_cast( ( road ? 2 : 0 ) + ( distant ? 1 : 0 ) ); + coarse[ index ] = static_cast( Global.physics_roadlod && road && distant && ( false == vehicle->MechInside ) && ( Iterationcount > 1 ) ); if( false == vehicle->bEnabled ) { continue; } ++counts.kind[ kinds[ index ] ]; auto const *mover { vehicle->MoverParameters }; @@ -8680,34 +8685,40 @@ vehicle_table::update( double Deltatime, int Iterationcount ) { for( int iteration = 0; iteration < Iterationcount - 1; ++iteration ) { std::size_t index { 0 }; for( auto *vehicle : m_items ) { - vehicle->UpdateForce( Deltatime ); + if( ( index >= coarse.size() ) || ( coarse[ index ] == 0 ) ) { + vehicle->UpdateForce( Deltatime ); + } lap( index++, Timer::subsystem.vehicles_forces_kind, mark ); } index = 0; for( auto *vehicle : m_items ) { - vehicle->FastUpdate( Deltatime ); + if( ( index >= coarse.size() ) || ( coarse[ index ] == 0 ) ) { + vehicle->FastUpdate( Deltatime ); + } lap( index++, Timer::subsystem.vehicles_forces_kind, mark ); } } } + auto const totaltime { Deltatime * Iterationcount }; // całkowity czas { std::size_t index { 0 }; for( auto *vehicle : m_items ) { - vehicle->UpdateForce( Deltatime ); + // the vehicles going by one step take it with the time of all of them + auto const coarsestep { ( index < coarse.size() ) && ( coarse[ index ] != 0 ) }; + vehicle->UpdateForce( coarsestep ? totaltime : Deltatime ); lap( index++, Timer::subsystem.vehicles_forces_kind, mark ); } } Timer::subsystem.vehicles_forces.stop(); - auto const totaltime { Deltatime * Iterationcount }; // całkowity czas - Timer::subsystem.vehicles_update.start(); { std::size_t index { 0 }; mark = std::chrono::steady_clock::now(); for( auto *vehicle : m_items ) { + auto const coarsestep { ( index < coarse.size() ) && ( coarse[ index ] != 0 ) }; // Ra 2015-01: tylko tu przelicza sieć trakcyjną - vehicle->Update( Deltatime, totaltime ); + vehicle->Update( coarsestep ? totaltime : Deltatime, totaltime ); lap( index++, Timer::subsystem.vehicles_update_kind, mark ); } }