diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 6e177bb0fa..c397097321 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -63,6 +63,7 @@ add_openmw_dir (mwlua luabindings localscripts playerscripts objectbindings cellbindings asyncbindings camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/actor types/container types/weapon types/npc types/creature types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair + worker ) add_openmw_dir (mwsound diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2d989bf797..185acaf179 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -49,6 +49,7 @@ #include "mwgui/windowmanagerimp.hpp" #include "mwlua/luamanagerimp.hpp" +#include "mwlua/worker.hpp" #include "mwscript/interpretercontext.hpp" #include "mwscript/scriptmanagerimp.hpp" @@ -70,6 +71,8 @@ #include "mwstate/statemanagerimp.hpp" +#include "profile.hpp" + namespace { void checkSDLError(int ret) @@ -78,140 +81,6 @@ namespace Log(Debug::Error) << "SDL error: " << SDL_GetError(); } - struct UserStats - { - const std::string mLabel; - const std::string mBegin; - const std::string mEnd; - const std::string mTaken; - - UserStats(const std::string& label, const std::string& prefix) - : mLabel(label) - , mBegin(prefix + "_time_begin") - , mEnd(prefix + "_time_end") - , mTaken(prefix + "_time_taken") - { - } - }; - - enum class UserStatsType : std::size_t - { - Input, - Sound, - State, - Script, - Mechanics, - Physics, - PhysicsWorker, - World, - Gui, - Lua, - LuaSyncUpdate, - Number, - }; - - template - struct UserStatsValue - { - static const UserStats sValue; - }; - - template <> - const UserStats UserStatsValue::sValue{ "Input", "input" }; - - template <> - const UserStats UserStatsValue::sValue{ "Sound", "sound" }; - - template <> - const UserStats UserStatsValue::sValue{ "State", "state" }; - - template <> - const UserStats UserStatsValue::sValue{ "Script", "script" }; - - template <> - const UserStats UserStatsValue::sValue{ "Mech", "mechanics" }; - - template <> - const UserStats UserStatsValue::sValue{ "Phys", "physics" }; - - template <> - const UserStats UserStatsValue::sValue{ " -Async", "physicsworker" }; - - template <> - const UserStats UserStatsValue::sValue{ "World", "world" }; - - template <> - const UserStats UserStatsValue::sValue{ "Gui", "gui" }; - - template <> - const UserStats UserStatsValue::sValue{ "Lua", "lua" }; - - template <> - const UserStats UserStatsValue::sValue{ " -Sync", "luasyncupdate" }; - - template - struct ForEachUserStatsValue - { - template - static void apply(F&& f) - { - f(UserStatsValue::sValue); - using Next = ForEachUserStatsValue(static_cast(type) + 1)>; - Next::apply(std::forward(f)); - } - }; - - template <> - struct ForEachUserStatsValue - { - template - static void apply(F&&) - { - } - }; - - template - void forEachUserStatsValue(F&& f) - { - ForEachUserStatsValue(0)>::apply(std::forward(f)); - } - - template - class ScopedProfile - { - public: - ScopedProfile(osg::Timer_t frameStart, unsigned int frameNumber, const osg::Timer& timer, osg::Stats& stats) - : mScopeStart(timer.tick()) - , mFrameStart(frameStart) - , mFrameNumber(frameNumber) - , mTimer(timer) - , mStats(stats) - { - } - - ScopedProfile(const ScopedProfile&) = delete; - ScopedProfile& operator=(const ScopedProfile&) = delete; - - ~ScopedProfile() - { - if (!mStats.collectStats("engine")) - return; - const osg::Timer_t end = mTimer.tick(); - const UserStats& stats = UserStatsValue::sValue; - - mStats.setAttribute(mFrameNumber, stats.mBegin, mTimer.delta_s(mFrameStart, mScopeStart)); - mStats.setAttribute(mFrameNumber, stats.mTaken, mTimer.delta_s(mScopeStart, end)); - mStats.setAttribute(mFrameNumber, stats.mEnd, mTimer.delta_s(mFrameStart, end)); - } - - private: - const osg::Timer_t mScopeStart; - const osg::Timer_t mFrameStart; - const unsigned int mFrameNumber; - const osg::Timer& mTimer; - osg::Stats& mStats; - }; - void initStatsHandler(Resource::Profiler& profiler) { const osg::Vec4f textColor(1.f, 1.f, 1.f, 1.f); @@ -221,7 +90,7 @@ namespace const bool averageInInverseSpace = false; const float maxValue = 10000; - forEachUserStatsValue([&](const UserStats& v) { + OMW::forEachUserStatsValue([&](const OMW::UserStats& v) { profiler.addUserStatsLine(v.mLabel, textColor, barColor, v.mTaken, multiplier, average, averageInInverseSpace, v.mBegin, v.mEnd, maxValue); }); @@ -302,15 +171,15 @@ void OMW::Engine::executeLocalScripts() bool OMW::Engine::frame(float frametime) { + const osg::Timer_t frameStart = mViewer->getStartTick(); + const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); + const osg::Timer* const timer = osg::Timer::instance(); + osg::Stats* const stats = mViewer->getViewerStats(); + + mEnvironment.setFrameDuration(frametime); + try { - const osg::Timer_t frameStart = mViewer->getStartTick(); - const unsigned int frameNumber = mViewer->getFrameStamp()->getFrameNumber(); - const osg::Timer* const timer = osg::Timer::instance(); - osg::Stats* const stats = mViewer->getViewerStats(); - - mEnvironment.setFrameDuration(frametime); - // update input { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); @@ -425,32 +294,47 @@ bool OMW::Engine::frame(float frametime) ScopedProfile profile(frameStart, frameNumber, *timer, *stats); mWindowManager->update(frametime); } - - const bool reportResource = stats->collectStats("resource"); - - if (reportResource) - stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getSize()); - - mUnrefQueue->flush(*mWorkQueue); - - if (reportResource) - { - stats->setAttribute(frameNumber, "FrameNumber", frameNumber); - - mResourceSystem->reportStats(frameNumber, stats); - - stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); - stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); - - mMechanicsManager->reportStats(frameNumber, *stats); - mWorld->reportStats(frameNumber, *stats); - mLuaManager->reportStats(frameNumber, *stats); - } } catch (const std::exception& e) { Log(Debug::Error) << "Error in frame: " << e.what(); } + + const bool reportResource = stats->collectStats("resource"); + + if (reportResource) + stats->setAttribute(frameNumber, "UnrefQueue", mUnrefQueue->getSize()); + + mUnrefQueue->flush(*mWorkQueue); + + if (reportResource) + { + stats->setAttribute(frameNumber, "FrameNumber", frameNumber); + + mResourceSystem->reportStats(frameNumber, stats); + + stats->setAttribute(frameNumber, "WorkQueue", mWorkQueue->getNumItems()); + stats->setAttribute(frameNumber, "WorkThread", mWorkQueue->getNumActiveThreads()); + + mMechanicsManager->reportStats(frameNumber, *stats); + mWorld->reportStats(frameNumber, *stats); + mLuaManager->reportStats(frameNumber, *stats); + } + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + + { + ScopedProfile profile(frameStart, frameNumber, *timer, *stats); + mWorld->updateWindowManager(); + } + + mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now + + mViewer->renderingTraversals(); + + mLuaWorker->finishUpdate(); + return true; } @@ -505,6 +389,7 @@ OMW::Engine::~Engine() mSoundManager = nullptr; mInputManager = nullptr; mStateManager = nullptr; + mLuaWorker = nullptr; mLuaManager = nullptr; mScriptContext = nullptr; @@ -800,6 +685,9 @@ void OMW::Engine::prepareEngine() mLuaManager = std::make_unique(mVFS.get(), mResDir / "lua_libs"); mEnvironment.setLuaManager(*mLuaManager); + // starts a separate lua thread if "lua num threads" > 0 + mLuaWorker = std::make_unique(*mLuaManager, *mViewer); + // Create input and UI first to set up a bootstrapping environment for // showing a loading screen and keeping the window responsive while doing so @@ -929,98 +817,6 @@ void OMW::Engine::prepareEngine() mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath()); } -class OMW::Engine::LuaWorker -{ -public: - explicit LuaWorker(Engine* engine) - : mEngine(engine) - { - if (Settings::Manager::getInt("lua num threads", "Lua") > 0) - mThread = std::thread([this] { threadBody(); }); - } - - ~LuaWorker() - { - if (mThread && mThread->joinable()) - { - Log(Debug::Error) - << "Unexpected destruction of LuaWorker; likely there is an unhandled exception in the main thread."; - join(); - } - } - - void allowUpdate() - { - if (!mThread) - return; - { - std::lock_guard lk(mMutex); - mUpdateRequest = true; - } - mCV.notify_one(); - } - - void finishUpdate() - { - if (mThread) - { - std::unique_lock lk(mMutex); - mCV.wait(lk, [&] { return !mUpdateRequest; }); - } - else - update(); - } - - void join() - { - if (mThread) - { - { - std::lock_guard lk(mMutex); - mJoinRequest = true; - } - mCV.notify_one(); - mThread->join(); - } - } - -private: - void update() - { - const auto& viewer = mEngine->mViewer; - const osg::Timer_t frameStart = viewer->getStartTick(); - const unsigned int frameNumber = viewer->getFrameStamp()->getFrameNumber(); - ScopedProfile profile( - frameStart, frameNumber, *osg::Timer::instance(), *viewer->getViewerStats()); - - mEngine->mLuaManager->update(); - } - - void threadBody() - { - while (true) - { - std::unique_lock lk(mMutex); - mCV.wait(lk, [&] { return mUpdateRequest || mJoinRequest; }); - if (mJoinRequest) - break; - - update(); - - mUpdateRequest = false; - lk.unlock(); - mCV.notify_one(); - } - } - - Engine* mEngine; - std::mutex mMutex; - std::condition_variable mCV; - bool mUpdateRequest = false; - bool mJoinRequest = false; - std::optional mThread; -}; - // Initialise and enter main loop. void OMW::Engine::go() { @@ -1109,8 +905,6 @@ void OMW::Engine::go() mWindowManager->executeInConsole(mStartupScript); } - LuaWorker luaWorker(this); // starts a separate lua thread if "lua num threads" > 0 - // Start the main rendering loop double simulationTime = 0.0; Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); @@ -1131,17 +925,6 @@ void OMW::Engine::go() } else { - mViewer->eventTraversal(); - mViewer->updateTraversal(); - - mWorld->updateWindowManager(); - - luaWorker.allowUpdate(); // if there is a separate Lua thread, it starts the update now - - mViewer->renderingTraversals(); - - luaWorker.finishUpdate(); - bool guiActive = mWindowManager->isGuiMode(); if (!guiActive) simulationTime += dt; @@ -1163,7 +946,7 @@ void OMW::Engine::go() frameRateLimiter.limit(); } - luaWorker.join(); + mLuaWorker->join(); // Save user settings Settings::Manager::saveUser(mCfgMgr.getUserConfigPath() / "settings.cfg"); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index cdc54c7018..021dccdf34 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -38,6 +38,7 @@ namespace Compiler namespace MWLua { class LuaManager; + class Worker; } namespace Stereo @@ -132,6 +133,7 @@ namespace OMW std::unique_ptr mInputManager; std::unique_ptr mStateManager; std::unique_ptr mLuaManager; + std::unique_ptr mLuaWorker; MWBase::Environment mEnvironment; ToUTF8::FromType mEncoding; std::unique_ptr mEncoder; @@ -262,7 +264,6 @@ namespace OMW private: Files::ConfigurationManager& mCfgMgr; - class LuaWorker; int mGlMaxTextureImageUnits; }; } diff --git a/apps/openmw/mwlua/worker.cpp b/apps/openmw/mwlua/worker.cpp new file mode 100644 index 0000000000..0dbe78ad4b --- /dev/null +++ b/apps/openmw/mwlua/worker.cpp @@ -0,0 +1,92 @@ +#include "worker.hpp" + +#include "luamanagerimp.hpp" + +#include + +#include + +#include + +namespace MWLua +{ + Worker::Worker(LuaManager& manager, osgViewer::Viewer& viewer) + : mManager(manager) + , mViewer(viewer) + { + if (Settings::Manager::getInt("lua num threads", "Lua") > 0) + mThread = std::thread([this] { run(); }); + } + + Worker::~Worker() + { + if (mThread && mThread->joinable()) + { + Log(Debug::Error) + << "Unexpected destruction of LuaWorker; likely there is an unhandled exception in the main thread."; + join(); + } + } + + void Worker::allowUpdate() + { + if (!mThread) + return; + { + std::lock_guard lk(mMutex); + mUpdateRequest = true; + } + mCV.notify_one(); + } + + void Worker::finishUpdate() + { + if (mThread) + { + std::unique_lock lk(mMutex); + mCV.wait(lk, [&] { return !mUpdateRequest; }); + } + else + update(); + } + + void Worker::join() + { + if (mThread) + { + { + std::lock_guard lk(mMutex); + mJoinRequest = true; + } + mCV.notify_one(); + mThread->join(); + } + } + + void Worker::update() + { + const osg::Timer_t frameStart = mViewer.getStartTick(); + const unsigned int frameNumber = mViewer.getFrameStamp()->getFrameNumber(); + OMW::ScopedProfile profile( + frameStart, frameNumber, *osg::Timer::instance(), *mViewer.getViewerStats()); + + mManager.update(); + } + + void Worker::run() noexcept + { + while (true) + { + std::unique_lock lk(mMutex); + mCV.wait(lk, [&] { return mUpdateRequest || mJoinRequest; }); + if (mJoinRequest) + break; + + update(); + + mUpdateRequest = false; + lk.unlock(); + mCV.notify_one(); + } + } +} diff --git a/apps/openmw/mwlua/worker.hpp b/apps/openmw/mwlua/worker.hpp new file mode 100644 index 0000000000..fed625e1f1 --- /dev/null +++ b/apps/openmw/mwlua/worker.hpp @@ -0,0 +1,46 @@ +#ifndef OPENMW_MWLUA_WORKER_H +#define OPENMW_MWLUA_WORKER_H + +#include +#include +#include +#include + +namespace osgViewer +{ + class Viewer; +} + +namespace MWLua +{ + class LuaManager; + + class Worker + { + public: + explicit Worker(LuaManager& manager, osgViewer::Viewer& viewer); + + ~Worker(); + + void allowUpdate(); + + void finishUpdate(); + + void join(); + + private: + void update(); + + void run() noexcept; + + LuaManager& mManager; + osgViewer::Viewer& mViewer; + std::mutex mMutex; + std::condition_variable mCV; + bool mUpdateRequest = false; + bool mJoinRequest = false; + std::optional mThread; + }; +} + +#endif // OPENMW_MWLUA_LUAWORKER_H diff --git a/apps/openmw/profile.hpp b/apps/openmw/profile.hpp new file mode 100644 index 0000000000..526dfa2f00 --- /dev/null +++ b/apps/openmw/profile.hpp @@ -0,0 +1,153 @@ +#ifndef OPENMW_PROFILE_H +#define OPENMW_PROFILE_H + +#include +#include + +#include +#include + +namespace OMW +{ + struct UserStats + { + const std::string mLabel; + const std::string mBegin; + const std::string mEnd; + const std::string mTaken; + + explicit UserStats(const std::string& label, const std::string& prefix) + : mLabel(label) + , mBegin(prefix + "_time_begin") + , mEnd(prefix + "_time_end") + , mTaken(prefix + "_time_taken") + { + } + }; + + enum class UserStatsType : std::size_t + { + Input, + Sound, + State, + Script, + Mechanics, + Physics, + PhysicsWorker, + World, + Gui, + Lua, + LuaSyncUpdate, + WindowManager, + Number, + }; + + template + struct UserStatsValue + { + static const UserStats sValue; + }; + + template <> + inline const UserStats UserStatsValue::sValue{ "Input", "input" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "Sound", "sound" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "State", "state" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "Script", "script" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "Mech", "mechanics" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "Phys", "physics" }; + + template <> + inline const UserStats UserStatsValue::sValue{ " -Async", "physicsworker" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "World", "world" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "Gui", "gui" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "Lua", "lua" }; + + template <> + inline const UserStats UserStatsValue::sValue{ " -Sync", "luasyncupdate" }; + + template <> + inline const UserStats UserStatsValue::sValue{ "WindowManager", "windowmanager" }; + + template + struct ForEachUserStatsValue + { + template + static void apply(F&& f) + { + f(UserStatsValue::sValue); + using Next = ForEachUserStatsValue(static_cast(type) + 1)>; + Next::apply(std::forward(f)); + } + }; + + template <> + struct ForEachUserStatsValue + { + template + static void apply(F&&) + { + } + }; + + template + void forEachUserStatsValue(F&& f) + { + ForEachUserStatsValue(0)>::apply(std::forward(f)); + } + + template + class ScopedProfile + { + public: + explicit ScopedProfile( + osg::Timer_t frameStart, unsigned int frameNumber, const osg::Timer& timer, osg::Stats& stats) + : mScopeStart(timer.tick()) + , mFrameStart(frameStart) + , mFrameNumber(frameNumber) + , mTimer(timer) + , mStats(stats) + { + } + + ScopedProfile(const ScopedProfile&) = delete; + ScopedProfile& operator=(const ScopedProfile&) = delete; + + ~ScopedProfile() + { + if (!mStats.collectStats("engine")) + return; + + const osg::Timer_t end = mTimer.tick(); + const UserStats& stats = UserStatsValue::sValue; + + mStats.setAttribute(mFrameNumber, stats.mBegin, mTimer.delta_s(mFrameStart, mScopeStart)); + mStats.setAttribute(mFrameNumber, stats.mTaken, mTimer.delta_s(mScopeStart, end)); + mStats.setAttribute(mFrameNumber, stats.mEnd, mTimer.delta_s(mFrameStart, end)); + } + + private: + const osg::Timer_t mScopeStart; + const osg::Timer_t mFrameStart; + const unsigned int mFrameNumber; + const osg::Timer& mTimer; + osg::Stats& mStats; + }; +} + +#endif