requestAnimationFrame in halogen-subscriptions (upgrading from Halogen v5)

@garyb helped me with this code a long time ago with an older version of Halogen. I can’t seem to figure out the best way to update it to PureScript v0.14 and Halogen v6.1.

import My.Prelude

import Effect.Ref as Ref
import Halogen as H
import Halogen.Query.EventSource as HES
import Web.HTML as HTML
import Web.HTML.Window as Window


handleAction = case _ of
  Initialize → do
    subscription ←
      H.subscribe
        $ HES.effectEventSource \emitter → do
            ref ← Ref.new Nothing
            let
              loop = do
                HES.emit emitter Tick
                animationID ← Window.requestAnimationFrame loop =<< HTML.window
                Ref.write (Just animationID) ref
            loop
            pure
              $ HES.Finalizer do
                  Ref.read ref
                    >>= traverse_ \animationID → do
                      HTML.window >>= Window.cancelAnimationFrame animationID

Since requestAnimationFrame isn’t an event listener, I don’t understand how to subscribe. Honestly I can’t even tell if I’m supposed to use HS.create or HS.makeEmitter in this case – especially since it needs the complementary cancelAnimationFrame on cancelation. There’s no concrete examples for makeEmitter and makeEmitter = coerce doesn’t make it clear (the signature looks vaguely like makeAff but I could be wrong). I would assume you could use a reference to the animationID like pre-Halogen-v6 subscription example.

Surely this is (or will be) a common request. Would this not be a good example for the purescript-halogen-subscriptions project, or a tangential purescript-halogen/purescript-halogen-subscriptions-request-animation-frame?

It just needs a few tweaks from what you had:

subscription ← H.subscribe $ HS.makeEmitter \emit →
  H.liftEffect do
    ref ← Ref.new Nothing
    let
      loop = do
        emit Tick
        animationID ← Window.requestAnimationFrame loop =<< HTML.window
        Ref.write (Just animationID) ref
    loop
    pure do
      Ref.read ref
        >>= traverse_ \animationID → do
          HTML.window >>= Window.cancelAnimationFrame animationID

But yeah, something like this would be a good example to include!

2 Likes

I don’t know that much about halogen yet, but i’m interested in whether this is possible without keeping track of the id in a ref. Would it be possible to make an emitter from an aff, which would emit when it resolves, and relaunch the aff, waiting for it to resolve again? if so, you could probably use something like this: https://github.com/artemisSystem/purescript-game/blob/v2.0.1/src/Game/Aff/AnimationFrame.purs#L15-L19, or a version that’s just Aff Unit that doesnt require passing in the window.

Actually, would something like this work?

HS.makeEmitter \emit -> do
  fiber <- launchAff $ forever do
    delayFrame
    liftEffect (emit Tick)
  pure $ launchAff_ do
    killFiber (error "stopped requestAnimationFrame") fiber

Something like that would be an option too, yep. I think the Ref approach has less overhead since it’s not relying on Aff machinery, but the Aff implementation is much nicer aesthetically for sure.

2 Likes