Merge branch 'asynctimer' into 'master'

Add async timer that works when the game is paused

See merge request OpenMW/openmw!4886
This commit is contained in:
TRUINGLol 2025-09-16 14:46:58 +00:00
commit 0e2b24d774
6 changed files with 113 additions and 1 deletions

View File

@ -83,7 +83,7 @@ add_openmw_dir (mwworld
store esmstore fallback actionrepair actionsoulgem livecellref actiondoor store esmstore fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
cellpreloader datetimemanager groundcoverstore magiceffects cell ptrregistry cellpreloader datetimemanager groundcoverstore magiceffects cell ptrregistry
positioncellgrid positioncellgrid asynctimer
) )
add_openmw_dir (mwphysics add_openmw_dir (mwphysics

View File

@ -69,6 +69,7 @@
#include "mwworld/class.hpp" #include "mwworld/class.hpp"
#include "mwworld/datetimemanager.hpp" #include "mwworld/datetimemanager.hpp"
#include "mwworld/asynctimer.hpp"
#include "mwworld/worldimp.hpp" #include "mwworld/worldimp.hpp"
#include "mwrender/vismask.hpp" #include "mwrender/vismask.hpp"
@ -721,6 +722,9 @@ void OMW::Engine::prepareEngine()
mStateManager = std::make_unique<MWState::StateManager>(mCfgMgr.getUserDataPath() / "saves", mContentFiles); mStateManager = std::make_unique<MWState::StateManager>(mCfgMgr.getUserDataPath() / "saves", mContentFiles);
mEnvironment.setStateManager(*mStateManager); mEnvironment.setStateManager(*mStateManager);
mAsyncTimer = std::make_unique<MWWorld::AsyncTimer>();
mEnvironment.setAsyncTimer(*mAsyncTimer);
const bool stereoEnabled = Settings::stereo().mStereoEnabled || osg::DisplaySettings::instance().get()->getStereo(); const bool stereoEnabled = Settings::stereo().mStereoEnabled || osg::DisplaySettings::instance().get()->getStereo();
mStereoManager = std::make_unique<Stereo::Manager>( mStereoManager = std::make_unique<Stereo::Manager>(
mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance); mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance);
@ -1051,6 +1055,8 @@ void OMW::Engine::go()
timeManager.setRenderingSimulationTime(timeManager.getRenderingSimulationTime() + dt); timeManager.setRenderingSimulationTime(timeManager.getRenderingSimulationTime() + dt);
} }
mAsyncTimer->updateTimer();
if (stats) if (stats)
{ {
// The delay is required because rendering happens in parallel to the main thread and stats from there is // The delay is required because rendering happens in parallel to the main thread and stats from there is

View File

@ -91,6 +91,7 @@ namespace MWSound
namespace MWWorld namespace MWWorld
{ {
class World; class World;
class AsyncTimer;
} }
namespace MWScript namespace MWScript
@ -139,6 +140,7 @@ namespace OMW
std::unique_ptr<MWDialogue::Journal> mJournal; std::unique_ptr<MWDialogue::Journal> mJournal;
std::unique_ptr<MWInput::InputManager> mInputManager; std::unique_ptr<MWInput::InputManager> mInputManager;
std::unique_ptr<MWState::StateManager> mStateManager; std::unique_ptr<MWState::StateManager> mStateManager;
std::unique_ptr<MWWorld::AsyncTimer> mAsyncTimer;
std::unique_ptr<MWLua::LuaManager> mLuaManager; std::unique_ptr<MWLua::LuaManager> mLuaManager;
std::unique_ptr<MWLua::Worker> mLuaWorker; std::unique_ptr<MWLua::Worker> mLuaWorker;
std::unique_ptr<L10n::Manager> mL10nManager; std::unique_ptr<L10n::Manager> mL10nManager;

View File

@ -20,6 +20,7 @@ namespace MWWorld
class ESMStore; class ESMStore;
class WorldModel; class WorldModel;
class Scene; class Scene;
class AsyncTimer;
} }
namespace MWBase namespace MWBase
@ -55,6 +56,7 @@ namespace MWBase
Journal* mJournal = nullptr; Journal* mJournal = nullptr;
InputManager* mInputManager = nullptr; InputManager* mInputManager = nullptr;
StateManager* mStateManager = nullptr; StateManager* mStateManager = nullptr;
MWWorld::AsyncTimer* mAsyncTimer = nullptr;
LuaManager* mLuaManager = nullptr; LuaManager* mLuaManager = nullptr;
Resource::ResourceSystem* mResourceSystem = nullptr; Resource::ResourceSystem* mResourceSystem = nullptr;
L10n::Manager* mL10nManager = nullptr; L10n::Manager* mL10nManager = nullptr;
@ -91,6 +93,8 @@ namespace MWBase
void setStateManager(StateManager& value) { mStateManager = &value; } void setStateManager(StateManager& value) { mStateManager = &value; }
void setAsyncTimer(MWWorld::AsyncTimer& value) { mAsyncTimer = &value; }
void setLuaManager(LuaManager& value) { mLuaManager = &value; } void setLuaManager(LuaManager& value) { mLuaManager = &value; }
void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; } void setResourceSystem(Resource::ResourceSystem& value) { mResourceSystem = &value; }
@ -118,6 +122,8 @@ namespace MWBase
Misc::NotNullPtr<StateManager> getStateManager() const { return mStateManager; } Misc::NotNullPtr<StateManager> getStateManager() const { return mStateManager; }
Misc::NotNullPtr<MWWorld::AsyncTimer> getAsyncTimer() const { return mAsyncTimer; }
Misc::NotNullPtr<LuaManager> getLuaManager() const { return mLuaManager; } Misc::NotNullPtr<LuaManager> getLuaManager() const { return mLuaManager; }
Misc::NotNullPtr<Resource::ResourceSystem> getResourceSystem() const { return mResourceSystem; } Misc::NotNullPtr<Resource::ResourceSystem> getResourceSystem() const { return mResourceSystem; }

