1
\$\begingroup\$

So I wrote this fixed timestep & rendering game loop. I am posting it here to see if there are ways to optimize it/make it more accurate.


    const float updatesRate = 1 / 60.0f;
    const float framesRate = 1 / 308.0f;

    double currentTime = hireTimeInSeconds( );
    double accumulator = 0.0;
    double accumulator2 = 0.0;
    uint32_t frames = 0, updates = 0;
    double timer = hireTimeInSeconds( );

    while ( true ) {

        double newTime = hireTimeInSeconds( );

        if ( newTime - timer >= 1 ) {

            //Printing fps and ups here

            updates = 0;
            frames = 0;
            timer = newTime;
        }

        double frameTime = newTime - currentTime;
        if ( frameTime > 0.25 ) frameTime = 0.25;
        currentTime = newTime;
        accumulator += frameTime;
        accumulator2 += frameTime;

        //Polling input here

        while ( accumulator >= updatesRate ) {

            //Updating here
            accumulator -= updatesRate;
            updates++;
        }

        const double alpha = accumulator / updatesRate;

        while ( accumulator2 >= framesRate ) {
            //Rendering
            frames++;
            accumulator2 -= framesRate;
        }
    }

``` 
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$
const float updatesRate = 1 / 60.0f;
const float framesRate = 1 / 308.0f;

The other time values are all doubles, it's a bit strange to use floats here.


    if ( newTime - timer >= 1 ) {
    ...
    if ( frameTime > 0.25 ) frameTime = 0.25;

These magic numbers should be named constants.


C++ now provides a variety of time-related utilities through the <chrono> header. We should use the clock types, type-safe duration types, and conversions that it provides.


double accumulator = 0.0;
double accumulator2 = 0.0;
uint32_t frames = 0, updates = 0;
double timer = hireTimeInSeconds( );

The naming of these variables leaves their purpose very unclear. Perhaps we could define an Accumulator class to group the variables for each accumulator and remove the duplication (see example code below).


hireTimeInSeconds This should probably be highResTimeInSeconds.


    while ( accumulator2 >= framesRate ) {
        //Rendering
        frames++;
        accumulator2 -= framesRate;
    }

I don't think this makes sense. We only want to render one frame, even if lots of time has passed. (We're not doing any updates between rendering frames in this loop, so we'd just be rendering the exact same thing over and over).


Applying the above suggestions, we might get something like the code below. It is quite a few more lines of code, but is also much more reusable (especially the accumulator and frame_timer classes), and is hopefully clearer in intent:

#include <chrono>
#include <iostream>

template<class c_t>
class accumulator
{
public:

    using clock_t = c_t;
    using duration_t = typename clock_t::duration;

    template<class d_t>
    explicit accumulator(d_t tick_length):
        m_tick_length(std::chrono::duration_cast<duration_t>(tick_length)) { }

    std::size_t accumulate(duration_t delta_time) // note: returns the number of ticks of m_tick_length triggered by this call
    {
        m_accumulated_time += delta_time;

        auto ticks = std::size_t{ 0 };

        while (m_accumulated_time >= m_tick_length)
        {
            ++ticks;
            m_accumulated_time -= m_tick_length;
        }

        return ticks;
    }

    duration_t get_tick_length() const
    {
        return m_tick_length;
    }

private:

    duration_t m_tick_length;
    duration_t m_accumulated_time;
};

template<class c_t>
class frame_timer
{
public:

    using clock_t = c_t;
    using duration_t = typename clock_t::duration;
    using time_point_t = typename clock_t::time_point;

    template<class d_t>
    explicit frame_timer(d_t initial_last_frame_time):
        m_last_frame_time(std::chrono::duration_cast<duration_t>(initial_last_frame_time)),
        m_last_tick(clock_t::now()) { }
    
    void tick()
    {
        auto now = clock_t::now();
        m_last_frame_time = now - m_last_tick;
        m_last_tick = now;
    }

    duration_t get_last_frame_time() const
    {
        return m_last_frame_time;
    }

private:

    duration_t m_last_frame_time;
    time_point_t m_last_tick;
};

int main()
{
    using clock_t = std::chrono::high_resolution_clock;
    using duration_t = clock_t::duration;
    using time_point_t = clock_t::time_point;
    using seconds_t = std::chrono::duration<float>;

    auto const update_time = seconds_t(1.f / 60.f);
    auto update_accumulator = accumulator<clock_t>(update_time);

    auto const render_time = seconds_t(1.f / 308.f);
    auto render_accumulator = accumulator<clock_t>(render_time);

    auto const print_time = seconds_t(1.f);
    auto print_accumulator = accumulator<clock_t>(print_time);

    auto const initial_frame_time = update_time;
    auto timer = frame_timer<clock_t>(initial_frame_time);

    auto const max_frame_time = std::chrono::duration_cast<duration_t>(seconds_t(0.25f));

    while (true)
    {
        auto const last_frame_time = (timer.get_last_frame_time() > max_frame_time) ? max_frame_time : timer.get_last_frame_time();

        if (print_accumulator.accumulate(last_frame_time) != 0) // note: will skip update periods if frames are super long (> print_time)
        {
            // ... print frames
            std::cout << "ping!" << std::endl;
        }

        auto const update_ticks = update_accumulator.accumulate(last_frame_time); // note: don't put this directly in the loop condition, we only want to call it once!

        for (auto i = std::size_t{ 0 }; i != update_ticks; ++i)
        {
            // ... do update
            std::cout << "update" << std::endl;
        }

        if (render_accumulator.accumulate(last_frame_time) != 0) // note: only ever render 1 frame, even if we should have rendered more
        {
            // ... render
            std::cout << "render" << std::endl;
        }

        timer.tick();
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.