{-# LANGUAGE TupleSections #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE Arrows #-} module LiveCoding.Gloss ( module X , module LiveCoding.Gloss ) where -- base import Control.Concurrent import Control.Monad (when) import Data.IORef import System.Exit (exitSuccess) -- transformers import Control.Arrow (returnA) import Control.Monad.Trans.Writer import Control.Monad.Trans.State.Strict (StateT) -- gloss import Graphics.Gloss as X import Graphics.Gloss.Interface.IO.Game as X -- essence-of-live-coding import LiveCoding -- essence-of-live-coding-gloss import LiveCoding.Gloss.Debugger as X import LiveCoding.Gloss.PictureM as X -- | In a 'Handle', store a separate thread where the gloss main loop is executed, -- and several concurrent variables to communicate with it. data GlossHandle = GlossHandle { glossThread :: ThreadId , glossVars :: GlossVars } -- | The concurrent variables needed to communicate with the gloss thread. data GlossVars = GlossVars { glossEventsRef :: IORef [Event] -- ^ Stores all 'Event's that arrived since the last tick , glossPicRef :: IORef Picture -- ^ Stores the next 'Picture' to be painted , glossDTimeVar :: MVar Float -- ^ Stores the time passed since the last tick , glossExitRef :: IORef Bool -- ^ Write 'True' here to stop the gloss thread } -- | Collect all settings that the @gloss@ backend requires. -- Taken from @rhine-gloss@. data GlossSettings = GlossSettings { displaySetting :: Display -- ^ Display mode (e.g. 'InWindow' or 'FullScreen'). , backgroundColor :: Color -- ^ Background color. , stepsPerSecond :: Int -- ^ Number of simulation steps per second of real time. , debugEvents :: Bool -- ^ Print all incoming events to the console. } defaultSettings :: GlossSettings defaultSettings = GlossSettings { displaySetting = InWindow "Essence of live coding" (600, 800) (20, 20) , backgroundColor = black , stepsPerSecond = 30 , debugEvents = False } -- | Will create a handle for communication with the gloss thread, -- and start gloss. glossHandle :: GlossSettings -> Handle IO GlossHandle glossHandle GlossSettings { .. } = Handle { create = do glossEventsRef <- newIORef [] glossDTimeVar <- newEmptyMVar glossPicRef <- newIORef blank glossExitRef <- newIORef False let glossVars = GlossVars { .. } glossThread <- forkIO $ playIO displaySetting backgroundColor stepsPerSecond glossVars getPicture (handleEvent debugEvents) stepGloss return GlossHandle { .. } , destroy = \GlossHandle { glossVars = GlossVars { .. }, .. } -> writeIORef glossExitRef True } getPicture :: GlossVars -> IO Picture getPicture GlossVars { .. } = readIORef glossPicRef handleEvent :: Bool -> Event -> GlossVars -> IO GlossVars handleEvent debugEvents event vars@GlossVars { .. } = do when debugEvents $ print event modifyIORef glossEventsRef (event :) return vars stepGloss :: Float -> GlossVars -> IO GlossVars stepGloss dTime vars@GlossVars { .. } = do putMVar glossDTimeVar dTime exitNow <- readIORef glossExitRef when exitNow exitSuccess return vars {- | Given a cell in the gloss monad 'PictureM', start the gloss backend and connect the cell to it. This introduces 'Handle's containing the gloss background thread, which need to be taken care of by calling 'runHandlingState' or a similar function. The resulting cell never blocks, but returns 'Nothing' if there currently is no gloss tick. -} glossWrapC :: GlossSettings -> Cell PictureM a b -> Cell (HandlingStateT IO) a (Maybe b) glossWrapC glossSettings cell = proc a -> do GlossHandle { .. } <- handling $ glossHandle glossSettings -< () liftCell pump -< (glossVars, a) where pump = proc (GlossVars { .. }, a) -> do timeMaybe <- arrM tryTakeMVar -< glossDTimeVar case timeMaybe of Just _ -> do events <- arrM $ flip atomicModifyIORef ([], ) -< glossEventsRef (picture, b) <- runPictureT cell -< (events, a) arrM (uncurry writeIORef) -< (glossPicRef, picture) returnA -< Just b Nothing -> do arrM threadDelay -< 1000 -- Prevent too much CPU load returnA -< Nothing