mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 16:15:31 +00:00
Prevent physics death spiral by falling back to true delta time when needed
This commit is contained in:
parent
86bd173d69
commit
63f01d8c5f
6 changed files with 134 additions and 15 deletions
|
@ -57,6 +57,7 @@
|
||||||
Bug #5424: Creatures do not headtrack player
|
Bug #5424: Creatures do not headtrack player
|
||||||
Bug #5425: Poison effect only appears for one frame
|
Bug #5425: Poison effect only appears for one frame
|
||||||
Bug #5427: GetDistance unknown ID error is misleading
|
Bug #5427: GetDistance unknown ID error is misleading
|
||||||
|
Bug #5431: Physics performance degradation after a specific number of actors on a scene
|
||||||
Bug #5435: Enemies can't hurt the player when collision is off
|
Bug #5435: Enemies can't hurt the player when collision is off
|
||||||
Bug #5441: Enemies can't push a player character when in critical strike stance
|
Bug #5441: Enemies can't push a player character when in critical strike stance
|
||||||
Bug #5451: Magic projectiles don't disappear with the caster
|
Bug #5451: Magic projectiles don't disappear with the caster
|
||||||
|
@ -143,6 +144,7 @@
|
||||||
Feature #5730: Add graphic herbalism option to the launcher and documents
|
Feature #5730: Add graphic herbalism option to the launcher and documents
|
||||||
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
|
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
|
||||||
Feature #5813: Instanced groundcover support
|
Feature #5813: Instanced groundcover support
|
||||||
|
Feature #5910: Fall back to delta time when physics can't keep up
|
||||||
Task #5480: Drop Qt4 support
|
Task #5480: Drop Qt4 support
|
||||||
Task #5520: Improve cell name autocompleter implementation
|
Task #5520: Improve cell name autocompleter implementation
|
||||||
Task #5844: Update 'toggle sneak' documentation
|
Task #5844: Update 'toggle sneak' documentation
|
||||||
|
|
|
@ -101,7 +101,7 @@ namespace
|
||||||
|
|
||||||
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
|
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
|
||||||
{
|
{
|
||||||
const float interpolationFactor = timeAccum / physicsDt;
|
const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
|
||||||
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
|
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,8 @@ namespace
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
|
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
|
||||||
: mPhysicsDt(physicsDt)
|
: mDefaultPhysicsDt(physicsDt)
|
||||||
|
, mPhysicsDt(physicsDt)
|
||||||
, mTimeAccum(0.f)
|
, mTimeAccum(0.f)
|
||||||
, mCollisionWorld(std::move(collisionWorld))
|
, mCollisionWorld(std::move(collisionWorld))
|
||||||
, mNumJobs(0)
|
, mNumJobs(0)
|
||||||
|
@ -152,6 +153,11 @@ namespace MWPhysics
|
||||||
, mNextLOS(0)
|
, mNextLOS(0)
|
||||||
, mFrameNumber(0)
|
, mFrameNumber(0)
|
||||||
, mTimer(osg::Timer::instance())
|
, mTimer(osg::Timer::instance())
|
||||||
|
, mPrevStepCount(1)
|
||||||
|
, mBudget(physicsDt)
|
||||||
|
, mAsyncBudget(0.0f)
|
||||||
|
, mBudgetCursor(0)
|
||||||
|
, mAsyncStartTime(0)
|
||||||
, mTimeBegin(0)
|
, mTimeBegin(0)
|
||||||
, mTimeEnd(0)
|
, mTimeEnd(0)
|
||||||
, mFrameStart(0)
|
, mFrameStart(0)
|
||||||
|
@ -220,13 +226,61 @@ namespace MWPhysics
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
std::tuple<int, float> PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const
|
||||||
|
{
|
||||||
|
int maxAllowedSteps = 2;
|
||||||
|
int numSteps = timeAccum / mDefaultPhysicsDt;
|
||||||
|
|
||||||
|
// adjust maximum step count based on whether we're likely physics bottlenecked or not
|
||||||
|
// if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time
|
||||||
|
// if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget
|
||||||
|
// if it ends up lower than numSteps and also 1, we will run a single delta time physics step
|
||||||
|
// if we did not do this, and had a fixed step count limit,
|
||||||
|
// we would have an unnecessarily low render framerate if we were only physics bottlenecked,
|
||||||
|
// and we would be unnecessarily invoking true delta time if we were only render bottlenecked
|
||||||
|
|
||||||
|
// get physics timing stats
|
||||||
|
float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get());
|
||||||
|
// time spent per step in terms of the intended physics framerate
|
||||||
|
budgetMeasurement /= mDefaultPhysicsDt;
|
||||||
|
// ensure sane minimum value
|
||||||
|
budgetMeasurement = std::max(0.00001f, budgetMeasurement);
|
||||||
|
// we're spending almost or more than realtime per physics frame; limit to a single step
|
||||||
|
if (budgetMeasurement > 0.95)
|
||||||
|
maxAllowedSteps = 1;
|
||||||
|
// physics is fairly cheap; limit based on expense
|
||||||
|
if (budgetMeasurement < 0.5)
|
||||||
|
maxAllowedSteps = std::ceil(1.0/budgetMeasurement);
|
||||||
|
// limit to a reasonable amount
|
||||||
|
maxAllowedSteps = std::min(10, maxAllowedSteps);
|
||||||
|
|
||||||
|
// fall back to delta time for this frame if fixed timestep physics would fall behind
|
||||||
|
float actualDelta = mDefaultPhysicsDt;
|
||||||
|
if (numSteps > maxAllowedSteps)
|
||||||
|
{
|
||||||
|
numSteps = maxAllowedSteps;
|
||||||
|
// ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency
|
||||||
|
// this causes interpolation to 100% use the most recent physics result when true delta time is happening
|
||||||
|
// and we deliberately simulate up to exactly the timestamp that we want to render
|
||||||
|
actualDelta = timeAccum/float(numSteps+1);
|
||||||
|
// actually: if this results in a per-step delta less than the target physics steptime, clamp it
|
||||||
|
// this might reintroduce some stutter, but only comes into play in obscure cases
|
||||||
|
// (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun)
|
||||||
|
actualDelta = std::max(actualDelta, mDefaultPhysicsDt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(numSteps, actualDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
|
||||||
{
|
{
|
||||||
// This function run in the main thread.
|
// This function run in the main thread.
|
||||||
// While the mSimulationMutex is held, background physics threads can't run.
|
// While the mSimulationMutex is held, background physics threads can't run.
|
||||||
|
|
||||||
std::unique_lock lock(mSimulationMutex);
|
std::unique_lock lock(mSimulationMutex);
|
||||||
|
|
||||||
|
double timeStart = mTimer->tick();
|
||||||
|
|
||||||
mMovedActors.clear();
|
mMovedActors.clear();
|
||||||
|
|
||||||
// start by finishing previous background computation
|
// start by finishing previous background computation
|
||||||
|
@ -251,14 +305,21 @@ namespace MWPhysics
|
||||||
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
mMovedActors.emplace_back(data.mActorRaw->getPtr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(mAdvanceSimulation)
|
||||||
|
mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
|
||||||
updateStats(frameStart, frameNumber, stats);
|
updateStats(frameStart, frameNumber, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
|
||||||
|
timeAccum -= numSteps*newDelta;
|
||||||
|
|
||||||
// init
|
// init
|
||||||
for (auto& data : actorsData)
|
for (auto& data : actorsData)
|
||||||
data.updatePosition();
|
data.updatePosition();
|
||||||
|
mPrevStepCount = numSteps;
|
||||||
mRemainingSteps = numSteps;
|
mRemainingSteps = numSteps;
|
||||||
mTimeAccum = timeAccum;
|
mTimeAccum = timeAccum;
|
||||||
|
mPhysicsDt = newDelta;
|
||||||
mActorsFrameData = std::move(actorsData);
|
mActorsFrameData = std::move(actorsData);
|
||||||
mAdvanceSimulation = (mRemainingSteps != 0);
|
mAdvanceSimulation = (mRemainingSteps != 0);
|
||||||
mNewFrame = true;
|
mNewFrame = true;
|
||||||
|
@ -269,20 +330,30 @@ namespace MWPhysics
|
||||||
if (mAdvanceSimulation)
|
if (mAdvanceSimulation)
|
||||||
mWorldFrameData = std::make_unique<WorldFrameData>();
|
mWorldFrameData = std::make_unique<WorldFrameData>();
|
||||||
|
|
||||||
|
if (mAdvanceSimulation)
|
||||||
|
mBudgetCursor += 1;
|
||||||
|
|
||||||
if (mNumThreads == 0)
|
if (mNumThreads == 0)
|
||||||
{
|
{
|
||||||
syncComputation();
|
syncComputation();
|
||||||
|
if(mAdvanceSimulation)
|
||||||
|
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
|
||||||
return mMovedActors;
|
return mMovedActors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mAsyncStartTime = mTimer->tick();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
mHasJob.notify_all();
|
mHasJob.notify_all();
|
||||||
|
if (mAdvanceSimulation)
|
||||||
|
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
|
||||||
return mMovedActors;
|
return mMovedActors;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
|
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
|
||||||
{
|
{
|
||||||
std::unique_lock lock(mSimulationMutex);
|
std::unique_lock lock(mSimulationMutex);
|
||||||
|
mBudget.reset(mDefaultPhysicsDt);
|
||||||
|
mAsyncBudget.reset(0.0f);
|
||||||
mMovedActors.clear();
|
mMovedActors.clear();
|
||||||
mActorsFrameData.clear();
|
mActorsFrameData.clear();
|
||||||
for (const auto& [_, actor] : actors)
|
for (const auto& [_, actor] : actors)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#include "physicssystem.hpp"
|
#include "physicssystem.hpp"
|
||||||
#include "ptrholder.hpp"
|
#include "ptrholder.hpp"
|
||||||
|
#include "components/misc/budgetmeasurement.hpp"
|
||||||
|
|
||||||
namespace Misc
|
namespace Misc
|
||||||
{
|
{
|
||||||
|
@ -32,7 +33,7 @@ namespace MWPhysics
|
||||||
/// @param timeAccum accumulated time from previous run to interpolate movements
|
/// @param timeAccum accumulated time from previous run to interpolate movements
|
||||||
/// @param actorsData per actor data needed to compute new positions
|
/// @param actorsData per actor data needed to compute new positions
|
||||||
/// @return new position of each actor
|
/// @return new position of each actor
|
||||||
const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
const std::vector<MWWorld::Ptr>& moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||||
|
|
||||||
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
|
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
|
||||||
|
|
||||||
|
@ -58,11 +59,13 @@ namespace MWPhysics
|
||||||
void updateAabbs();
|
void updateAabbs();
|
||||||
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
|
||||||
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
|
||||||
|
std::tuple<int, float> calculateStepConfig(float timeAccum) const;
|
||||||
|
|
||||||
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
std::unique_ptr<WorldFrameData> mWorldFrameData;
|
||||||
std::vector<ActorFrameData> mActorsFrameData;
|
std::vector<ActorFrameData> mActorsFrameData;
|
||||||
std::vector<MWWorld::Ptr> mMovedActors;
|
std::vector<MWWorld::Ptr> mMovedActors;
|
||||||
const float mPhysicsDt;
|
float mDefaultPhysicsDt;
|
||||||
|
float mPhysicsDt;
|
||||||
float mTimeAccum;
|
float mTimeAccum;
|
||||||
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
std::shared_ptr<btCollisionWorld> mCollisionWorld;
|
||||||
std::vector<LOSRequest> mLOSCache;
|
std::vector<LOSRequest> mLOSCache;
|
||||||
|
@ -94,6 +97,12 @@ namespace MWPhysics
|
||||||
|
|
||||||
unsigned int mFrameNumber;
|
unsigned int mFrameNumber;
|
||||||
const osg::Timer* mTimer;
|
const osg::Timer* mTimer;
|
||||||
|
|
||||||
|
int mPrevStepCount;
|
||||||
|
Misc::BudgetMeasurement mBudget;
|
||||||
|
Misc::BudgetMeasurement mAsyncBudget;
|
||||||
|
unsigned int mBudgetCursor;
|
||||||
|
osg::Timer_t mAsyncStartTime;
|
||||||
osg::Timer_t mTimeBegin;
|
osg::Timer_t mTimeBegin;
|
||||||
osg::Timer_t mTimeEnd;
|
osg::Timer_t mTimeEnd;
|
||||||
osg::Timer_t mFrameStart;
|
osg::Timer_t mFrameStart;
|
||||||
|
|
|
@ -744,19 +744,14 @@ namespace MWPhysics
|
||||||
{
|
{
|
||||||
mTimeAccum += dt;
|
mTimeAccum += dt;
|
||||||
|
|
||||||
const int maxAllowedSteps = 20;
|
|
||||||
int numSteps = mTimeAccum / mPhysicsDt;
|
|
||||||
numSteps = std::min(numSteps, maxAllowedSteps);
|
|
||||||
|
|
||||||
mTimeAccum -= numSteps * mPhysicsDt;
|
|
||||||
|
|
||||||
if (skipSimulation)
|
if (skipSimulation)
|
||||||
return mTaskScheduler->resetSimulation(mActors);
|
return mTaskScheduler->resetSimulation(mActors);
|
||||||
|
|
||||||
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats);
|
// modifies mTimeAccum
|
||||||
|
return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(int numSteps)
|
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(bool willSimulate)
|
||||||
{
|
{
|
||||||
std::vector<ActorFrameData> actorsFrameData;
|
std::vector<ActorFrameData> actorsFrameData;
|
||||||
actorsFrameData.reserve(mMovementQueue.size());
|
actorsFrameData.reserve(mMovementQueue.size());
|
||||||
|
@ -796,7 +791,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
|
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
|
||||||
MWWorld::Ptr standingOn;
|
MWWorld::Ptr standingOn;
|
||||||
if (numSteps == 0)
|
if (!willSimulate)
|
||||||
standingOn = physicActor->getStandingOnPtr();
|
standingOn = physicActor->getStandingOnPtr();
|
||||||
|
|
||||||
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
|
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
|
||||||
|
|
|
@ -252,7 +252,7 @@ namespace MWPhysics
|
||||||
|
|
||||||
void updateWater();
|
void updateWater();
|
||||||
|
|
||||||
std::vector<ActorFrameData> prepareFrameData(int numSteps);
|
std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
|
||||||
|
|
||||||
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
|
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
|
||||||
|
|
||||||
|
|
42
components/misc/budgetmeasurement.hpp
Normal file
42
components/misc/budgetmeasurement.hpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H
|
||||||
|
#define OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H
|
||||||
|
|
||||||
|
|
||||||
|
namespace Misc
|
||||||
|
{
|
||||||
|
|
||||||
|
class BudgetMeasurement
|
||||||
|
{
|
||||||
|
std::array<float, 4> mBudgetHistory;
|
||||||
|
std::array<unsigned int, 4> mBudgetStepCount;
|
||||||
|
|
||||||
|
public:
|
||||||
|
BudgetMeasurement(const float default_expense)
|
||||||
|
{
|
||||||
|
mBudgetHistory = {default_expense, default_expense, default_expense, default_expense};
|
||||||
|
mBudgetStepCount = {1, 1, 1, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(const float default_expense)
|
||||||
|
{
|
||||||
|
mBudgetHistory = {default_expense, default_expense, default_expense, default_expense};
|
||||||
|
mBudgetStepCount = {1, 1, 1, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(double delta, unsigned int stepCount, size_t cursor)
|
||||||
|
{
|
||||||
|
mBudgetHistory[cursor%4] = delta;
|
||||||
|
mBudgetStepCount[cursor%4] = stepCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
double get() const
|
||||||
|
{
|
||||||
|
float sum = (mBudgetHistory[0] + mBudgetHistory[1] + mBudgetHistory[2] + mBudgetHistory[3]);
|
||||||
|
unsigned int stepCountSum = (mBudgetStepCount[0] + mBudgetStepCount[1] + mBudgetStepCount[2] + mBudgetStepCount[3]);
|
||||||
|
return sum/float(stepCountSum);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue