How to implement a game loop in reactive-banana?
Asked Answered
V

1

19

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)?

Vagrom answered 2/10, 2012 at 6:24 Comment(0)
C
18

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.

Cauvery answered 2/10, 2012 at 17:14 Comment(8)
I'm not sure about SDL, but with OpenGL and GLFW they both use thread local storage on the process's original thread (it has to be that original thread, vendor limitation). GHCi runs each command in a different thread by default. This means that libraries like OpenGL/GLFW (and several other gui libraries) can't correctly access their thread local storage and go bonkers from GHCi. The solution is to add -fno-ghci-sandbox when starting GHCi. You might try this and see if it fixes your SDL + GHCi woes: haskell.org/ghc/docs/7.0.1/html/users_guide/release-7-0-1.htmlVagrom
It's a little more difficult than -fno-ghci-sandbox, I am afraid. On Mac, SDL needs to be compiled because it redefines main to be a macro. The GLFW versions tend to crash in GHCi due to other incompatibilities I don't understand.Cauvery
GLFW-b should not crash from GHCi if you use -fno-ghci-sandbox. If it does, you're hitting a new bug so please make a report! :)Vagrom
Ok, I will check it out again when I have time. Thanks Jason!Cauvery
@JasonDagit Yup, the GLWF-b0demo application freezes and crashes after a couple of seconds when calling it from GHCi on OS X (with -fno-ghci-sandbox and both with and without the EnableGUI.hs trick).Cauvery
@HeinrichApfelmus your game loop example is great, but I'm totally unable to get it working. Also , Maybe iit makes clear that reactive programming isn't useful for a game loop, What's the advantage of fire an draw event compared with actually call draw function?Sori
@Zhen: I wrote the example for an older version of reactive-banana and haven't updated it, that's why it doesn't work. The advantage is in the "functionality here" part that I skipped. FRP gives you powerful tools to combine existing events and keep track of internal state. As you can see, it's decoupled from the game loop, too.Cauvery
I really want to understand the answer here to apply in the context of Sodium/Typescript... but I can't understand Haskell (yet!)... would someone be so kind as to translate it into Javascript and requestAnimationFrame ? I have a similar question on the Sodium Forum - and you can see I quoted @HeinrichApfelmus from somewhere else there: sodium.nz/t/need-sodium-in-this-case-workers-and-single-state/… (if SO isn't the right place, feel free to email me - [email protected])Ardolino

© 2022 - 2024 — McMap. All rights reserved.