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 liftEffect
s):
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.