diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 72d4eb2437..9aef052167 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -238,6 +238,7 @@ 9308DA04209908090079EE96 /* TileElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9308D9FC209908080079EE96 /* TileElement.h */; }; 9308DA05209908090079EE96 /* Surface.h in Headers */ = {isa = PBXBuildFile; fileRef = 9308D9FD209908090079EE96 /* Surface.h */; }; 930EEA6A24FC00950070314E /* ScenarioSelect.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 930EEA6924FC00940070314E /* ScenarioSelect.cpp */; }; + 9329D520240C17C60054301C /* BenchUpdate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9329D51F240C17C60054301C /* BenchUpdate.cpp */; }; 932A211E22D73CFA00C57EDB /* GameActionCompat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 932A20CF22D73CEE00C57EDB /* GameActionCompat.cpp */; }; 932A211F22D73CFA00C57EDB /* GameActionRegistration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 932A20D322D73CEF00C57EDB /* GameActionRegistration.cpp */; }; 932A212022D73CFA00C57EDB /* GameAction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 932A211C22D73CFA00C57EDB /* GameAction.cpp */; }; @@ -1325,6 +1326,9 @@ 9308D9FC209908080079EE96 /* TileElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TileElement.h; sourceTree = ""; }; 9308D9FD209908090079EE96 /* Surface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Surface.h; sourceTree = ""; }; 930EEA6924FC00940070314E /* ScenarioSelect.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScenarioSelect.cpp; sourceTree = ""; }; + 9329D51F240C17C60054301C /* BenchUpdate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BenchUpdate.cpp; sourceTree = ""; }; + 932A20CD22D73CEE00C57EDB /* GuestSetNameAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = GuestSetNameAction.hpp; sourceTree = ""; }; + 932A20CE22D73CEE00C57EDB /* RideSetVehiclesAction.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = RideSetVehiclesAction.hpp; sourceTree = ""; }; 932A20CF22D73CEE00C57EDB /* GameActionCompat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameActionCompat.cpp; sourceTree = ""; }; 932A20D322D73CEF00C57EDB /* GameActionRegistration.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GameActionRegistration.cpp; sourceTree = ""; }; 932A20F522D73CF300C57EDB /* GameAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GameAction.h; sourceTree = ""; }; @@ -3122,6 +3126,7 @@ children = ( D48AFDB61EF78DBF0081C644 /* BenchGfxCommmands.cpp */, 4C724B2121F0AD790012ADD0 /* BenchSpriteSort.cpp */, + 9329D51F240C17C60054301C /* BenchUpdate.cpp */, F76C83631EC4E7CC00FA49E2 /* CommandLine.cpp */, F76C83641EC4E7CC00FA49E2 /* CommandLine.hpp */, F76C83651EC4E7CC00FA49E2 /* ConvertCommand.cpp */, @@ -4659,6 +4664,7 @@ C688785820289A0A0084B384 /* Balloon.cpp in Sources */, C688788820289ADE0084B384 /* X8DrawingEngine.cpp in Sources */, 66A10F6A257F1E1800DD651A /* LargeScenerySetColourAction.cpp in Sources */, + 9329D520240C17C60054301C /* BenchUpdate.cpp in Sources */, F775F5381EE3725C001F00E7 /* DummyAudioContext.cpp in Sources */, F775F5351EE35A89001F00E7 /* DummyUiContext.cpp in Sources */, 2A1F4FE1221FF4B0003CA045 /* Audio.cpp in Sources */, diff --git a/src/openrct2/GameState.cpp b/src/openrct2/GameState.cpp index c13ca62b35..595c31aa4b 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_MEASUREMENTS_COUNT; + } } void GameState::CreateStateSnapshot() diff --git a/src/openrct2/GameState.h b/src/openrct2/GameState.h index 414bcc3eee..c4db3cf35c 100644 --- a/src/openrct2/GameState.h +++ b/src/openrct2/GameState.h @@ -11,12 +11,55 @@ #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_MEASUREMENTS_COUNT = 256; + + // In order not to cause allocations, collect multiple samples into single pre-allocated struct + using LogicTimingInfo = std::unordered_map< + LogicTimePart, std::array, LOGIC_UPDATE_MEASUREMENTS_COUNT>>; + + struct LogicTimings + { + LogicTimingInfo TimingInfo; + size_t CurrentIdx{}; + }; + /** * Class to update the state of the map and park. */ @@ -41,7 +84,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 new file mode 100644 index 0000000000..aa9f982bff --- /dev/null +++ b/src/openrct2/cmdline/BenchUpdate.cpp @@ -0,0 +1,161 @@ +/***************************************************************************** + * Copyright (c) 2014-2020 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "CommandLine.hpp" + +#ifdef USE_BENCHMARK + +# include "../Context.h" +# include "../GameState.h" +# include "../OpenRCT2.h" +# include "../platform/Platform2.h" +# include "../platform/platform.h" + +# include +# include +# include +# include +# include + +using namespace OpenRCT2; + +static void BM_update(benchmark::State& state, const std::string& filename) +{ + std::unique_ptr context(CreateContext()); + if (context->Initialise()) + { + if (!filename.empty() && !context->LoadParkFromFile(filename)) + { + state.SkipWithError("Failed to load file!"); + } + + std::vector timings(1); + timings.reserve(100); + int currentTimingIdx = 0; + for (auto _ : state) + { + if (timings[currentTimingIdx].CurrentIdx == (LOGIC_UPDATE_MEASUREMENTS_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) + { + timesum = std::accumulate( + timing.TimingInfo.at(part).begin(), timing.TimingInfo.at(part).end(), std::chrono::duration()); + } + 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 + { + state.SkipWithError("Context initialization failed."); + } +} + +static int CmdlineForBenchSpriteSort(int argc, const char* const* argv) +{ + // Add a baseline test on an empty park + benchmark::RegisterBenchmark("baseline", BM_update, std::string{}); + + // Google benchmark does stuff to argv. It doesn't modify the pointees, + // but it wants to reorder the pointers, so present a copy of them. + std::vector argv_for_benchmark; + + // argv[0] is expected to contain the binary name. It's only for logging purposes, don't bother. + argv_for_benchmark.push_back(nullptr); + + // 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::FileExists(argv[i])) + { + // Register benchmark for sv6 if valid + benchmark::RegisterBenchmark(argv[i], BM_update, argv[i]); + } + else + { + argv_for_benchmark.push_back(const_cast(argv[i])); + } + } + // Update argc with all the changes made + argc = static_cast(argv_for_benchmark.size()); + ::benchmark::Initialize(&argc, &argv_for_benchmark[0]); + if (::benchmark::ReportUnrecognizedArguments(argc, &argv_for_benchmark[0])) + return -1; + + core_init(); + gOpenRCT2Headless = true; + + ::benchmark::RunSpecifiedBenchmarks(); + return 0; +} + +static exitcode_t HandleBenchUpdate(CommandLineArgEnumerator* argEnumerator) +{ + const char* const* argv = static_cast(argEnumerator->GetArguments()) + argEnumerator->GetIndex(); + int32_t argc = argEnumerator->GetCount() - argEnumerator->GetIndex(); + int32_t result = CmdlineForBenchSpriteSort(argc, argv); + if (result < 0) + { + return EXITCODE_FAIL; + } + return EXITCODE_OK; +} + +#else +static exitcode_t HandleBenchUpdate(CommandLineArgEnumerator* argEnumerator) +{ + log_error("Sorry, Google benchmark not enabled in this build"); + return EXITCODE_FAIL; +} +#endif // USE_BENCHMARK + +const CommandLineCommand CommandLine::BenchUpdateCommands[]{ +#ifdef USE_BENCHMARK + DefineCommand( + "", + "... [--benchmark_list_tests={true|false}] [--benchmark_filter=] [--benchmark_min_time=] " + "[--benchmark_repetitions=] [--benchmark_report_aggregates_only={true|false}] " + "[--benchmark_format=] [--benchmark_out=] [--benchmark_out_format=] " + "[--benchmark_color={auto|true|false}] [--benchmark_counters_tabular={true|false}] [--v=]", + nullptr, HandleBenchUpdate), + CommandTableEnd +#else + DefineCommand("", "*** SORRY NOT ENABLED IN THIS BUILD ***", nullptr, HandleBenchUpdate), CommandTableEnd +#endif // USE_BENCHMARK +}; diff --git a/src/openrct2/cmdline/CommandLine.hpp b/src/openrct2/cmdline/CommandLine.hpp index 665cb6dbe3..1df4fa3326 100644 --- a/src/openrct2/cmdline/CommandLine.hpp +++ b/src/openrct2/cmdline/CommandLine.hpp @@ -118,6 +118,7 @@ namespace CommandLine extern const CommandLineCommand SpriteCommands[]; extern const CommandLineCommand BenchGfxCommands[]; extern const CommandLineCommand BenchSpriteSortCommands[]; + extern const CommandLineCommand BenchUpdateCommands[]; extern const CommandLineCommand SimulateCommands[]; extern const CommandLineExample RootExamples[]; diff --git a/src/openrct2/cmdline/RootCommands.cpp b/src/openrct2/cmdline/RootCommands.cpp index a7d1a56c2a..b074e0b785 100644 --- a/src/openrct2/cmdline/RootCommands.cpp +++ b/src/openrct2/cmdline/RootCommands.cpp @@ -140,6 +140,7 @@ const CommandLineCommand CommandLine::RootCommands[] DefineSubCommand("sprite", CommandLine::SpriteCommands ), DefineSubCommand("benchgfx", CommandLine::BenchGfxCommands ), DefineSubCommand("benchspritesort", CommandLine::BenchSpriteSortCommands ), + DefineSubCommand("benchsimulate", CommandLine::BenchUpdateCommands ), DefineSubCommand("simulate", CommandLine::SimulateCommands ), CommandTableEnd }; diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index ae1919630e..267d41f755 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -553,6 +553,7 @@ +