Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Core/GameEngine/Include/Common/FramePacer.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class FramePacer
Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in seconds
Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0) const; ///< Get the logic time step in milliseconds

Real getLogicFramePhase() const; ///< Get how far the current render step reaches into the current logic frame, in [0,1]. Used to interpolate render updates between logic updates.

protected:

FrameRateLimit m_frameRateLimit;
Expand All @@ -75,6 +77,7 @@ class FramePacer
Int m_logicTimeScaleFPS; ///< Maximum frames per second for logic time scale

Real m_updateTime; ///< Last update delta time in seconds
Real m_logicFramePhase; ///< How far the current render step reaches into the current logic frame, ranging 0 to 1.

Bool m_enableFpsLimit;
Bool m_enableLogicTimeScale;
Expand Down
40 changes: 34 additions & 6 deletions Core/GameEngine/Include/GameClient/ParticleSys.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ class Particle : public MemoryPoolObject,

Particle( ParticleSystem *system, const ParticleInfo *data );

Bool update(); ///< update this particle's behavior - return false if dead
void doWindMotion(); ///< do wind motion (if present) from particle system
Bool update(); ///< update this particle's behavior - return false if dead

void draw( Real timeScale ); ///< render update
void doWindMotion( Real timeScale ); ///< do wind motion (if present) from particle system

void applyForce( const Coord3D *force ); ///< add the given acceleration

Expand All @@ -205,6 +207,8 @@ class Particle : public MemoryPoolObject,
UnsignedInt getPersonality() { return m_personality; };
void setPersonality(UnsignedInt p) { m_personality = p; };

UnsignedInt getElapsedFrames() const;

protected:

// snapshot methods
Expand All @@ -228,7 +232,6 @@ class Particle : public MemoryPoolObject,
// most of the particle data is derived from ParticleInfo

Coord3D m_accel; ///< current acceleration
Coord3D m_lastPos; ///< previous position
UnsignedInt m_lifetimeLeft; ///< lifetime remaining, if zero -> destroy
UnsignedInt m_createTimestamp; ///< frame this particle was created

Expand Down Expand Up @@ -270,6 +273,8 @@ class ParticleSystemInfo : public Snapshot
virtual void xfer( Xfer *xfer ) override;
virtual void loadPostProcess() override;

void validate();

Bool m_isOneShot; ///< if true, destroy system after one burst has occurred

enum ParticleShaderType
Expand Down Expand Up @@ -320,7 +325,7 @@ class ParticleSystemInfo : public Snapshot
};


RandomKeyframe m_alphaKey[ MAX_KEYFRAMES ];
RandomKeyframe m_alphaKey[ MAX_KEYFRAMES ]; ///< alpha of particle
RGBColorKeyframe m_colorKey[ MAX_KEYFRAMES ]; ///< color of particle

typedef Int Color;
Expand Down Expand Up @@ -596,6 +601,8 @@ class ParticleSystem : public MemoryPoolObject,
virtual Bool update( Int localPlayerIndex ); ///< update this particle system, return false if dead
void updateWindMotion(); ///< update wind motion

void draw( Real timeScale ); ///< render update

void setControlParticle( Particle *p ); ///< set control particle

void start(); ///< (re)start a stopped particle system
Expand Down Expand Up @@ -680,6 +687,12 @@ class ParticleSystem : public MemoryPoolObject,

protected:

struct VisibilityState
{
VisibilityState() : isShrouded(false) {}
Bool isShrouded;
};

// snapshot methods
virtual void crc( Xfer *xfer ) override;
virtual void xfer( Xfer *xfer ) override;
Expand All @@ -689,6 +702,11 @@ class ParticleSystem : public MemoryPoolObject,
ParticlePriorityType priority,
Bool forceCreate = FALSE ); ///< factory method for particles

void updateTransform();
void applyParentTransform(const Matrix3D &parentXfrm);
void applyLocalTransform();

VisibilityState updateVisibility( Int localPlayerIndex );