View File

@ -0,0 +1,53 @@
#include <chrono>
#include <functional>
#include <set>
#include <algorithm>
#include <cstdint>
#include <utility>
#include "asynctimer.hpp"
namespace MWWorld
{
AsyncTimer::AsyncTimer() = default;
AsyncTimer::~AsyncTimer() = default;
void AsyncTimer::updateTimer(){
auto time_now = std::chrono::steady_clock::now();
std::vector<Callback> expired_callbacks;
while(!mTasks.empty() && mTasks.begin()->trigger_time <= time_now){
expired_callbacks.push_back(mTasks.begin()->callback);
mTasks.erase(mTasks.begin());
}
for(auto& callback : expired_callbacks){
callback();
}
}
AsyncTimer::TaskId AsyncTimer::setTask(float delay, Callback callback){
if(delay < 0 || !callback) return -1;
auto triggerTime = std::chrono::steady_clock::now() +
std::chrono::milliseconds(static_cast<int>(delay * 1000));
AsyncTimer::TaskId new_id = mNext_id++;
mTasks.insert({triggerTime, std::move(callback), new_id});
return new_id;
}
void AsyncTimer::cancelTask(AsyncTimer::TaskId id){
if(id < 0) return;
for(auto it = mTasks.begin(); it != mTasks.end(); ++it){
if(it->id == id){
mTasks.erase(it);
break;
}
}
}
}

View File

@ -0,0 +1,45 @@
#ifndef GAME_MWWORLD_ASYNCTIMER_H
#define GAME_MWWORLD_ASYNCTIMER_H
#include <chrono>
#include <functional>
#include <set>
namespace MWWorld
{
// @brief Async timer that works when the game is paused
class AsyncTimer
{
public:
AsyncTimer();
~AsyncTimer();
using TimePoint = std::chrono::steady_clock::time_point;
using Callback = std::function<void()>;
using TaskId = uint64_t;
struct TimerTask{
TimePoint trigger_time;
Callback callback;
TaskId id;
// Comparator for ordering tasks by execution time (earliest first)
bool operator < (const TimerTask& other) const{
return trigger_time < other.trigger_time;
}
};
// @brief Updates timer state and executes due tasks
void updateTimer();
// @brief Schedules a new task for delayed execution
TaskId setTask(float delay, Callback callback);
// @brief Cancels a task by id
void cancelTask(TaskId id);
private:
std::multiset<TimerTask> mTasks;
TaskId mNext_id = 0;
};
}
#endif /* GAME_MWWORLD_ASYNCTIMER_H */