In my previous post I asked how to render a grid-based game. I’ve decided to go with Halogen hooks. And well, here I am now.
I’d like to discuss potential ways to implement a simple game-loop that would be rendered using a HTML5 canvas managed by Halogen. My ultimate goal is to make a very simple library that my students could use to implement simple games (sokoban, GoL, snake).
My initial idea was to to have a hook-based Halogen component that manages both the main loop and the rendering. The component will receive a Config state as its input with the following fields:
type Config state =
  { initialState :: state
  , draw :: state -> Grid
  , onTick :: state -> state
  }
I planned to call the onTick function in a recursive requestAnimationFrame loop, modifying the state and requesting the renders at the same time, similarly to the following (please ignore the missing liftEffects):
gridComponent { onTick, draw, initialState } = do
  _ /\ stateId <- Hooks.useState initialState
  Hooks.useLifecycleEffect $
    -- Start the game loop
    requestAnimationFrame (ticker stateId)
  where
    ticker stateId = do
      state <- Hooks.get stateId
      let newState = onTick state
      Hooks.modify_ stateId newState
      renderGrid (draw newState)
      requestAnimationFrame (ticker stateId)
(The code is simplified, I have already implemented a functioning mouse- and key- event tracking into the component.)
However, requestAnimationFrame needs to run an Effect Unit, while the inner state of a hook-based component can only be modified in a HookM. Thus, my initial idea seems to be impossible to implement — unless it’s possible to run a MonadEffect m inside an Effect somehow.
What would be the correct course of action here?
As a sidenote, do you think a onTick :: state -> state is enough, or should I allow some effects there? I  guess requiring effectful functions (like with onTick :: state -> Effect state) is an overkill and a bad practice — again, I’m open to suggestions.
Tagging @thomashoneyman for obvious reasons.