packages feed

keid-frp-banana-0.1.0.0: src/Engine/ReactiveBanana/Course.hs

{-# LANGUAGE RecursiveDo #-}

module Engine.ReactiveBanana.Course
  ( Course(..)

  , setup

  , whenIdle
  , whenActive
  , whenFinished

  , isIdle
  , isActive
  , isFinished
  , when
  , unless
  ) where

import RIO hiding (when, unless)

import Data.Maybe (fromJust)
import Data.These (These(..))
import Reactive.Banana qualified as RB
import Reactive.Banana.Frameworks qualified as RBF

data Course a
  = Idle
  | Active a
  | Finished
  deriving (Eq, Ord, Show, Functor)

setup
  :: RB.Event a
  -> RB.Event (a -> Either final a)
  -> RBF.MomentIO (RB.Event a, RB.Event final, RB.Behavior (Course a))
setup triggerE stepE = mdo
  (e, b) <- RB.mapAccum Idle $
    fmap dispatch $
      RB.merge triggerE (whenActive b stepE)

  (activeE, fireActive) <- RBF.newEvent
  RBF.reactimate $ (e RB.@> b) <&> \case
    Active a ->
      fireActive a
    _ ->
      pure ()

  finishedE <- RB.once $
    fmap fromJust $ RB.filterE isJust e
  pure (activeE, finishedE, b)
  where
    dispatch = \case
      This initial -> \case
        Idle ->
          (Nothing, Active initial)
        Active{} ->
          error "trigger not filtered when active"
        Finished ->
          error "trigger not filtered when finished"

      That step -> \case
        Idle ->
          error "tick not filtered when idle"
        Active current ->
          case step current of
            Left done ->
              (Just done, Finished)
            Right next ->
              (Nothing, Active next)
        Finished ->
          error "tick not filtered when finished"

      These{} ->
        error "tick happened on top of trigger"

when :: (Course a -> Bool) -> RB.Behavior (Course a) -> RB.Event e -> RB.Event e
when pred course =
  RB.filterApply (course <&> const . pred)

unless :: (Course a -> Bool) -> RB.Behavior (Course a) -> RB.Event e -> RB.Event e
unless pred course =
  RB.filterApply (course <&> const . not . pred)

whenIdle :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenIdle = when isIdle

whenActive :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenActive = when isActive

whenFinished :: RB.Behavior (Course a) -> RB.Event e -> RB.Event e
whenFinished = when isFinished

isIdle :: Course a -> Bool
isIdle = \case
  Idle -> True
  _    -> False

isActive :: Course a -> Bool
isActive = \case
  Active{} -> True
  _        -> False

isFinished :: Course a -> Bool
isFinished = \case
  Finished -> True
  _        -> False