Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a game loop in reactive-banana?

This question is specific to reactive-banana and real-time simulations with a physical and visual component (eg., games).

According to Fix Your Timestep! the ideal way to setup a game loop (assuming physics that needs to be reproducible), you need a fixed timestep between frames. After considering a number of real complications , the author arrives at this game loop:

double t = 0.0;
const double dt = 0.01;

double currentTime = hires_time_in_seconds();
double accumulator = 0.0;

State previous;
State current;

while ( !quit )
{
     double newTime = time();
     double frameTime = newTime - currentTime;
     if ( frameTime > 0.25 )
          frameTime = 0.25;   // note: max frame time to avoid spiral of death
     currentTime = newTime;

     accumulator += frameTime;

     while ( accumulator >= dt )
     {
          previousState = currentState;
          integrate( currentState, t, dt );
          t += dt;
          accumulator -= dt;
     }

     const double alpha = accumulator / dt;

     State state = currentState*alpha + previousState * ( 1.0 - alpha );

     render( state );
}

The synopsis is that the physics simulation is always fed the same increment of time (dt) for numerical stability. Arranging for that must consider that physics and visuals may update at different frequencies and you don't want to get too far behind.

For example, you may want updates at a frequency of 20hz, but a visual update with a framerate of 60hz. This loop does linear interpolation of the physics to make up the difference between physics updates and graphical updates.

Additionally, when the difference in time between frames is much larger than dt there is a loop to handle stepping the updates in chunks of dt. The note about the spiral of death just refers to a case when your physics calculation simply can't keep up with the desired frequency of updates, so you allow it to skip some updates.

For this discussion, the part I'm most interested in is arranging so that the call to the physics engine (the call to integrate) is always stepped by dt. Does reactive-banana allow the user to write this style loop? If so how? Perhaps an example doing real-time physics simulation is in order (or already exists)?

like image 401
Jason Dagit Avatar asked Oct 02 '12 06:10

Jason Dagit


1 Answers

For this discussion, the part I'm most interested in is arranging so that the call to the physics engine (the call to integrate) is always stepped by dt. Does reactive-banana allow the user to write this style loop?

Yes, reactive-banana can do that.

The idea is that you write a custom event loop and hook reactive-banana into that. The library doesn't make any assumptions as to where you get your events from, it "only" solves the problem of neatly describing new events in terms of existing ones. In particular, you can use the newAddHandler function to create two callback functions that are called in the appropriate places in the event loop. Essentially, reactive-banana is just a mind-boggling method to write ordinary callback functions that maintain state. When and how you call these functions is up to you.

Here a general outline:

-- set up callback functions
(renderEvent, render) <- newAddHandler
(stateUpdateEvent, stateUpdate) <- newAddHandler

-- make the callback functions do something interesting
let networkDescription = do
    eRender      <- fromAddHandler renderEvent
    eStateUpdate <- fromAddHandler stateUpdateEvent
    ...
    -- functionality here

actuate =<< compile networkDescription

-- event loop
while (! quit)
{
    ...
    while (accumulator >= dt)
    {
        stateUpdate (t,dt)      -- call one callback
        t += dt
        accumulator -= dt
    }
    ...
    render ()                   -- call another callback
}

In fact, I have written a game loop example in this style for an older version of reactive-banana, but haven't gotten around to polishing and publishing it on hackage. The important things that I would like to see completed are:

  • Pick a graphics engine that is easy to install and works in GHCi. The concept uses SDL, but this is really quite awkward as it cannot be used from GHCi. Something like OpenGL + GLFW would be nice.
  • Offer a small abstraction to make it easier to write the interpolation phase. Probably just two things: an event eTimer :: Event t () that represents the regular physics updates and a behavior bSinceLastTimer :: Behavior t TimeDiff that measures the time since the last physics updates, which can be used for doing the interpolation. (It's a behavior instead of an event, so the internal "draw this!" updates are transparent.)

Andreas Bernstein's blackout clone using reactive-banana may be an excellent example to implement in this style.

like image 139
Heinrich Apfelmus Avatar answered Nov 11 '22 01:11

Heinrich Apfelmus