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
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
cellpreloader datetimemanager groundcoverstore magiceffects cell ptrregistry
positioncellgrid
positioncellgrid asynctimer
)
add_openmw_dir (mwphysics

View File

@ -69,6 +69,7 @@
#include "mwworld/class.hpp"
#include "mwworld/datetimemanager.hpp"
#include "mwworld/asynctimer.hpp"
#include "mwworld/worldimp.hpp"
#include "mwrender/vismask.hpp"
@ -721,6 +722,9 @@ void OMW::Engine::prepareEngine()
mStateManager = std::make_unique<MWState::StateManager>(mCfgMgr.getUserDataPath() / "saves", mContentFiles);
mEnvironment.setStateManager(*mStateManager);
mAsyncTimer = std::make_unique<MWWorld::AsyncTimer>();
mEnvironment.setAsyncTimer(*mAsyncTimer);
const bool stereoEnabled = Settings::stereo().mStereoEnabled || osg::DisplaySettings::instance().get()->getStereo();
mStereoManager = std::make_unique<Stereo::Manager>(
mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance);
@ -1051,6 +1055,8 @@ void OMW::Engine::go()
timeManager.setRenderingSimulationTime(timeManager.getRenderingSimulationTime() + dt);
}
mAsyncTimer->updateTimer();
if (stats)
{
// 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
{
class World;
class AsyncTimer;
}
namespace MWScript
@ -139,6 +140,7 @@ namespace OMW
std::unique_ptr<MWDialogue::Journal> mJournal;
std::unique_ptr<MWInput::InputManager> mInputManager;
std::unique_ptr<MWState::StateManager> mStateManager;
std::unique_ptr<MWWorld::AsyncTimer> mAsyncTimer;
std::unique_ptr<MWLua::LuaManager> mLuaManager;
std::unique_ptr<MWLua::Worker> mLuaWorker;
std::unique_ptr<L10n::Manager> mL10nManager;

View File

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