rhine-gloss-0.7.0: src/FRP/Rhine/Gloss/Pure/Combined.hs
{- | A pure @gloss@ backend for Rhine,
with separated event and simulation loop.
To run pure Rhine apps with @gloss@,
write a signal network ('SN') in the 'GlossCombinedClock' and use 'flowGlossCombined'.
As an easy starter, you can use the helper function 'buildGlossRhine'.
-}
{-# LANGUAGE Arrows #-}
{-# LANGUAGE RecordWildCards #-}
module FRP.Rhine.Gloss.Pure.Combined where
-- transformers
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Writer
-- dunai
import qualified Control.Monad.Trans.MSF.Reader as MSFReader
import Data.MonadicStreamFunction.InternalCore
-- rhine
import FRP.Rhine
import FRP.Rhine.Reactimation.ClockErasure
-- rhine-gloss
import FRP.Rhine.Gloss.Common
import FRP.Rhine.Gloss.Pure
-- | The overall clock of a pure @rhine@ 'SN' that can be run by @gloss@.
-- It is combined of two subsystems, the event part and the simulation part.
-- @a@ is the type of subevents that are selected.
type GlossCombinedClock a
= SequentialClock GlossM
(GlossEventClock a)
GlossSimulationClock
-- | Schedule the subclocks of the 'GlossCombinedClock'.
glossSchedule :: Schedule GlossM (GlossEventClock a) GlossSimulationClock
glossSchedule = schedSelectClocks
-- ** Events
-- | The clock that ticks whenever a specific @gloss@ event occurs.
type GlossEventClock a = SelectClock GlossClock a
-- | Select the relevant events by converting them to @Just a@,
-- and the irrelevant ones to 'Nothing'.
glossEventSelectClock
:: (Event -> Maybe a)
-> GlossEventClock a
glossEventSelectClock selector = SelectClock
{ mainClock = GlossClock
, select = (>>= selector)
}
-- | Tick on every event.
glossEventClock :: GlossEventClock Event
glossEventClock = glossEventSelectClock Just
-- ** Simulation
-- | The clock that ticks for every @gloss@ simulation step.
type GlossSimulationClock = SelectClock GlossClock ()
glossSimulationClock :: GlossSimulationClock
glossSimulationClock = SelectClock { .. }
where
mainClock = GlossClock
select (Just _event) = Nothing
select Nothing = Just ()
-- * Signal networks
{- |
The type of a valid 'Rhine' that can be run by @gloss@,
if you chose to separate events and simulation into two subsystems.
@a@ is the type of subevents that are selected.
All painting has to be done in 'GlossM', e.g. via the 'paint' method.
Typically, such a 'Rhine' is built something like this:
@
-- | Select only key press events
myEventClock :: GlossEventClock Key
myEventClock = glossEventSelectClock selector
where
selector (EventKey key _ _ _) = Just key
selector _ = Nothing
myEventSubsystem :: ClSF GlossM GlossEventClock () MyType
myEventSubsystem = ...
mySim :: ClSF GlossM GlossSimulationClock [MyType] ()
mySim = ...
myGlossRhine :: GlossRhine a
myGlossRhine
= myEventSubsystem @@ myEventClock >-- collect -@- glossSchedule --> mySim @@ glossSimulationClock
@
-}
type GlossRhine a = Rhine GlossM (GlossCombinedClock a) () ()
{- | For most applications, it is sufficient to implement
a single signal function
that is called with a list of all relevant events
that occurred in the last tick.
-}
buildGlossRhine
:: (Event -> Maybe a) -- ^ The event selector
-> ClSF GlossM GlossSimulationClock [a] () -- ^ The 'ClSF' representing the game loop.
-> GlossRhine a
buildGlossRhine selector clsfSim
= timeInfoOf tag @@ glossEventSelectClock selector
>-- collect -@- glossSchedule
--> clsfSim @@ glossSimulationClock
-- * Reactimation
-- | The main function that will start the @gloss@ backend and run the 'SN'.
flowGlossCombined
:: GlossSettings
-> GlossRhine a -- ^ The @gloss@-compatible 'Rhine'.
-> IO ()
flowGlossCombined settings Rhine { .. } = flowGlossWithWorldMSF settings clock $ proc tick -> do
eraseClockSN 0 sn -< case tick of
(_ , Left event) -> (0 , Left event, Just ())
(diffTime, Right () ) -> (diffTime, Right () , Nothing)