const ParticleInfo *generateParticleInfo( Int particleNum, Int particleCount ); ///< generate a new, random set of ParticleInfo
const Coord3D *computeParticlePosition(); ///< compute a position based on emission properties
Expand Down Expand Up @@ -754,6 +772,11 @@ class ParticleSystem : public MemoryPoolObject,
/**
* The particle system manager, responsible for maintaining all ParticleSystems
*/
// TheSuperHackers @tweak The particle render update is now decoupled from the logic step.
// The lifetime management and the velocity and rate changes remain coupled to the logic step.
// The render updates integrate exactly one logic time step per logic frame, regardless of how many render updates
// fall into it, so the particles follow the same course as in the original update.
//
class ParticleSystemManager : public SubsystemInterface,
public Snapshot
{
Expand All @@ -770,7 +793,8 @@ class ParticleSystemManager : public SubsystemInterface,

virtual void init() override; ///< initialize the manager
virtual void reset() override; ///< reset the manager and all particle systems
virtual void update() override; ///< update all particle systems
virtual void update() override; ///< logic update for all particle systems
virtual void draw() override; ///< render update for all particle systems

virtual Bool isDummy() const { return false; }

Expand Down Expand Up @@ -840,6 +864,9 @@ class ParticleSystemManager : public SubsystemInterface,
virtual void xfer( Xfer *xfer ) override;
virtual void loadPostProcess() override;

void completeLogicFrameDrawUpdate(); ///< render update for the rest of the current logic frame
void drawSystems( Real timeScale ); ///< render update for all particle systems

Particle *m_allParticlesHead[ NUM_PARTICLE_PRIORITIES ];
Particle *m_allParticlesTail[ NUM_PARTICLE_PRIORITIES ];

Expand All @@ -851,8 +878,8 @@ class ParticleSystemManager : public SubsystemInterface,
UnsignedInt m_fieldParticleCount; ///< this does not need to be xfered, since it is evaluated every frame
UnsignedInt m_particleSystemCount;
Int m_onScreenParticleCount; ///< number of particles displayed on screen per frame
UnsignedInt m_lastLogicFrameUpdate;
Int m_localPlayerIndex; ///<used to tell particle systems which particles can be skipped due to player shroud status
Real m_drawnLogicFramePhase; ///< How far the render updates have integrated the current logic frame, ranging 0 to 1.

private:
TemplateMap m_templateMap; ///< a hash map of all particle system templates
Expand All @@ -878,6 +905,7 @@ class ParticleSystemManagerDummy : public ParticleSystemManager
virtual void reset() override {}
#endif
virtual void update() override {}
virtual void draw() override {}

virtual Bool isDummy() const override { return true; }

Expand Down
32 changes: 27 additions & 5 deletions Core/GameEngine/Source/Common/FramePacer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ FramePacer::FramePacer()
m_maxFPS = BaseFps;
m_logicTimeScaleFPS = LOGICFRAMES_PER_SECOND;
m_updateTime = 1.0f / (Real)BaseFps; // initialized to something to avoid division by zero on first use
m_logicFramePhase = 1.0f;
m_enableFpsLimit = FALSE;
m_enableLogicTimeScale = FALSE;
m_isTimeFrozen = FALSE;
Expand All @@ -52,16 +53,33 @@ FramePacer::~FramePacer()

void FramePacer::update()
{
// TheSuperHackers @bugfix xezon 05/08/2025 Re-implements the frame rate limiter
// with higher resolution counters to cap the frame rate more accurately to the desired limit.
const UnsignedInt maxFps = getActualFramesPerSecondLimit();// allowFpsLimit ? getFramesPerSecondLimit() : RenderFpsPreset::UncappedFpsValue;
// Uses a high resolution counter to cap the frame rate more accurately to the desired limit than retail did.
const UnsignedInt maxFps = getActualFramesPerSecondLimit();
m_updateTime = m_frameRateLimit.wait(maxFps);

if (TheGameLogic != nullptr)
{
// Set or advance the logic frame phase by the render step that the next update will draw.
// It is capped at a whole logic frame, because the render steps in between can add up to more than one.
// Consumers are expected to interpolate towards the next logic frame and not extrapolate past it.
const Real timeScale = getActualLogicTimeScaleOverFpsRatio();

if (TheGameLogic->hasUpdated())
{
m_logicFramePhase = timeScale;
}
else
{
m_logicFramePhase = min(1.0f, m_logicFramePhase + timeScale);
}
}
}

void FramePacer::reset()
{
m_frameRateLimit.reset();
m_updateTime = 1.0f / (Real)getActualFramesPerSecondLimit();
m_logicFramePhase = 1.0f;
}

void FramePacer::setFramesPerSecondLimit( Int fps )
Expand Down Expand Up @@ -204,8 +222,7 @@ Real FramePacer::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) const

Real FramePacer::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) const
{
// TheSuperHackers @info Clamps ratio to min 1, because the logic
// frame rate is currently capped by the render frame rate.
// Clamps ratio to min 1, because the logic frame rate is currently capped by the render frame rate.
return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps());
}

Expand All @@ -218,3 +235,8 @@ Real FramePacer::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) const
{
return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags);
}

Real FramePacer::getLogicFramePhase() const
{
return m_logicFramePhase;
}
Loading
Loading