Why does changing my FRP implementation to be more reactive lag?

95 Views Asked by At

I got a version of snake working with the threepenny-gui library, but I didn't like the fact that I was explicitly calling newEvent and addStateUpdate manually instead of defining the behavior completely based on events, e.g. this:

(updates, addUpdate) <- liftIO newEvent
managerB <- accumB initialManager updates

on UI.tick timer $ \_ -> addUpdate $ \manager -> manager'

compared to:

managerB <- accumB initialManager $
  UI.tick timer $> \manager -> manager'

IIUC the second is more idiomatic FRP, as it defines a behavior with the actual event instead of creating a proxy event to proxy updates through. But when I make this change, it causes one of two problems:

  1. If I define managerB first (using RecursiveDo to access timer, which is defined below), nothing's rendered at all
  2. If I move managerB to the end (using RecursiveDo to access managerB from the DOM elements), the initial movement when hitting an arrow key for the first time lags, and the frames render in a jerky fashion.

Am I doing something wrong? What's the idiomatic way I should structure these events/behaviors?

Code diff here: https://github.com/brandonchinn178/snake/compare/inline-event-handlers

1

There are 1 best solutions below

0
On

A not particularly pretty workaround for the jerkiness, which I tested on the alternate branch in your repository, is, so to say, using both approaches at once: re-firing the tick event and using that instead of UI.tick timer to define managerB:

  (timeE, fireTime) <- liftIO newEvent
  on UI.tick timer $ \_ -> liftIO (fireTime ())

  let managerUpdateE =
        fmap concatenate . unions $
          [ timeE $> getNextManagerState
          --  Instead of: UI.tick timer $> getNextManagerState
          --  etc.

The issue appears to be that plugging UI.tick timer directly into the event network somehow gets in the way of Threepenny sending the JavaScript calls needed to update the UI in a timely way. The indirection in using on with fireTime (which, in particular, should mean timeE happens notionally after UI.tick timer) seems to skirt around the problem. A less intrusive workaround would be, instead of introducing timeE, explicitly calling flushCallBuffer in a handler for UI.tick timer; in my tests, however, that reduced the jerkiness a lot but didn't eliminate it completely. (See also threepenny-gui issue #191 for possibly relevant background information.)

As for the delay on the first keystroke, it appears that can be eliminated by moving your invocation of UI.start timer to the very end of gui, after managerB and the rest of your event network is set up.

(On an additional note, it is probably a good idea to follow the recommendation of the Graphics.UI.Threepenny.Timer docs and set -threaded in the ghc-options for compiling your executable, even if that doesn't seem to have an effect on the problem you describe here.)