1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-30 03:15:32 +00:00

Apply uniform random deviation to AI reaction timer

This allows to distribute AI reaction calls over time.

Before this change actors appearing at the same frame will react in the same
frame over and over because AI reaction period is constant. It creates a
non-uniform CPU usage over frames. If a single frame has too many AI reactions
it may cause stuttering when there are too many actors on a scene for current
system.

Another concern is a synchronization of actions between creatures and NPC.
They start to go or hit at the same frame that is unnatural.
This commit is contained in:
elsid 2021-03-19 23:23:26 +01:00
parent d13d90a50d
commit 675c0ab72f
No known key found for this signature in database
GPG key ID: B845CB9FEE18AB40
10 changed files with 90 additions and 33 deletions

View file

@ -146,19 +146,10 @@ namespace MWMechanics
}
storage.mActionCooldown -= duration;
float& timerReact = storage.mTimerReact;
if (timerReact < AI_REACTION_TIME)
{
timerReact += duration;
}
else
{
timerReact = 0;
if (attack(actor, target, storage, characterController))
return true;
}
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
return false;
return false;
return attack(actor, target, storage, characterController);
}
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController)

View file

@ -10,6 +10,7 @@
#include "pathfinding.hpp"
#include "movement.hpp"
#include "obstacle.hpp"
#include "aitimer.hpp"
namespace ESM
{
@ -27,7 +28,7 @@ namespace MWMechanics
struct AiCombatStorage : AiTemporaryBase
{
float mAttackCooldown;
float mTimerReact;
AiReactionTimer mReaction;
float mTimerCombatMove;
bool mReadyToAttack;
bool mAttack;
@ -60,7 +61,6 @@ namespace MWMechanics
AiCombatStorage():
mAttackCooldown(0.0f),
mTimerReact(AI_REACTION_TIME),
mTimerCombatMove(0.0f),
mReadyToAttack(false),
mAttack(false),

View file

@ -28,7 +28,6 @@
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
mTypeId(typeId),
mOptions(options),
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""),
mTargetActorId(-1),
mRotateOnTheRunChecks(0),
@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
void MWMechanics::AiPackage::reset()
{
// reset all members
mTimer = AI_REACTION_TIME + 1.0f;
mReaction.reset();
mIsShortcutting = false;
mShortcutProhibited = false;
mShortcutFailPos = osg::Vec3f();
@ -75,7 +74,7 @@ void MWMechanics::AiPackage::reset()
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance)
{
mTimer += duration; //Update timer
const Misc::TimerStatus timerStatus = mReaction.update(duration);
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
MWBase::World* world = MWBase::Environment::get().getWorld();
@ -98,7 +97,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
const bool isDestReached = (distToTarget <= destTolerance);
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
if (!isDestReached && mTimer > AI_REACTION_TIME)
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
{
if (actor.getClass().isBipedal(actor))
openDoors(actor);
@ -142,8 +141,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
}
}
mTimer = 0;
}
const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration

View file

@ -10,6 +10,7 @@
#include "obstacle.hpp"
#include "aistate.hpp"
#include "aipackagetypeid.hpp"
#include "aitimer.hpp"
namespace MWWorld
{
@ -28,8 +29,6 @@ namespace ESM
namespace MWMechanics
{
const float AI_REACTION_TIME = 0.25f;
class CharacterController;
class PathgridGraph;
@ -158,7 +157,7 @@ namespace MWMechanics
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;
float mTimer;
AiReactionTimer mReaction;
std::string mTargetActorRefId;
mutable int mTargetActorId;

View file

@ -0,0 +1,26 @@
#ifndef OPENMW_MECHANICS_AITIMER_H
#define OPENMW_MECHANICS_AITIMER_H
#include <components/misc/rng.hpp>
#include <components/misc/timer.hpp>
namespace MWMechanics
{
constexpr float AI_REACTION_TIME = 0.25f;
class AiReactionTimer
{
public:
static constexpr float sDeviation = 0.1f;
Misc::TimerStatus update(float duration) { return mImpl.update(duration); }
void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); }
private:
Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation,
Misc::Rng::deviate(0, sDeviation)};
};
}
#endif

View file

@ -223,15 +223,10 @@ namespace MWMechanics
doPerFrameActionsForState(actor, duration, storage);
float& lastReaction = storage.mReaction;
lastReaction += duration;
if (AI_REACTION_TIME <= lastReaction)
{
lastReaction = 0;
return reactionTimeActions(actor, storage, pos);
}
else
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
return false;
return reactionTimeActions(actor, storage, pos);
}
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)

View file

@ -10,6 +10,7 @@
#include "pathfinding.hpp"
#include "obstacle.hpp"
#include "aistate.hpp"
#include "aitimer.hpp"
namespace ESM
{
@ -25,7 +26,7 @@ namespace MWMechanics
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase
{
float mReaction; // update some actions infrequently
AiReactionTimer mReaction;
// AiWander states
enum WanderState
@ -57,7 +58,6 @@ namespace MWMechanics
int mStuckCount;
AiWanderStorage():
mReaction(0),
mState(Wander_ChooseAction),
mIsWanderingManually(false),
mCanWanderAlongPathGrid(true),

View file

@ -47,4 +47,9 @@ namespace Misc
{
return static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
}
float Rng::deviate(float mean, float deviation, Seed& seed)
{
return std::uniform_real_distribution<float>(mean - deviation, mean + deviation)(seed.mGenerator);
}
}

View file

@ -42,6 +42,8 @@ public:
/// returns default seed for RNG
static unsigned int generateDefaultSeed();
static float deviate(float mean, float deviation, Seed& seed = getSeed());
};
}

42
components/misc/timer.hpp Normal file
View file

@ -0,0 +1,42 @@
#ifndef OPENMW_COMPONENTS_MISC_TIMER_H
#define OPENMW_COMPONENTS_MISC_TIMER_H
#include "rng.hpp"
namespace Misc
{
enum class TimerStatus
{
Waiting,
Elapsed,
};
class DeviatingPeriodicTimer
{
public:
explicit DeviatingPeriodicTimer(float period, float deviation, float timeLeft)
: mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft)
{}
TimerStatus update(float duration)
{
if (mTimeLeft > 0)
{
mTimeLeft -= duration;
return TimerStatus::Waiting;
}
mTimeLeft = Rng::deviate(mPeriod, mDeviation);
return TimerStatus::Elapsed;
}
void reset(float timeLeft) { mTimeLeft = timeLeft; }
private:
const float mPeriod;
const float mDeviation;
float mTimeLeft;
};
}
#endif