diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index c44e70f382..9186ae1cbc 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -39,6 +39,8 @@ declare global { var scenario: Scenario; /** APIs for the climate and weather. */ var climate: Climate; + /** APIs for performance profiling. */ + var profiler: Profiler; /** * APIs for creating and editing title sequences. * These will only be available to clients that are not running headless mode. @@ -2697,4 +2699,21 @@ declare global { start: number; count: number; } + + interface Profiler { + getData(): ProfiledFunction[]; + start(): void; + stop(): void; + reset(): void; + } + + interface ProfiledFunction { + readonly name: string; + readonly callCount: number; + readonly minTime: number; + readonly maxTime: number; + readonly totalTime: number; + readonly parents: number[]; + readonly children: number[]; + } } diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 3c8822a3bd..a64056d425 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -59,6 +59,7 @@ #include "platform/Crash.h" #include "platform/Platform2.h" #include "platform/platform.h" +#include "profiling/Profiling.h" #include "ride/TrackData.h" #include "ride/TrackDesignRepository.h" #include "scenario/Scenario.h" @@ -1063,9 +1064,7 @@ namespace OpenRCT2 if (ShouldDraw()) { - _drawingEngine->BeginDraw(); - _painter->Paint(*_drawingEngine); - _drawingEngine->EndDraw(); + Draw(); } } @@ -1099,14 +1098,23 @@ namespace OpenRCT2 const float alpha = std::min(_ticksAccumulator / GAME_UPDATE_TIME_MS, 1.0f); tweener.Tween(alpha); - _drawingEngine->BeginDraw(); - _painter->Paint(*_drawingEngine); - _drawingEngine->EndDraw(); + Draw(); } } + void Draw() + { + PROFILED_FUNCTION(); + + _drawingEngine->BeginDraw(); + _painter->Paint(*_drawingEngine); + _drawingEngine->EndDraw(); + } + void Tick() { + PROFILED_FUNCTION(); + // TODO: This variable has been never "variable" in time, some code expects // this to be 40Hz (25 ms). Refactor this once the UI is decoupled. gCurrentDeltaTime = static_cast(GAME_UPDATE_TIME_MS * 1000.0f); diff --git a/src/openrct2/GameState.cpp b/src/openrct2/GameState.cpp index 5f627467c8..71d4dc3ab2 100644 --- a/src/openrct2/GameState.cpp +++ b/src/openrct2/GameState.cpp @@ -27,6 +27,7 @@ #include "management/NewsItem.h" #include "network/network.h" #include "platform/Platform2.h" +#include "profiling/Profiling.h" #include "ride/Vehicle.h" #include "scenario/Scenario.h" #include "scripting/ScriptEngine.h" @@ -55,6 +56,8 @@ GameState::GameState() */ void GameState::InitAll(int32_t mapSize) { + PROFILED_FUNCTION(); + gInMapInitCode = true; gCurrentTicks = 0; @@ -93,6 +96,8 @@ void GameState::InitAll(int32_t mapSize) */ void GameState::Tick() { + PROFILED_FUNCTION(); + gInUpdateCode = true; // Normal game play will update only once every GAME_UPDATE_TIME_MS @@ -241,6 +246,8 @@ void GameState::Tick() void GameState::UpdateLogic(LogicTimings* timings) { + PROFILED_FUNCTION(); + auto start_time = std::chrono::high_resolution_clock::now(); auto report_time = [timings, start_time](LogicTimePart part) { @@ -386,6 +393,8 @@ void GameState::UpdateLogic(LogicTimings* timings) void GameState::CreateStateSnapshot() { + PROFILED_FUNCTION(); + IGameStateSnapshots* snapshots = GetContext()->GetGameStateSnapshots(); auto& snapshot = snapshots->CreateSnapshot(); diff --git a/src/openrct2/actions/GameAction.cpp b/src/openrct2/actions/GameAction.cpp index 96df9aab6e..e024d4906a 100644 --- a/src/openrct2/actions/GameAction.cpp +++ b/src/openrct2/actions/GameAction.cpp @@ -19,6 +19,7 @@ #include "../localisation/Localisation.h" #include "../network/network.h" #include "../platform/platform.h" +#include "../profiling/Profiling.h" #include "../scenario/Scenario.h" #include "../scripting/Duktape.hpp" #include "../scripting/HookEngine.h" diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp index 5c661222e0..4a93ade023 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -19,6 +19,7 @@ #include "../entity/Staff.h" #include "../interface/Viewport.h" #include "../peep/RideUseSystem.h" +#include "../profiling/Profiling.h" #include "../ride/Vehicle.h" #include "../scenario/Scenario.h" #include "Balloon.h" @@ -395,6 +396,8 @@ template void MiscUpdateAllTypes() */ void UpdateAllMiscEntities() { + PROFILED_FUNCTION(); + MiscUpdateAllTypes< SteamParticle, MoneyEffect, VehicleCrashParticle, ExplosionCloud, CrashSplashParticle, ExplosionFlare, JumpingFountain, Balloon, Duck>(); diff --git a/src/openrct2/entity/Peep.cpp b/src/openrct2/entity/Peep.cpp index 2f9546b031..4f0a7f7bcd 100644 --- a/src/openrct2/entity/Peep.cpp +++ b/src/openrct2/entity/Peep.cpp @@ -32,6 +32,7 @@ #include "../network/network.h" #include "../paint/Paint.h" #include "../peep/GuestPathfinding.h" +#include "../profiling/Profiling.h" #include "../ride/Ride.h" #include "../ride/RideData.h" #include "../ride/ShopItem.h" @@ -197,6 +198,8 @@ int32_t peep_get_staff_count() */ void peep_update_all() { + PROFILED_FUNCTION(); + if (gScreenFlags & SCREEN_FLAGS_EDITOR) return; @@ -1163,6 +1166,8 @@ void peep_stop_crowd_noise() */ void peep_update_crowd_noise() { + PROFILED_FUNCTION(); + if (OpenRCT2::Audio::gGameSoundsOff) return; diff --git a/src/openrct2/interface/InteractiveConsole.cpp b/src/openrct2/interface/InteractiveConsole.cpp index 723276f98e..0e510f85c9 100644 --- a/src/openrct2/interface/InteractiveConsole.cpp +++ b/src/openrct2/interface/InteractiveConsole.cpp @@ -45,6 +45,7 @@ #include "../object/ObjectManager.h" #include "../object/ObjectRepository.h" #include "../platform/platform.h" +#include "../profiling/Profiling.h" #include "../ride/Ride.h" #include "../ride/RideData.h" #include "../ride/Vehicle.h" @@ -1725,6 +1726,53 @@ static int32_t cc_add_news_item([[maybe_unused]] InteractiveConsole& console, [[ return 0; } +static int32_t cc_profiler_reset([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv) +{ + OpenRCT2::Profiling::ResetData(); + return 0; +} +static int32_t cc_profiler_start([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv) +{ + if (!OpenRCT2::Profiling::IsEnabled()) + console.WriteLine("Started profiler"); + OpenRCT2::Profiling::Enable(); + return 0; +} + +static int32_t cc_profiler_exportcsv([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv) +{ + if (argv.size() < 1) + { + console.WriteLineError("Missing argument: "); + return 1; + } + + const auto& csvFilePath = argv[0]; + if (!OpenRCT2::Profiling::ExportCSV(csvFilePath)) + { + console.WriteFormatLine("Unable to export CSV file to %s", csvFilePath.c_str()); + return 1; + } + + console.WriteFormatLine("Wrote file CSV file: \"%s\"", csvFilePath.c_str()); + return 0; +} + +static int32_t cc_profiler_stop([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv) +{ + if (OpenRCT2::Profiling::IsEnabled()) + console.WriteLine("Stopped profiler"); + OpenRCT2::Profiling::Disable(); + + // Export to CSV if argument is provided. + if (argv.size() >= 1) + { + return cc_profiler_exportcsv(console, argv); + } + + return 0; +} + using console_command_func = int32_t (*)(InteractiveConsole& console, const arguments_t& argv); struct console_command { @@ -1829,6 +1877,10 @@ static constexpr const console_command console_command_table[] = { "replay_normalise " }, { "mp_desync", cc_mp_desync, "Forces a multiplayer desync", "cc_mp_desync [desync_type, 0 = Random t-shirt color on random guest, 1 = Remove random guest ]" }, + { "profiler_reset", cc_profiler_reset, "Resets the profiler data.", "profiler_reset" }, + { "profiler_start", cc_profiler_start, "Starts the profiler.", "profiler_start" }, + { "profiler_stop", cc_profiler_stop, "Stops the profiler.", "profiler_stop []" }, + { "profiler_exportcsv", cc_profiler_exportcsv, "Exports the current profiler data.", "profiler_exportcsv " }, }; static int32_t cc_windows(InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv) diff --git a/src/openrct2/interface/Viewport.cpp b/src/openrct2/interface/Viewport.cpp index eb96e09e59..eac7bc4f33 100644 --- a/src/openrct2/interface/Viewport.cpp +++ b/src/openrct2/interface/Viewport.cpp @@ -22,6 +22,7 @@ #include "../entity/Guest.h" #include "../entity/Staff.h" #include "../paint/Paint.h" +#include "../profiling/Profiling.h" #include "../ride/Ride.h" #include "../ride/TrackDesign.h" #include "../ride/Vehicle.h" @@ -950,6 +951,8 @@ void viewport_paint( const rct_viewport* viewport, rct_drawpixelinfo* dpi, const ScreenRect& screenRect, std::vector* recorded_sessions) { + PROFILED_FUNCTION(); + const uint32_t viewFlags = viewport->flags; uint32_t width = screenRect.GetWidth(); uint32_t height = screenRect.GetHeight(); diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 9888a01518..f414f419ab 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -315,6 +315,8 @@ + + @@ -449,6 +451,7 @@ + @@ -791,6 +794,7 @@ + @@ -947,4 +951,4 @@ - + \ No newline at end of file diff --git a/src/openrct2/localisation/Localisation.Date.cpp b/src/openrct2/localisation/Localisation.Date.cpp index aed68d3158..bc0a1ef363 100644 --- a/src/openrct2/localisation/Localisation.Date.cpp +++ b/src/openrct2/localisation/Localisation.Date.cpp @@ -8,6 +8,7 @@ *****************************************************************************/ #include "../Game.h" +#include "../profiling/Profiling.h" #include "../util/Math.hpp" #include "Date.h" #include "StringIds.h" @@ -76,6 +77,8 @@ void date_set(int32_t year, int32_t month, int32_t day) void date_update() { + PROFILED_FUNCTION(); + int32_t monthTicks = gDateMonthTicks + 4; if (monthTicks >= TICKS_PER_MONTH) { diff --git a/src/openrct2/management/NewsItem.cpp b/src/openrct2/management/NewsItem.cpp index 7059fe1bb6..520de9cdb4 100644 --- a/src/openrct2/management/NewsItem.cpp +++ b/src/openrct2/management/NewsItem.cpp @@ -21,6 +21,7 @@ #include "../localisation/Formatter.h" #include "../localisation/Localisation.h" #include "../management/Research.h" +#include "../profiling/Profiling.h" #include "../ride/Ride.h" #include "../ride/Vehicle.h" #include "../util/Util.h" @@ -153,6 +154,8 @@ bool News::ItemQueues::CurrentShouldBeArchived() const */ void News::UpdateCurrentItem() { + PROFILED_FUNCTION(); + // Check if there is a current news item if (gNewsItems.IsEmpty()) return; diff --git a/src/openrct2/management/Research.cpp b/src/openrct2/management/Research.cpp index 0f13a4fbcb..2b780c0f9e 100644 --- a/src/openrct2/management/Research.cpp +++ b/src/openrct2/management/Research.cpp @@ -23,6 +23,7 @@ #include "../localisation/StringIds.h" #include "../object/ObjectList.h" #include "../object/RideObject.h" +#include "../profiling/Profiling.h" #include "../ride/Ride.h" #include "../ride/RideData.h" #include "../ride/RideEntry.h" @@ -312,6 +313,8 @@ void research_finish_item(ResearchItem* researchItem) */ void research_update() { + PROFILED_FUNCTION(); + int32_t editorScreenFlags, researchLevel, currentResearchProgress; editorScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER; diff --git a/src/openrct2/paint/Paint.cpp b/src/openrct2/paint/Paint.cpp index 0a50fa6543..7598f39dfb 100644 --- a/src/openrct2/paint/Paint.cpp +++ b/src/openrct2/paint/Paint.cpp @@ -17,6 +17,7 @@ #include "../localisation/Localisation.h" #include "../localisation/LocalisationService.h" #include "../paint/Painter.h" +#include "../profiling/Profiling.h" #include "../util/Math.hpp" #include "Paint.Entity.h" #include "tile_element/Paint.TileElement.h" @@ -466,6 +467,7 @@ template static void PaintSessionArrange(PaintSessionCore& sessio */ void PaintSessionArrange(PaintSessionCore& session) { + PROFILED_FUNCTION(); switch (session.CurrentRotation) { case 0: diff --git a/src/openrct2/profiling/Profiling.cpp b/src/openrct2/profiling/Profiling.cpp new file mode 100644 index 0000000000..b8a015951e --- /dev/null +++ b/src/openrct2/profiling/Profiling.cpp @@ -0,0 +1,174 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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 "Profiling.h" + +#include +#include +#include +#include +#include + +namespace OpenRCT2::Profiling +{ + inline static bool _enabled = false; + + void Enable() + { + _enabled = true; + } + + void Disable() + { + _enabled = false; + } + + bool IsEnabled() + { + return _enabled; + } + + namespace Detail + { + using Clock = std::chrono::high_resolution_clock; + using Tp = Clock::time_point; + + struct FunctionEntry + { + FunctionInternal* Parent; + FunctionInternal* Func; + Tp EntryTime; + + FunctionEntry(FunctionInternal* parent, FunctionInternal* func, const Tp& entryTime) + : Parent(parent) + , Func(func) + , EntryTime(entryTime) + { + } + }; + + static thread_local std::stack _callStack; + + void FunctionEnter(Function& func) + { + const auto entryTime = Clock::now(); + + auto& funcInternal = static_cast(func); + funcInternal.CallCount++; + + FunctionInternal* parent = nullptr; + + if (!_callStack.empty()) + parent = _callStack.top().Func; + + _callStack.emplace(parent, &funcInternal, entryTime); + } + + void FunctionExit(Function& func) + { + const auto exitTime = Clock::now(); + + assert(!_callStack.empty()); + + auto& stackEntry = _callStack.top(); + + const auto deltaTime = exitTime - stackEntry.EntryTime; + + // Elapsed microseconds. + const auto elapsedTimeUs = std::chrono::duration_cast(deltaTime).count() / 1000.0; + + auto* funcData = stackEntry.Func; + + // We don't need a lock for this, we only have a fixed window. + const auto sampleEntryIdx = funcData->SampleIterator++ % funcData->Samples.size(); + funcData->Samples[sampleEntryIdx] = elapsedTimeUs; + + if (stackEntry.Parent) + { + std::scoped_lock lock(stackEntry.Parent->Mutex); + stackEntry.Parent->Children.insert(funcData); + } + + // This requires locking. + { + std::scoped_lock lock(funcData->Mutex); + + if (stackEntry.Parent) + funcData->Parents.insert(stackEntry.Parent); + + if (funcData->MinTimeUs == 0.0) + funcData->MinTimeUs = elapsedTimeUs; + else + funcData->MinTimeUs = std::min(elapsedTimeUs, funcData->MinTimeUs); + + funcData->MaxTimeUs = std::max(elapsedTimeUs, funcData->MaxTimeUs); + funcData->TotalTimeUs += elapsedTimeUs; + } + + _callStack.pop(); + } + + std::vector& GetRegistry() + { + static std::vector Registry; + return Registry; + } + + } // namespace Detail + + const std::vector& GetData() + { + return Detail::GetRegistry(); + } + + void ResetData() + { + for (auto* func : Detail::GetRegistry()) + { + auto* funcInternal = static_cast(func); + + std::scoped_lock lock(funcInternal->Mutex); + funcInternal->CallCount = 0; + funcInternal->MinTimeUs = 0.0; + funcInternal->MaxTimeUs = 0.0; + funcInternal->SampleIterator = 0; + funcInternal->Children.clear(); + funcInternal->Parents.clear(); + } + } + + bool ExportCSV(const std::string& filePath) + { + std::ofstream out(filePath); + if (!out.is_open()) + return false; + + out << "function_name;calls;min_microseconds;max_microseconds;average_microseconds\n"; + out << std::setprecision(12); + + const auto& data = GetData(); + for (auto* func : data) + { + out << "\"" << func->GetName() << "\"" + << ";"; + out << func->GetCallCount() << ";"; + out << func->GetMinTime() << ";"; + out << func->GetMaxTime() << ";"; + + double avg = 0.0; + if (func->GetCallCount() > 0) + avg = func->GetTotalTime() / func->GetCallCount(); + + out << avg << "\n"; + } + + return true; + } + +} // namespace OpenRCT2::Profiling diff --git a/src/openrct2/profiling/Profiling.h b/src/openrct2/profiling/Profiling.h new file mode 100644 index 0000000000..7f3bc8ba74 --- /dev/null +++ b/src/openrct2/profiling/Profiling.h @@ -0,0 +1,193 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ +#pragma once + +#include "ProfilingMacros.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace OpenRCT2::Profiling +{ + void Enable(); + void Disable(); + bool IsEnabled(); + + struct Function + { + virtual ~Function() = default; + + // This returns the full function prototype not just the name. + virtual const char* GetName() const noexcept = 0; + + // Total calls made to the function. + virtual uint64_t GetCallCount() const noexcept = 0; + + // Returns a small window of the most recent times. + virtual std::vector GetTimeSamples() const = 0; + + // Returns all time accumulated in microseconds. + virtual double GetTotalTime() const = 0; + + // Returns the min. time in microseconds. + virtual double GetMinTime() const = 0; + + // Returns the max. time in microseconds. + virtual double GetMaxTime() const = 0; + + // Returns a list of all functions that called this function. + virtual std::vector GetParents() const = 0; + + // Returns a list of function this function is calling. + virtual std::vector GetChildren() const = 0; + }; + + namespace Detail + { + static constexpr auto MaxSamplesSize = 1024; + static constexpr auto MaxNameSize = 250; + + std::vector& GetRegistry(); + + struct FunctionInternal : Function + { + FunctionInternal() + { + GetRegistry().push_back(this); + } + + virtual ~FunctionInternal() = default; + + // Functions marked MT require locking, this is per function basis. + mutable std::mutex Mutex; + + std::array Name{}; + + // Call count of function. + std::atomic CallCount{}; + + // Function times in microseconds. + std::array Samples{}; + + // Used internally to write into Samples without a lock. + std::atomic SampleIterator{}; + + double MinTimeUs{}; + + double MaxTimeUs{}; + + double TotalTimeUs{}; + + // Functions that called us. + std::unordered_set Parents; + + // Functions that this function called. + std::unordered_set Children; + + uint64_t GetCallCount() const noexcept override + { + return CallCount.load(); + } + + std::vector GetTimeSamples() const override + { + const auto numSamples = std::min(SampleIterator.load(), Samples.size()); + return { Samples.begin(), Samples.begin() + numSamples }; + } + + std::vector GetParents() const override + { + std::scoped_lock lock(Mutex); + return { Parents.begin(), Parents.end() }; + } + + std::vector GetChildren() const override + { + std::scoped_lock lock(Mutex); + return { Children.begin(), Children.end() }; + } + + double GetTotalTime() const override + { + std::scoped_lock lock(Mutex); + return TotalTimeUs; + } + + double GetMinTime() const override + { + std::scoped_lock lock(Mutex); + return MinTimeUs; + } + + double GetMaxTime() const override + { + std::scoped_lock lock(Mutex); + return MaxTimeUs; + } + }; + + template struct FunctionWrapper : FunctionInternal + { + const char* GetName() const noexcept override + { + return TName::Str(); + } + }; + + // Wrapper to avoid static initialization inside the function. + // This avoids the compiler generating thread-safe initialization + // by making a unique type per function which hosts a global using + // the inline keyword for the variable (C++17). + template struct Storage + { + static inline FunctionWrapper Data; + }; + + void FunctionEnter(Function& func); + void FunctionExit(Function& func); + + } // namespace Detail + + template class ScopedProfiling + { + bool _enabled; + T& _func; + + public: + ScopedProfiling(T& func) + : _enabled{ IsEnabled() } + , _func(func) + { + if (_enabled) + { + Detail::FunctionEnter(_func); + } + } + ~ScopedProfiling() + { + if (!_enabled) + return; + Detail::FunctionExit(_func); + } + }; + + // Clears all the current data of each function. + void ResetData(); + + // Returns all functions. + const std::vector& GetData(); + + bool ExportCSV(const std::string& filePath); + +} // namespace OpenRCT2::Profiling diff --git a/src/openrct2/profiling/ProfilingMacros.hpp b/src/openrct2/profiling/ProfilingMacros.hpp new file mode 100644 index 0000000000..14bf242031 --- /dev/null +++ b/src/openrct2/profiling/ProfilingMacros.hpp @@ -0,0 +1,42 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ +#pragma once + +namespace OpenRCT2::Profiling +{ +#if defined(__clang__) || defined(__GNUC__) +# define PROFILING_FUNC_NAME __PRETTY_FUNCTION__ +#elif defined(_MSC_VER) +# define PROFILING_FUNC_NAME __FUNCSIG__ +#else +# error "Unsupported compiler" +#endif + +#define PROFILED_FUNCTION_NAME(func) \ + static constexpr auto _profiling_func_name = func; \ + struct Profiler_FunctionLiteral \ + { \ + static constexpr const char* Str() \ + { \ + return _profiling_func_name; \ + } \ + }; + +#if defined(__clang_major__) && __clang_major__ <= 5 + // Clang 5 crashes using the profiler, we need to disable it. +# define PROFILED_FUNCTION() +#else + +# define PROFILED_FUNCTION() \ + PROFILED_FUNCTION_NAME(PROFILING_FUNC_NAME) \ + static auto& _profiling_func = ::OpenRCT2::Profiling::Detail::Storage::Data; \ + ::OpenRCT2::Profiling::ScopedProfiling _profiling_scope(_profiling_func); +#endif + +} // namespace OpenRCT2::Profiling diff --git a/src/openrct2/ride/Ride.cpp b/src/openrct2/ride/Ride.cpp index 5c35bb9e53..3d5d029abc 100644 --- a/src/openrct2/ride/Ride.cpp +++ b/src/openrct2/ride/Ride.cpp @@ -42,6 +42,7 @@ #include "../object/RideObject.h" #include "../object/StationObject.h" #include "../paint/VirtualFloor.h" +#include "../profiling/Profiling.h" #include "../rct1/RCT1.h" #include "../scenario/Scenario.h" #include "../ui/UiContext.h" @@ -923,6 +924,8 @@ void reset_all_ride_build_dates() */ void Ride::UpdateAll() { + PROFILED_FUNCTION(); + // Remove all rides if scenario editor if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) { @@ -1978,6 +1981,8 @@ static void ride_measurement_update(Ride& ride, RideMeasurement& measurement) */ void ride_measurements_update() { + PROFILED_FUNCTION(); + if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) return; diff --git a/src/openrct2/ride/RideRatings.cpp b/src/openrct2/ride/RideRatings.cpp index 410690c4fb..5056492591 100644 --- a/src/openrct2/ride/RideRatings.cpp +++ b/src/openrct2/ride/RideRatings.cpp @@ -14,6 +14,7 @@ #include "../OpenRCT2.h" #include "../interface/Window.h" #include "../localisation/Date.h" +#include "../profiling/Profiling.h" #include "../scripting/ScriptEngine.h" #include "../world/Footpath.h" #include "../world/Map.h" @@ -118,6 +119,8 @@ void ride_ratings_update_ride(const Ride& ride) */ void ride_ratings_update_all() { + PROFILED_FUNCTION(); + if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) return; diff --git a/src/openrct2/ride/Vehicle.cpp b/src/openrct2/ride/Vehicle.cpp index e36e021834..0f82376568 100644 --- a/src/openrct2/ride/Vehicle.cpp +++ b/src/openrct2/ride/Vehicle.cpp @@ -25,6 +25,7 @@ #include "../localisation/Localisation.h" #include "../management/NewsItem.h" #include "../platform/platform.h" +#include "../profiling/Profiling.h" #include "../rct12/RCT12.h" #include "../scenario/Scenario.h" #include "../scripting/HookEngine.h" @@ -1139,6 +1140,8 @@ static void UpdateSound( */ void vehicle_sounds_update() { + PROFILED_FUNCTION(); + if (!OpenRCT2::Audio::IsAvailable()) return; @@ -1224,6 +1227,8 @@ void vehicle_sounds_update() */ void vehicle_update_all() { + PROFILED_FUNCTION(); + if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) return; diff --git a/src/openrct2/scenario/Scenario.cpp b/src/openrct2/scenario/Scenario.cpp index eb54d46dfb..7549477692 100644 --- a/src/openrct2/scenario/Scenario.cpp +++ b/src/openrct2/scenario/Scenario.cpp @@ -37,6 +37,7 @@ #include "../object/ObjectList.h" #include "../object/ObjectManager.h" #include "../platform/platform.h" +#include "../profiling/Profiling.h" #include "../rct1/RCT1.h" #include "../rct12/RCT12.h" #include "../ride/Ride.h" @@ -399,6 +400,8 @@ static void scenario_update_daynight_cycle() */ void scenario_update() { + PROFILED_FUNCTION(); + if (gScreenFlags == SCREEN_FLAGS_PLAYING) { if (date_is_day_start(gDateMonthTicks)) diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index 5cb475353e..a31c5a86de 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -137,6 +137,13 @@ namespace OpenRCT2::Scripting duk_put_prop_string(_ctx, _idx, name); } + void Set(const char* name, double value) + { + EnsureObjectPushed(); + duk_push_number(_ctx, value); + duk_put_prop_string(_ctx, _idx, name); + } + void Set(const char* name, std::string_view value) { EnsureObjectPushed(); diff --git a/src/openrct2/scripting/Plugin.cpp b/src/openrct2/scripting/Plugin.cpp index 084ad97ce6..854c08da66 100644 --- a/src/openrct2/scripting/Plugin.cpp +++ b/src/openrct2/scripting/Plugin.cpp @@ -41,7 +41,7 @@ void Plugin::Load() LoadCodeFromFile(); } - std::string projectedVariables = "console,context,date,map,network,park"; + std::string projectedVariables = "console,context,date,map,network,park,profiler"; if (!gOpenRCT2Headless) { projectedVariables += ",ui"; diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 155edfaf8b..2e3d259e7a 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -34,6 +34,7 @@ # include "bindings/game/ScConsole.hpp" # include "bindings/game/ScContext.hpp" # include "bindings/game/ScDisposable.hpp" +# include "bindings/game/ScProfiler.hpp" # include "bindings/network/ScNetwork.hpp" # include "bindings/network/ScPlayer.hpp" # include "bindings/network/ScPlayerGroup.hpp" @@ -402,6 +403,7 @@ void ScriptEngine::Initialise() ScParkMessage::Register(ctx); ScPlayer::Register(ctx); ScPlayerGroup::Register(ctx); + ScProfiler::Register(ctx); ScRide::Register(ctx); ScRideStation::Register(ctx); ScRideObject::Register(ctx); @@ -429,6 +431,7 @@ void ScriptEngine::Initialise() dukglue_register_global(ctx, std::make_shared(ctx), "map"); dukglue_register_global(ctx, std::make_shared(ctx), "network"); dukglue_register_global(ctx, std::make_shared(), "park"); + dukglue_register_global(ctx, std::make_shared(ctx), "profiler"); dukglue_register_global(ctx, std::make_shared(), "scenario"); _initialised = true; @@ -648,6 +651,8 @@ void ScriptEngine::StopPlugins() void ScriptEngine::Tick() { + PROFILED_FUNCTION(); + if (!_initialised) { Initialise(); diff --git a/src/openrct2/scripting/bindings/game/ScProfiler.hpp b/src/openrct2/scripting/bindings/game/ScProfiler.hpp new file mode 100644 index 0000000000..1f176d8a86 --- /dev/null +++ b/src/openrct2/scripting/bindings/game/ScProfiler.hpp @@ -0,0 +1,98 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 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. + *****************************************************************************/ + +#pragma once + +#ifdef ENABLE_SCRIPTING + +# include "../../../profiling/Profiling.h" +# include "../../Duktape.hpp" + +namespace OpenRCT2::Scripting +{ + class ScProfiler + { + private: + duk_context* _ctx{}; + + public: + ScProfiler(duk_context* ctx) + : _ctx(ctx) + { + } + + private: + DukValue getData() + { + const auto& data = OpenRCT2::Profiling::GetData(); + duk_push_array(_ctx); + duk_uarridx_t index = 0; + for (const auto& f : data) + { + DukObject obj(_ctx); + obj.Set("name", f->GetName()); + obj.Set("callCount", f->GetCallCount()); + obj.Set("minTime", f->GetMinTime()); + obj.Set("maxTime", f->GetMaxTime()); + obj.Set("totalTime", f->GetTotalTime()); + obj.Set("parents", GetFunctionIndexArray(data, f->GetParents())); + obj.Set("children", GetFunctionIndexArray(data, f->GetChildren())); + obj.Take().push(); + duk_put_prop_index(_ctx, /* duk stack index */ -2, index); + index++; + } + return DukValue::take_from_stack(_ctx); + } + + DukValue GetFunctionIndexArray( + const std::vector& all, const std::vector& items) + { + duk_push_array(_ctx); + duk_uarridx_t index = 0; + for (const auto& item : items) + { + auto it = std::find(all.begin(), all.end(), item); + if (it != all.end()) + { + auto value = static_cast(std::distance(all.begin(), it)); + duk_push_int(_ctx, value); + duk_put_prop_index(_ctx, /* duk stack index */ -2, index); + index++; + } + } + return DukValue::take_from_stack(_ctx); + } + + void start() + { + OpenRCT2::Profiling::Enable(); + } + + void stop() + { + OpenRCT2::Profiling::Disable(); + } + + void reset() + { + OpenRCT2::Profiling::ResetData(); + } + + public: + static void Register(duk_context* ctx) + { + dukglue_register_method(ctx, &ScProfiler::getData, "getData"); + dukglue_register_method(ctx, &ScProfiler::start, "start"); + dukglue_register_method(ctx, &ScProfiler::stop, "stop"); + dukglue_register_method(ctx, &ScProfiler::reset, "reset"); + } + }; +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2/world/Climate.cpp b/src/openrct2/world/Climate.cpp index 101b1ae0b8..9fd16ebed8 100644 --- a/src/openrct2/world/Climate.cpp +++ b/src/openrct2/world/Climate.cpp @@ -19,6 +19,7 @@ #include "../drawing/Drawing.h" #include "../interface/Window.h" #include "../localisation/Date.h" +#include "../profiling/Profiling.h" #include "../scenario/Scenario.h" #include "../sprites.h" #include "../util/Util.h" @@ -113,6 +114,8 @@ void climate_reset(ClimateType climate) */ void climate_update() { + PROFILED_FUNCTION(); + // Only do climate logic if playing (not in scenario editor or title screen) if (gScreenFlags & (~SCREEN_FLAGS_PLAYING)) return; @@ -208,6 +211,8 @@ void climate_force_weather(WeatherType weather) void climate_update_sound() { + PROFILED_FUNCTION(); + if (!OpenRCT2::Audio::IsAvailable()) return; diff --git a/src/openrct2/world/Map.cpp b/src/openrct2/world/Map.cpp index f11020ba02..1f1aaaa8e7 100644 --- a/src/openrct2/world/Map.cpp +++ b/src/openrct2/world/Map.cpp @@ -29,6 +29,7 @@ #include "../network/network.h" #include "../object/ObjectManager.h" #include "../object/TerrainSurfaceObject.h" +#include "../profiling/Profiling.h" #include "../ride/RideConstruction.h" #include "../ride/RideData.h" #include "../ride/Track.h" @@ -727,6 +728,8 @@ bool map_coord_is_connected(const TileCoordsXYZ& loc, uint8_t faceDirection) */ void map_update_path_wide_flags() { + PROFILED_FUNCTION(); + if (gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER)) { return; @@ -1308,6 +1311,8 @@ TileElement* tile_element_insert(const CoordsXYZ& loc, int32_t occupiedQuadrants */ void map_update_tiles() { + PROFILED_FUNCTION(); + int32_t ignoreScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR | SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER; if (gScreenFlags & ignoreScreenFlags) return; @@ -1349,6 +1354,8 @@ void map_update_tiles() void map_remove_provisional_elements() { + PROFILED_FUNCTION(); + if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1) { footpath_provisional_remove(); @@ -1370,6 +1377,8 @@ void map_remove_provisional_elements() void map_restore_provisional_elements() { + PROFILED_FUNCTION(); + if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1) { gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_1; diff --git a/src/openrct2/world/MapAnimation.cpp b/src/openrct2/world/MapAnimation.cpp index bc564fc0d8..8eac4b8000 100644 --- a/src/openrct2/world/MapAnimation.cpp +++ b/src/openrct2/world/MapAnimation.cpp @@ -15,6 +15,7 @@ #include "../entity/Peep.h" #include "../interface/Viewport.h" #include "../object/StationObject.h" +#include "../profiling/Profiling.h" #include "../ride/Ride.h" #include "../ride/RideData.h" #include "../ride/Track.h" @@ -69,6 +70,8 @@ void map_animation_create(int32_t type, const CoordsXYZ& loc) */ void map_animation_invalidate_all() { + PROFILED_FUNCTION(); + auto it = _mapAnimations.begin(); while (it != _mapAnimations.end()) { diff --git a/src/openrct2/world/Park.cpp b/src/openrct2/world/Park.cpp index 61c7be48ed..e713740a50 100644 --- a/src/openrct2/world/Park.cpp +++ b/src/openrct2/world/Park.cpp @@ -30,6 +30,7 @@ #include "../management/Marketing.h" #include "../management/Research.h" #include "../network/network.h" +#include "../profiling/Profiling.h" #include "../ride/Ride.h" #include "../ride/RideData.h" #include "../ride/ShopItem.h" @@ -307,6 +308,8 @@ void Park::Initialise() void Park::Update(const Date& date) { + PROFILED_FUNCTION(); + // Every ~13 seconds if (gCurrentTicks % 512 == 0) {