diff --git a/src/openrct2/GameState.cpp b/src/openrct2/GameState.cpp index c13ca62b35..e61d81035a 100644 --- a/src/openrct2/GameState.cpp +++ b/src/openrct2/GameState.cpp @@ -12,6 +12,7 @@ #include "Context.h" #include "Editor.h" #include "Game.h" +#include "GameState.h" #include "GameStateSnapshots.h" #include "Input.h" #include "OpenRCT2.h" @@ -37,6 +38,7 @@ #include "world/Scenery.h" #include +#include using namespace OpenRCT2; using namespace OpenRCT2::Scripting; @@ -225,8 +227,17 @@ void GameState::Update() gInUpdateCode = false; } -void GameState::UpdateLogic() +void GameState::UpdateLogic(LogicTimings* timings) { + auto start_time = std::chrono::high_resolution_clock::now(); + + auto report_time = [timings, start_time](LogicTimePart part) { + if (timings != nullptr) + { + timings->TimingInfo[part][timings->CurrentIdx] = std::chrono::high_resolution_clock::now() - start_time; + } + }; + gScreenAge++; if (gScreenAge == 0) gScreenAge--; @@ -234,6 +245,7 @@ void GameState::UpdateLogic() GetContext()->GetReplayManager()->Update(); network_update(); + report_time(LogicTimePart::NetworkUpdate); if (network_get_mode() == NETWORK_MODE_SERVER) { @@ -277,33 +289,51 @@ void GameState::UpdateLogic() date_update(); _date = Date(static_cast(gDateMonthsElapsed), gDateMonthTicks); + report_time(LogicTimePart::Date); scenario_update(); + report_time(LogicTimePart::Scenario); climate_update(); + report_time(LogicTimePart::Climate); map_update_tiles(); + report_time(LogicTimePart::MapTiles); // Temporarily remove provisional paths to prevent peep from interacting with them map_remove_provisional_elements(); + report_time(LogicTimePart::MapStashProvisionalElements); map_update_path_wide_flags(); + report_time(LogicTimePart::MapPathWideFlags); peep_update_all(); + report_time(LogicTimePart::Peep); map_restore_provisional_elements(); + report_time(LogicTimePart::MapRestoreProvisionalElements); vehicle_update_all(); + report_time(LogicTimePart::Vehicle); sprite_misc_update_all(); + report_time(LogicTimePart::Misc); Ride::UpdateAll(); + report_time(LogicTimePart::Ride); if (!(gScreenFlags & SCREEN_FLAGS_EDITOR)) { _park->Update(_date); } + report_time(LogicTimePart::Park); research_update(); + report_time(LogicTimePart::Research); ride_ratings_update_all(); + report_time(LogicTimePart::RideRatings); ride_measurements_update(); + report_time(LogicTimePart::RideMeasurments); News::UpdateCurrentItem(); + report_time(LogicTimePart::News); map_animation_invalidate_all(); + report_time(LogicTimePart::MapAnimation); vehicle_sounds_update(); peep_update_crowd_noise(); climate_update_sound(); + report_time(LogicTimePart::Sounds); editor_open_windows_for_current_step(); // Update windows @@ -316,9 +346,11 @@ void GameState::UpdateLogic() } GameActions::ProcessQueue(); + report_time(LogicTimePart::GameActions); network_process_pending(); network_flush(); + report_time(LogicTimePart::NetworkFlush); gCurrentTicks++; gScenarioTicks++; @@ -332,7 +364,13 @@ void GameState::UpdateLogic() { hookEngine.Call(HOOK_TYPE::INTERVAL_DAY, true); } + report_time(LogicTimePart::Scripts); #endif + + if (timings != nullptr) + { + timings->CurrentIdx = (timings->CurrentIdx + 1) % LOGIC_UPDATE_MEASURMENTS_COUNT; + } } void GameState::CreateStateSnapshot() diff --git a/src/openrct2/GameState.h b/src/openrct2/GameState.h index 414bcc3eee..a5844696c2 100644 --- a/src/openrct2/GameState.h +++ b/src/openrct2/GameState.h @@ -11,12 +11,54 @@ #include "Date.h" +#include +#include +#include #include namespace OpenRCT2 { class Park; + // Information regarding various pieces of logic update + enum class LogicTimePart + { + NetworkUpdate, + Date, + Scenario, + Climate, + MapTiles, + MapStashProvisionalElements, + MapPathWideFlags, + Peep, + MapRestoreProvisionalElements, + Vehicle, + Misc, + Ride, + Park, + Research, + RideRatings, + RideMeasurments, + News, + MapAnimation, + Sounds, + GameActions, + NetworkFlush, + Scripts, + }; + + // ~6.5s at 40Hz + constexpr size_t LOGIC_UPDATE_MEASURMENTS_COUNT = 256; + + // In order not to cause allocations, collect multiple samples into single pre-allocated struct + using LogicTimingInfo = std::map, LOGIC_UPDATE_MEASURMENTS_COUNT>>; + + struct LogicTimings + { + LogicTimingInfo TimingInfo; + size_t CurrentIdx{}; + }; + /** * Class to update the state of the map and park. */ @@ -41,7 +83,7 @@ namespace OpenRCT2 void InitAll(int32_t mapSize); void Update(); - void UpdateLogic(); + void UpdateLogic(LogicTimings* timings = nullptr); private: void CreateStateSnapshot(); diff --git a/src/openrct2/cmdline/BenchUpdate.cpp b/src/openrct2/cmdline/BenchUpdate.cpp index 641c43c84a..b3fe767de3 100644 --- a/src/openrct2/cmdline/BenchUpdate.cpp +++ b/src/openrct2/cmdline/BenchUpdate.cpp @@ -14,6 +14,7 @@ # include "../Context.h" # include "../GameState.h" # include "../OpenRCT2.h" +# include "../platform/Platform2.h" # include "../platform/platform.h" # include @@ -33,11 +34,51 @@ static void BM_update(benchmark::State& state, const std::string& filename) state.SkipWithError("Failed to load file!"); } + std::vector timings(1); + timings.reserve(100); + int currentTimingIdx = 0; for (auto _ : state) { - context->GetGameState()->UpdateLogic(); + if (timings[currentTimingIdx].CurrentIdx == (LOGIC_UPDATE_MEASURMENTS_COUNT - 1)) + { + timings.resize(timings.size() + 1); + currentTimingIdx++; + } + LogicTimings* timingToUse = &timings[currentTimingIdx]; + context->GetGameState()->UpdateLogic(timingToUse); } state.SetItemsProcessed(state.iterations()); + auto accumulator = [timings](LogicTimePart part) -> double { + std::chrono::duration timesum; + for (const auto& timing : timings) + { + for (const auto& PartTime : timing.TimingInfo.at(part)) + timesum += PartTime; + } + return std::chrono::duration_cast(timesum).count(); + }; + state.counters["NetworkUpdateAcc_ms"] = accumulator(LogicTimePart::NetworkUpdate); + state.counters["DateAcc_ms"] = accumulator(LogicTimePart::Date); + state.counters["ScenarioAcc_ms"] = accumulator(LogicTimePart::Scenario); + state.counters["ClimateAcc_ms"] = accumulator(LogicTimePart::Climate); + state.counters["MapTilesAcc_ms"] = accumulator(LogicTimePart::MapTiles); + state.counters["MapStashProvisionalElementsAcc_ms"] = accumulator(LogicTimePart::MapStashProvisionalElements); + state.counters["MapPathWideFlagsAcc_ms"] = accumulator(LogicTimePart::MapPathWideFlags); + state.counters["PeepAcc_ms"] = accumulator(LogicTimePart::Peep); + state.counters["MapRestoreProvisionalElementsAcc_ms"] = accumulator(LogicTimePart::MapRestoreProvisionalElements); + state.counters["VehicleAcc_ms"] = accumulator(LogicTimePart::Vehicle); + state.counters["MiscAcc_ms"] = accumulator(LogicTimePart::Misc); + state.counters["RideAcc_ms"] = accumulator(LogicTimePart::Ride); + state.counters["ParkAcc_ms"] = accumulator(LogicTimePart::Park); + state.counters["ResearchAcc_ms"] = accumulator(LogicTimePart::Research); + state.counters["RideRatingsAcc_ms"] = accumulator(LogicTimePart::RideRatings); + state.counters["RideMeasurmentsAcc_ms"] = accumulator(LogicTimePart::RideMeasurments); + state.counters["NewsAcc_ms"] = accumulator(LogicTimePart::News); + state.counters["MapAnimationAcc_ms"] = accumulator(LogicTimePart::MapAnimation); + state.counters["SoundsAcc_ms"] = accumulator(LogicTimePart::Sounds); + state.counters["GameActionsAcc_ms"] = accumulator(LogicTimePart::GameActions); + state.counters["NetworkFlushAcc_ms"] = accumulator(LogicTimePart::NetworkFlush); + state.counters["ScriptsAcc_ms"] = accumulator(LogicTimePart::Scripts); } else { @@ -60,7 +101,7 @@ static int cmdline_for_bench_sprite_sort(int argc, const char** argv) // Extract file names from argument list. If there is no such file, consider it benchmark option. for (int i = 0; i < argc; i++) { - if (platform_file_exists(argv[i])) + if (Platform::FileExists(argv[i])) { // Register benchmark for sv6 if valid benchmark::RegisterBenchmark(argv[i], BM_update, argv[i]);