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
5 changes: 5 additions & 0 deletions Core/GameEngine/Include/Common/FramePacer.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class FramePacer
void update(); ///< Signal that the app/render update is done and wait for the fps limit if applicable.
void reset(); ///< Move the frame timing anchor to now and predict the next update time from the target frame rate. Call after a long blocking operation so its duration does not leak into the next frame delta.

void onNewLogicFrame(); ///< Signal that a new logic frame has begun.

void setFramesPerSecondLimit( Int fps ); ///< Set the update fps limit.
Int getFramesPerSecondLimit() const; ///< Get the update fps limit.
void enableFramesPerSecondLimit( Bool enable ); ///< Enable or disable the update fps limit.
Expand All @@ -67,6 +69,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 +79,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
57 changes: 49 additions & 8 deletions Core/GameEngine/Include/GameClient/ParticleSys.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,12 @@ 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

// TheSuperHackers @info The force must be applied at full magnitude on every render step.
void applyForce( const Coord3D *force ); ///< add the given acceleration

const Coord3D *getPosition() { return &m_pos; }
Expand All @@ -205,14 +208,19 @@ class Particle : public MemoryPoolObject,
UnsignedInt getPersonality() { return m_personality; };
void setPersonality(UnsignedInt p) { m_personality = p; };

UnsignedInt getElapsedFrames() const;

protected:

// snapshot methods
virtual void crc( Xfer *xfer ) override;
virtual void xfer( Xfer *xfer ) override;
virtual void loadPostProcess() override;

#if RETAIL_COMPATIBLE_XFER_SAVE
void computeAlphaRate(); ///< compute alpha rate to get to next key
#endif
Real computeKeyframeAlpha( Real elapsedFrames ) const; ///< compute the alpha that the key frames describe for the current age of this particle
void computeColorRate(); ///< compute color change to get to next key

public:
Expand All @@ -228,12 +236,13 @@ 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

Real m_alpha; ///< current alpha of this particle
Real m_alphaRate; ///< current rate of alpha change
#if RETAIL_COMPATIBLE_XFER_SAVE
Real m_alphaRate; ///< current rate of alpha change (LEGACY)
#endif
Int m_alphaTargetKey; ///< next index into key array

RGBColor m_color; ///< current color of this particle
Expand Down Expand Up @@ -270,6 +279,8 @@ class ParticleSystemInfo : public Snapshot
virtual void xfer( Xfer *xfer ) override;
virtual void loadPostProcess() override;

void validate(const char *systemName);

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

enum ParticleShaderType
Expand Down Expand Up @@ -320,7 +331,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 @@ -457,6 +468,11 @@ class ParticleSystemInfo : public Snapshot
Real m_windMotionEndAngleMax; ///< (for ping pong) max angel for angle 2
Byte m_windMotionMovingToEndAngle; ///< (for ping pong) TRUE if we're moving "towards" the end angle

private:

template <typename KeyframeType>
static void validateKeyframes(KeyframeType *keys, Int keysSize, const char *keyName, const char *systemName);
static void validateDampingValue(GameClientRandomVariable &value, const char *systemName);
};

//--------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -581,7 +597,9 @@ class ParticleSystem : public MemoryPoolObject,
void attachToObject( const Object *obj ); ///< attach this particle system to an Object

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 updateWindMotion( Real timeScale ); ///< update wind motion

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

Expand Down Expand Up @@ -666,6 +684,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 @@ -675,6 +699,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 @@ -740,6 +769,9 @@ 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 remains coupled to the logic step.
//
class ParticleSystemManager : public SubsystemInterface,
public Snapshot
{
Expand All @@ -756,7 +788,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 @@ -837,7 +870,6 @@ 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

private:
Expand All @@ -864,6 +896,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 All @@ -884,3 +917,11 @@ extern ParticleSystemManager *TheParticleSystemManager;

class DebugDisplayInterface;
extern void ParticleSystemDebugDisplay( DebugDisplayInterface *dd, void *, FILE *fp = nullptr );

namespace pfx
{
inline Real clampDampingValue(Real value)
{
return std::max(0.0f, value);
}
} // namespace pfx
28 changes: 23 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,29 @@ 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);

// 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 when
// the render frame rate is not a multiple of the logic frame rate. Consumers are expected to interpolate
// towards the next logic frame and not extrapolate past it.
const Real timeScale = getActualLogicTimeScaleOverFpsRatio();
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::onNewLogicFrame()
{
// Restarts the logic frame phase.
m_logicFramePhase = 0.0f;
}

void FramePacer::setFramesPerSecondLimit( Int fps )
Expand Down Expand Up @@ -204,8 +218,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 +231,8 @@ Real FramePacer::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) const
{
return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags);
}

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