rhine-gloss 0.6.0.1 → 0.7.0
raw patch · 9 files changed
+510/−184 lines, 9 filesdep +transformersdep ~rhinePVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
Dependency ranges changed: rhine
API changes (from Hackage documentation)
- FRP.Rhine.Gloss: buildGlossRhine :: (Event -> Maybe a) -> GlossClSF a -> GlossRhine a
- FRP.Rhine.Gloss: flowGloss :: Display -> Color -> Int -> GlossRhine a -> IO ()
- FRP.Rhine.Gloss: type GlossClSF a = ClSF Identity GlossSimulationClock [a] Picture
- FRP.Rhine.Gloss: type GlossClock a = SequentialClock Identity (SelectClock GlossEventClock a) GlossSimulationClock_
- FRP.Rhine.Gloss: type GlossRhine a = Rhine Identity (GlossClock a) () Picture
+ FRP.Rhine.Gloss.Common: GlossSettings :: Display -> Color -> Int -> GlossSettings
+ FRP.Rhine.Gloss.Common: [backgroundColor] :: GlossSettings -> Color
+ FRP.Rhine.Gloss.Common: [display] :: GlossSettings -> Display
+ FRP.Rhine.Gloss.Common: [stepsPerSecond] :: GlossSettings -> Int
+ FRP.Rhine.Gloss.Common: data GlossSettings
+ FRP.Rhine.Gloss.Common: defaultSettings :: GlossSettings
+ FRP.Rhine.Gloss.IO: GlossEventClockIO :: GlossEventClockIO
+ FRP.Rhine.Gloss.IO: GlossSimClockIO :: GlossSimClockIO
+ FRP.Rhine.Gloss.IO: clearIO :: MonadIO m => GlossConcT m ()
+ FRP.Rhine.Gloss.IO: data GlossConcT m a
+ FRP.Rhine.Gloss.IO: data GlossEventClockIO
+ FRP.Rhine.Gloss.IO: data GlossSimClockIO
+ FRP.Rhine.Gloss.IO: flowGlossIO :: (Clock (GlossConcT IO) cl, GetClockProxy cl, Time cl ~ Time (In cl), Time cl ~ Time (Out cl)) => GlossSettings -> Rhine (GlossConcT IO) cl () () -> IO ()
+ FRP.Rhine.Gloss.IO: glossConcurrently :: (Monad IO, Clock (GlossConcT IO) cl1, Clock (GlossConcT IO) cl2, Time cl1 ~ Time cl2) => Schedule (GlossConcT IO) cl1 cl2
+ FRP.Rhine.Gloss.IO: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (FRP.Rhine.Gloss.IO.GlossConcT m)
+ FRP.Rhine.Gloss.IO: instance Control.Monad.IO.Class.MonadIO m => FRP.Rhine.Clock.Clock (FRP.Rhine.Gloss.IO.GlossConcT m) FRP.Rhine.Gloss.IO.GlossEventClockIO
+ FRP.Rhine.Gloss.IO: instance Control.Monad.IO.Class.MonadIO m => FRP.Rhine.Clock.Clock (FRP.Rhine.Gloss.IO.GlossConcT m) FRP.Rhine.Gloss.IO.GlossSimClockIO
+ FRP.Rhine.Gloss.IO: instance Control.Monad.Trans.Class.MonadTrans FRP.Rhine.Gloss.IO.GlossConcT
+ FRP.Rhine.Gloss.IO: instance FRP.Rhine.Clock.Proxy.GetClockProxy FRP.Rhine.Gloss.IO.GlossEventClockIO
+ FRP.Rhine.Gloss.IO: instance FRP.Rhine.Clock.Proxy.GetClockProxy FRP.Rhine.Gloss.IO.GlossSimClockIO
+ FRP.Rhine.Gloss.IO: instance GHC.Base.Applicative m => GHC.Base.Applicative (FRP.Rhine.Gloss.IO.GlossConcT m)
+ FRP.Rhine.Gloss.IO: instance GHC.Base.Functor m => GHC.Base.Functor (FRP.Rhine.Gloss.IO.GlossConcT m)
+ FRP.Rhine.Gloss.IO: instance GHC.Base.Monad m => GHC.Base.Monad (FRP.Rhine.Gloss.IO.GlossConcT m)
+ FRP.Rhine.Gloss.IO: launchGlossThread :: MonadIO m => GlossSettings -> GlossConcT m a -> m a
+ FRP.Rhine.Gloss.IO: paintAllIO :: MonadIO m => Picture -> GlossConcT m ()
+ FRP.Rhine.Gloss.IO: paintIO :: MonadIO m => Picture -> GlossConcT m ()
+ FRP.Rhine.Gloss.Pure: GlossClock :: GlossClock
+ FRP.Rhine.Gloss.Pure: clear :: GlossM ()
+ FRP.Rhine.Gloss.Pure: currentEvent :: ClSF GlossM GlossClock () (Maybe Event)
+ FRP.Rhine.Gloss.Pure: data GlossClock
+ FRP.Rhine.Gloss.Pure: data GlossM a
+ FRP.Rhine.Gloss.Pure: flowGloss :: GlossSettings -> GlossClSF -> IO ()
+ FRP.Rhine.Gloss.Pure: flowGlossWithWorldMSF :: Clock GlossM cl => GlossSettings -> cl -> MSF GlossM (Time cl, Tag cl) b -> IO ()
+ FRP.Rhine.Gloss.Pure: instance FRP.Rhine.Clock.Clock FRP.Rhine.Gloss.Pure.GlossM FRP.Rhine.Gloss.Pure.GlossClock
+ FRP.Rhine.Gloss.Pure: instance FRP.Rhine.Clock.Proxy.GetClockProxy FRP.Rhine.Gloss.Pure.GlossClock
+ FRP.Rhine.Gloss.Pure: instance GHC.Base.Applicative FRP.Rhine.Gloss.Pure.GlossM
+ FRP.Rhine.Gloss.Pure: instance GHC.Base.Functor FRP.Rhine.Gloss.Pure.GlossM
+ FRP.Rhine.Gloss.Pure: instance GHC.Base.Monad FRP.Rhine.Gloss.Pure.GlossM
+ FRP.Rhine.Gloss.Pure: instance GHC.Base.Semigroup FRP.Rhine.Gloss.Pure.GlossClock
+ FRP.Rhine.Gloss.Pure: paint :: Picture -> GlossM ()
+ FRP.Rhine.Gloss.Pure: paintAll :: Picture -> GlossM ()
+ FRP.Rhine.Gloss.Pure: type GlossClSF = ClSF GlossM GlossClock () Picture
+ FRP.Rhine.Gloss.Pure.Combined: buildGlossRhine :: (Event -> Maybe a) -> ClSF GlossM GlossSimulationClock [a] () -> GlossRhine a
+ FRP.Rhine.Gloss.Pure.Combined: flowGlossCombined :: GlossSettings -> GlossRhine a -> IO ()
+ FRP.Rhine.Gloss.Pure.Combined: glossEventClock :: GlossEventClock Event
+ FRP.Rhine.Gloss.Pure.Combined: glossEventSelectClock :: (Event -> Maybe a) -> GlossEventClock a
+ FRP.Rhine.Gloss.Pure.Combined: glossSchedule :: Schedule GlossM (GlossEventClock a) GlossSimulationClock
+ FRP.Rhine.Gloss.Pure.Combined: glossSimulationClock :: GlossSimulationClock
+ FRP.Rhine.Gloss.Pure.Combined: type GlossCombinedClock a = SequentialClock GlossM (GlossEventClock a) GlossSimulationClock
+ FRP.Rhine.Gloss.Pure.Combined: type GlossEventClock a = SelectClock GlossClock a
+ FRP.Rhine.Gloss.Pure.Combined: type GlossRhine a = Rhine GlossM (GlossCombinedClock a) () ()
+ FRP.Rhine.Gloss.Pure.Combined: type GlossSimulationClock = SelectClock GlossClock ()
Files
- Main.hs +25/−5
- README.md +4/−3
- rhine-gloss.cabal +11/−4
- src/FRP/Rhine/Gloss.hs +15/−81
- src/FRP/Rhine/Gloss/Common.hs +27/−0
- src/FRP/Rhine/Gloss/IO.hs +175/−0
- src/FRP/Rhine/Gloss/Internals.hs +0/−91
- src/FRP/Rhine/Gloss/Pure.hs +123/−0
- src/FRP/Rhine/Gloss/Pure/Combined.hs +130/−0
Main.hs view
@@ -1,10 +1,14 @@ {- | Example application for the @gloss@ wrapper. -} +{-# LANGUAGE TypeFamilies #-} +-- base+import qualified Control.Category as Category+import Data.Maybe (maybeToList)+ -- rhine-gloss import FRP.Rhine.Gloss - -- | Calculate a gear wheel rotated by a certain angle. gears :: Float -> Picture gears angle = color green $ pictures@@ -12,9 +16,25 @@ : map (rotate angle) [ rotate (45 * n) $ rectangleSolid 20 150 | n <- [0..3] ] -- | Rotate the gear with a constant angular velocity.-mainClSF :: GlossClSF a-mainClSF = timeInfoOf sinceInit >>> arr (* 50) >>> arr gears+-- Disregards all events.+sim :: Monad m => BehaviourF m Float [Event] Picture+sim = timeInfoOf sinceInit >>> arr (* 50) >>> arr gears main :: IO ()-main = flowGloss (InWindow "rhine-gloss-gears" (400, 400) (10, 10)) (greyN 0.3) 30- $ buildGlossRhine Just mainClSF+main = do+ putStrLn "Please choose between the pure (1), the pure combined (2), and the IO backend (3):"+ n <- readLn+ case n of+ 1 -> flowGloss defaultSettings pureClSF+ 2 -> flowGlossCombined defaultSettings pureRhine+ 3 -> flowGlossIO defaultSettings ioRhine++-- | Run the gears simulation with the pure backend synchronously.+pureClSF = currentEvent >>> arr maybeToList >>> sim++-- | Run the gears simulation with the pure backend with two subsystems,+-- one at the rate of events, one at the rate of simulation.+pureRhine = tagS @@ glossEventClock >-- collect -@- glossSchedule --> sim >-> arrMCl paintAll @@ glossSimulationClock++-- | Run the gears simulation with the 'IO' backend.+ioRhine = tagS @@ GlossEventClockIO >-- collect -@- glossConcurrently --> sim >-> arrMCl paintAllIO @@ GlossSimClockIO
README.md view
@@ -8,6 +8,8 @@ now becomes as simple as: ```haskell+module README where+ import FRP.Rhine.Gloss @@ -18,12 +20,11 @@ : map (rotate angle) [ rotate (45 * n) $ rectangleSolid 20 150 | n <- [0..3] ] -- | Rotate the gear with a constant angular velocity.-mainClSF :: GlossClSF a+mainClSF :: GlossClSF mainClSF = timeInfoOf sinceInit >>> arr (* 50) >>> arr gears main :: IO ()-main = flowGloss (InWindow "rhine-gloss-gears" (400, 400) (10, 10)) (greyN 0.3) 30- $ buildGlossRhine Just mainClSF+main = flowGloss defaultSettings mainClSF ``` ## Installation
rhine-gloss.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: rhine-gloss-version: 0.6.0.1+version: 0.7.0 synopsis: Gloss backend for Rhine description: This package provides a simple wrapper for the `gloss` library,@@ -30,14 +30,20 @@ library- exposed-modules: FRP.Rhine.Gloss- other-modules: FRP.Rhine.Gloss.Internals+ exposed-modules:+ FRP.Rhine.Gloss+ FRP.Rhine.Gloss.Common+ FRP.Rhine.Gloss.IO+ FRP.Rhine.Gloss.Pure+ FRP.Rhine.Gloss.Pure.Combined build-depends: base >= 4.9 && < 5- , rhine == 0.6.*+ , transformers >= 0.5+ , rhine == 0.7.0 , dunai >= 0.6 , gloss >= 1.12 hs-source-dirs: src default-language: Haskell2010+ ghc-options: -Wall executable rhine-gloss-gears main-is: Main.hs@@ -45,3 +51,4 @@ build-depends: base >= 4.9 && < 5 , rhine-gloss default-language: Haskell2010+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
src/FRP/Rhine/Gloss.hs view
@@ -1,89 +1,23 @@-{- | Wrapper library to write Gloss applications in Rhine.-@gloss@ acts as the backend here.+{- | Wrapper library to write @gloss@ Gloss applications in Rhine. -A Rhine app with the Gloss backend must use the 'GlossClock',+A pure Rhine app with @gloss@ backend must use the 'GlossClock' or 'GlossCombinedClock'+(from 'FRP.Rhine.Gloss.Pure.Single' and 'FRP.Rhine.Gloss.Pure.Combined', respectively), since the @gloss@ API only offers callbacks.-In order to run such a reactive program, you have to use 'flowGloss'.--}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}-module FRP.Rhine.Gloss- ( module FRP.Rhine.Gloss- , module X- )- where---- base-import Data.Functor.Identity (Identity, runIdentity)--import qualified Control.Arrow as X+In order to run such a reactive program, you have to use 'flowGloss' or 'flowGlossCombined'. --- gloss-import Graphics.Gloss.Interface.Pure.Game+A more flexible alternative, at the cost of introducing 'IO' concurrency,+is the 'FRP.Rhine.Gloss.IO' wrapper.+There, you can combine the @gloss@ clocks with arbitrary other 'IO' clocks.+-}+module FRP.Rhine.Gloss (module X) where -import qualified Graphics.Gloss as X+import Control.Arrow as X -- rhine-import FRP.Rhine hiding (trivialResamplingBuffer)-import FRP.Rhine.Reactimation.Tick--import qualified FRP.Rhine as X-import qualified FRP.Rhine.ClSF as X+import FRP.Rhine as X -- rhine-gloss-import FRP.Rhine.Gloss.Internals---- TODO Consider generalising to IO----- | The overall clock of a valid @rhine@ 'SN' that can be run by @gloss@.--- @a@ is the type of subevents that are selected.-type GlossClock a- = SequentialClock Identity- (SelectClock GlossEventClock a)- GlossSimulationClock_---- | The type of a valid 'Rhine' that can be run by @gloss@.--- @a@ is the type of subevents that are selected.-type GlossRhine a = Rhine Identity (GlossClock a) () Picture---- | The type of a 'ClSF' that you have to implement to get a @gloss@ app.-type GlossClSF a = ClSF Identity GlossSimulationClock [a] Picture--{- | 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- -> GlossClSF a -- ^ The 'ClSF' representing the game loop.- -> GlossRhine a-buildGlossRhine select clsfSim- = timeInfoOf tag @@ SelectClock { mainClock = GlossEventClock, .. }- >-- collect -@- glossSchedule- --> withProperSimClock clsfSim @@ GlossSimulationClock_---- | The main function that will start the @gloss@ backend and run the 'SN'.-flowGloss- :: Display -- ^ Display mode (e.g. 'InWindow' or 'FullScreen').- -> Color -- ^ Background color.- -> Int -- ^ Number of simulation steps per second of real time.- -> GlossRhine a -- ^ The @gloss@-compatible 'Rhine'.- -> IO ()-flowGloss display color n Rhine {..}- = play display color n world getPic handleEvent simStep- where- graphicsBuffer- :: ResamplingBuffer Identity- GlossSimulationClock_ GlossGraphicsClock- Picture Picture- graphicsBuffer = keepLast Blank- world = createTickable (trivialResamplingBuffer clock) sn graphicsBuffer ()- getPic Tickable { buffer2 } = fst $ runIdentity $ get buffer2 $ TimeInfo () () () ()- handleEvent event tickable = case select (sequentialCl1 clock) event of- Just a -> runIdentity $ tick tickable () $ Left a -- Event is relevant- Nothing -> tickable -- Event is irrelevant, state doesn't change- simStep diff tickable = runIdentity $ tick tickable () $ Right diff+import FRP.Rhine.Gloss.IO as X+import FRP.Rhine.Gloss.Common as X+import FRP.Rhine.Gloss.Pure as X+import FRP.Rhine.Gloss.Pure.Combined as X
+ src/FRP/Rhine/Gloss/Common.hs view
@@ -0,0 +1,27 @@+{- | Common definitions for all @gloss@ backends.+-}++module FRP.Rhine.Gloss.Common+ ( module FRP.Rhine.Gloss.Common+ , module X+ ) where++-- gloss+import qualified Graphics.Gloss as X+import qualified Graphics.Gloss.Interface.Pure.Game as X+import Graphics.Gloss.Interface.Pure.Game++-- | Collect all settings that the @gloss@ backend requires.+data GlossSettings = GlossSettings+ { display :: Display -- ^ Display mode (e.g. 'InWindow' or 'FullScreen').+ , backgroundColor :: Color -- ^ Background color.+ , stepsPerSecond :: Int -- ^ Number of simulation steps per second of real time.+ }++-- | Some standard settings, a 400 x 400 window with grey background, at 30 FPS.+defaultSettings :: GlossSettings+defaultSettings = GlossSettings+ { display = InWindow "rhine-gloss" (400, 400) (10, 10)+ , backgroundColor = greyN 0.3+ , stepsPerSecond = 30+ }
+ src/FRP/Rhine/Gloss/IO.hs view
@@ -0,0 +1,175 @@+{- | Wrapper to write @gloss@ applications in Rhine, using concurrency.+-}++{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+module FRP.Rhine.Gloss.IO+ ( GlossConcT+ , paintIO+ , clearIO+ , paintAllIO+ , GlossEventClockIO (..)+ , GlossSimClockIO (..)+ , launchGlossThread+ , flowGlossIO+ , glossConcurrently+ )+ where++-- base+import qualified Control.Category as Category+import Control.Concurrent+import Control.Concurrent.MVar+import Data.Functor (void)+import Data.IORef++-- transformers+import Control.Monad.Trans.Class+import Control.Monad.Trans.Reader++-- gloss+import Graphics.Gloss.Interface.IO.Game++-- rhine+import FRP.Rhine++-- rhine-gloss+import FRP.Rhine.Gloss.Common++-- * Gloss effects++type GlossEnv = (MVar Float, MVar Event, IORef Float, IORef Picture)++-- | Wraps the concurrent variables needed for communication with the @gloss@ backend.+newtype GlossConcT m a = GlossConcT+ { unGlossConcT :: ReaderT GlossEnv m a }+ deriving (Functor, Applicative, Monad, MonadTrans, MonadIO)++withPicRef+ :: MonadIO m+ => (IORef Picture -> IO a)+ -> GlossConcT m a+withPicRef action = GlossConcT $ do+ (_, _, _, picRef) <- ask+ liftIO $ action picRef++-- | Add a picture to the canvas.+paintIO :: MonadIO m => Picture -> GlossConcT m ()+paintIO pic = withPicRef $ \ref -> modifyIORef' ref (<> pic)++-- | Clear the canvas.+clearIO :: MonadIO m => GlossConcT m ()+clearIO = withPicRef $ \ref -> writeIORef ref Blank++-- | Clear the canvas and then paint.+paintAllIO :: MonadIO m => Picture -> GlossConcT m ()+paintAllIO pic = clearIO >> paintIO pic++-- * Gloss clocks in 'IO'++-- | Concurrently block on @gloss@ events.+data GlossEventClockIO = GlossEventClockIO++instance MonadIO m => Clock (GlossConcT m) GlossEventClockIO where+ type Time GlossEventClockIO = Float+ type Tag GlossEventClockIO = Event+ initClock _ = return (constM getEvent, 0)+ where+ getEvent = do+ (_, eventVar, timeRef, _) <- GlossConcT ask+ liftIO $ do+ event <- takeMVar eventVar+ time <- readIORef timeRef+ return (time, event)++instance GetClockProxy GlossEventClockIO++-- | Concurrently block on @gloss@ simulation ticks.+data GlossSimClockIO = GlossSimClockIO++instance MonadIO m => Clock (GlossConcT m) GlossSimClockIO where+ type Time GlossSimClockIO = Float+ type Tag GlossSimClockIO = ()+ initClock _ = return (constM getTime >>> sumS >>> withSideEffect writeTime &&& arr (const ()), 0)+ where+ getTime = do+ (timeVar, _, _, _) <- GlossConcT ask+ liftIO $ takeMVar timeVar+ writeTime time = do+ (_, _, timeRef, _) <- GlossConcT ask+ liftIO $ writeIORef timeRef time++instance GetClockProxy GlossSimClockIO++-- * Reactimation++-- | Apply this to supply the 'GlossConcT' effect.+-- Creates a new thread in which @gloss@ is run,+-- and feeds the clocks 'GlossEventClockIO' and 'GlossSimClockIO'.+--+-- Usually, this function is applied to the result of 'flow',+-- so you can handle all occurring effects as needed.+-- If you only use @gloss@ in your whole signal network,+-- you can use 'flowGlossIO' instead.+launchGlossThread+ :: MonadIO m+ => GlossSettings+ -> GlossConcT m a+ -> m a+launchGlossThread GlossSettings { .. } glossLoop = do+ vars <- liftIO $ ( , , , ) <$> newEmptyMVar <*> newEmptyMVar <*> newIORef 0 <*> newIORef Blank+ let+ getPic (_, _, _, picRef) = readIORef picRef+ -- Only try to put so this doesn't hang in case noone is listening for events or ticks+ handleEvent event vars@(_, eventVar, _, _) = do+ result <- tryPutMVar eventVar event+ return vars+ simStep diffTime vars@(timeVar, _, _, _) = do+ result <- tryPutMVar timeVar diffTime+ return vars+ void $ liftIO $ forkIO $ playIO display backgroundColor stepsPerSecond vars getPic handleEvent simStep+ runReaderT (unGlossConcT glossLoop) vars++-- | Run a 'Rhine' in the 'GlossConcT' monad by launching a separate thread for the @gloss@ backend,+-- and reactimate in the foreground.+flowGlossIO+ :: ( Clock (GlossConcT IO) cl+ , GetClockProxy cl+ , Time cl ~ Time (In cl)+ , Time cl ~ Time (Out cl)+ )+ => GlossSettings+ -> Rhine (GlossConcT IO) cl () ()+ -> IO ()+flowGlossIO settings = launchGlossThread settings . flow++-- | A schedule in the 'GlossConcT' transformer,+-- supplying the same backend connection to its scheduled clocks.+glossConcurrently+ :: ( Monad IO+ , Clock (GlossConcT IO) cl1, Clock (GlossConcT IO) cl2+ , Time cl1 ~ Time cl2+ )+ => Schedule (GlossConcT IO) cl1 cl2+glossConcurrently = Schedule+ $ \cl1 cl2 -> GlossConcT $ ReaderT+ $ \vars -> first liftTransS+ <$> initSchedule concurrently+ (runGlossEnvClock vars cl1)+ (runGlossEnvClock vars cl2)++type RunGlossEnvClock cl = HoistClock (GlossConcT IO) IO cl++runGlossEnvClock+ :: GlossEnv+ -> cl+ -> RunGlossEnvClock cl+runGlossEnvClock env unhoistedClock = HoistClock+ { monadMorphism = flip runReaderT env . unGlossConcT+ , ..+ }++-- FIXME And a schedule for gloss clocks and other clocks
− src/FRP/Rhine/Gloss/Internals.hs
@@ -1,91 +0,0 @@-{- | Internals for 'FRP.Rhine.Gloss'.-You probably won't need this module.--}-{-# LANGUAGE Arrows #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}--module FRP.Rhine.Gloss.Internals where---- base-import qualified Control.Category as Category-import Data.Functor.Identity (Identity)---- dunai-import Control.Monad.Trans.MSF.Reader (readerS, runReaderS)---- gloss-import Graphics.Gloss.Interface.Pure.Game---- rhine-import FRP.Rhine hiding (readerS, runReaderS)----- * Clocks---- | The error message that gets thrown when you try to start a pure @rhine-gloss@ app with 'flow'.-errMsg :: String-errMsg = unwords- [ "You cannot start pure rhine-gloss apps with FRP.Rhine.flow,"- , "since gloss has its own main loop."- , "Use FRP.Rhine.Gloss.flowGloss instead."- ]---- | The clock that ticks whenever a @gloss@ event occurs.-data GlossEventClock = GlossEventClock--instance Clock m GlossEventClock where- type Time GlossEventClock = ()- type Tag GlossEventClock = Event- initClock _ = error errMsg---- | The clock that ticks for every @gloss@ simulation step,--- but only shows the time /differences/ in the tag.--- Usually, you don't need this clock, but rather 'GlossSimulationClock'.-data GlossSimulationClock_ = GlossSimulationClock_--instance Clock m GlossSimulationClock_ where- type Time GlossSimulationClock_ = ()- type Tag GlossSimulationClock_ = Float- initClock _ = error errMsg---- | The clock that ticks for every @gloss@ simulation step.--- Use 'withProperSimClock' to transform to 'GlossSimulationClock_'.-data GlossSimulationClock = GlossSimulationClock--instance Clock m GlossSimulationClock where- type Time GlossSimulationClock = Float- type Tag GlossSimulationClock = ()- initClock _ = error errMsg---- | To use all features of the 'ClSF' framework,--- write your synchronous stream function on the 'GlossSimulationClock'--- and then use this function to transform it.-withProperSimClock- :: Monad m- => ClSF m GlossSimulationClock a b- -> ClSF m GlossSimulationClock_ a b-withProperSimClock clsf = readerS- $ (intermingle *** Category.id) >>> runReaderS clsf- where- intermingle :: Monad m => MSF m (TimeInfo GlossSimulationClock_) (TimeInfo GlossSimulationClock)- intermingle = proc TimeInfo {tag} -> do- let sinceLast = tag- absolute <- sumS -< sinceLast- let sinceInit = absolute- returnA -< TimeInfo { tag = (), .. }---- | The clock that ticks for every @gloss@ graphics output.-data GlossGraphicsClock = GlossGraphicsClock--instance Clock m GlossGraphicsClock where- type Time GlossGraphicsClock = ()- type Tag GlossGraphicsClock = ()- initClock _ = error errMsg---- | A schedule you can't actually use, for internal purposes.-glossSchedule :: Schedule Identity (SelectClock GlossEventClock a) GlossSimulationClock_-glossSchedule = error errMsg
+ src/FRP/Rhine/Gloss/Pure.hs view
@@ -0,0 +1,123 @@+{- | A pure @gloss@ backend for Rhine.++To run pure Rhine apps with @gloss@,+write a clocked signal function ('ClSF') in the 'GlossClock' and use 'flowGloss'.+-}++{-# LANGUAGE Arrows #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}++module FRP.Rhine.Gloss.Pure+ ( GlossM+ , paint+ , clear+ , paintAll+ , GlossClock (..)+ , GlossClSF+ , currentEvent+ , flowGloss+ , flowGlossWithWorldMSF+ ) where++-- base+import qualified Control.Category as Category++-- 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++-- * @gloss@ effects++-- FIXME How about a Reader (MSF () (Either Float Event))? That might unify the two backends and make the pure one more flexible.++-- | A pure monad in which all effects caused by the @gloss@ backend take place.+newtype GlossM a = GlossM { unGlossM :: (ReaderT (Float, Maybe Event)) (Writer Picture) a }+ deriving (Functor, Applicative, Monad)++-- | Add a picture to the canvas.+paint :: Picture -> GlossM ()+paint = GlossM . lift . tell++-- FIXME This doesn't what you think it does+-- | Clear the canvas.+clear :: GlossM ()+clear = paint Blank++-- | Clear the canvas and then paint.+paintAll :: Picture -> GlossM ()+paintAll pic = clear >> paint pic++-- * Clocks++-- | The overall clock of a pure @rhine@ 'ClSF' that can be run by @gloss@.+-- It ticks both on events (@tag = Just Event@) and simulation steps (@tag = Nothing@).+data GlossClock = GlossClock++instance Semigroup GlossClock where+ _ <> _ = GlossClock++instance Clock GlossM GlossClock where+ type Time GlossClock = Float+ type Tag GlossClock = Maybe Event+ initClock _ = return (constM (GlossM ask) >>> (sumS *** Category.id), 0)++instance GetClockProxy GlossClock++-- * Signal functions++{- |+The type of a 'ClSF' you can implement to get a @gloss@ app,+if you chose to handle events and simulation steps in the same subsystem.++You can, but don't need to paint via 'GlossM':+You can also simply output the picture and it will be painted on top.+-}+type GlossClSF = ClSF GlossM GlossClock () Picture++-- | Observe whether there was an event this tick,+-- and which one.+currentEvent :: ClSF GlossM GlossClock () (Maybe Event)+currentEvent = tagS++-- * Reactimation++-- | The main function that will start the @gloss@ backend and run the 'SN'+-- (in the case of the combined clock).+flowGloss+ :: GlossSettings+ -> GlossClSF -- ^ The @gloss@-compatible 'Rhine'.+ -> IO ()+flowGloss settings clsf = flowGlossWithWorldMSF settings GlossClock $ proc (time, tag) -> do+ arrM (const clear) -< ()+ pic <- eraseClockClSF getClockProxy 0 clsf -< (time, tag, ())+ arrM paint -< pic+++-- FIXME Hide?+-- | Helper function+flowGlossWithWorldMSF GlossSettings { .. } clock msf+ = play display backgroundColor stepsPerSecond (worldMSF, Blank) getPic handleEvent simStep+ where+ worldMSF = MSFReader.runReaderS $ morphS unGlossM $ proc () -> do+ (time, tag) <- fst $ fst $ runWriter $ flip runReaderT (0, Nothing) $ unGlossM $ initClock clock -< ()+ msf -< (time, tag)+ getPic (_, pic) = pic+ stepWith (diff, maybeEvent) (msf, _) = snd *** id $ runWriter $ unMSF msf ((diff, maybeEvent), ())+ handleEvent event = stepWith (0, Just event)+ simStep diff = stepWith (diff, Nothing)
+ src/FRP/Rhine/Gloss/Pure/Combined.hs view
@@ -0,0 +1,130 @@+{- | 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)