immutaball-core-0.1.0.4.1: Immutaball/Share/GLManager.hs
{-# OPTIONS_GHC -fno-warn-tabs #-} -- Support tab indentation better, for a better default of no warning if tabs are used: https://dmitryfrank.com/articles/indent_with_tabs_align_with_spaces .
-- Enable warnings:
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}
-- State.hs.
{-# LANGUAGE Haskell2010 #-}
-- | A thread that can order GL commands in sequence.
--
-- When GL commands need to be in order to be correct, this thread can be
-- useful for this end.
--
-- Having a GL thread issuing OpenGL separate from the SDL manager thread
-- seemed to have issues on my platform, so I switched to having the SDL
-- manager thread execute GL commands. I might remove this module in the
-- future.
module Immutaball.Share.GLManager
(
-- * High level
withGLManager,
GLManagerHandle(..), glmh_done, glmh_doneReceived, glmh_commands,
GLManagerCommand(..),
issueGLCommand,
glQueueValueless,
-- * Low level
initGLManager,
glForkIBIO,
quitGLManager,
glManagerThread
) where
import Prelude ()
import Immutaball.Prelude
import Control.Concurrent.STM.TVar
import Control.Concurrent.STM.TChan
import Control.Lens
import Control.Monad.STM
import Data.Functor
import Immutaball.Share.ImmutaballIO
import Immutaball.Share.ImmutaballIO.BasicIO
import Immutaball.Share.ImmutaballIO.GLIO
import Immutaball.Share.GLManager.Types
-- * High level
withGLManager :: (GLManagerHandle -> ImmutaballIO) -> ImmutaballIO
withGLManager withHandle =
initGLManager $ \glManagerHandle ->
withHandle glManagerHandle <>>
quitGLManager glManagerHandle
-- Moved to .Types to fix Template Haskell erors:
-- GLManagerHandle
-- GLManagerCommand
issueGLCommand :: GLManagerHandle -> GLManagerCommand -> me -> ImmutaballIOF me
issueGLCommand glMgr cmd withUnit = Atomically (writeTChan (glMgr^.glmh_commands) cmd) (\() -> withUnit)
glQueueValueless :: GLManagerHandle -> [GLIOF ()] -> me -> ImmutaballIOF me
glQueueValueless glMgr orderedGLCommands withUnit =
issueGLCommand glMgr (GLQueueValueless orderedGLCommands) withUnit
-- * Low level
-- | Manually start the lifetime of the GLManager OS thread; the caller will
-- need to manage the lifetime.
initGLManager :: (GLManagerHandle -> ImmutaballIO) -> ImmutaballIO
initGLManager withGLMgr =
mkAtomically (newTVar False) $ \done ->
mkAtomically (newTVar False) $ \doneReceived ->
mkAtomically newTChan $ \commands ->
let glMgr = GLManagerHandle {
_glmh_done = done,
_glmh_doneReceived = doneReceived,
_glmh_commands = commands
}
in glForkIBIO (glManagerThread glMgr) $ withGLMgr glMgr
-- | glForkIBIO: The return value is better associated with the callback rather than the thread, so flip.
glForkIBIO :: ImmutaballIO -> ImmutaballIO -> ImmutaballIO
glForkIBIO = flip mkAndIBIO
-- | Manually close the GLManager thread low-level. High-level
-- 'withGLManager' automatically manages the lifetime.
quitGLManager :: GLManagerHandle -> ImmutaballIO
quitGLManager glMgr =
mkAtomically (do
writeTVar (glMgr^.glmh_done) True) (const mempty) <>
mkAtomically (do
writeTChan (glMgr^.glmh_commands) QuitGLManager) (const mempty) <>>
mkAtomically (do
readTVar (glMgr^.glmh_doneReceived) >>= check) (const mempty)
glManagerThread :: GLManagerHandle -> ImmutaballIO
glManagerThread glMgr =
mkAtomically (
readTVar (glMgr^.glmh_done) >>= \done ->
(check done $> Left done) `orElse` (Right <$> readTChan (glMgr^.glmh_commands))
) $ \doneOrCmd ->
case doneOrCmd of
Left done -> if not done then glManagerThread glMgr else quit
Right cmd -> case cmd of
QuitGLManager -> quit
NopGLManager -> glManagerThread glMgr
GLQueueValueless orderedGLCommands -> runOrderedGLValueless orderedGLCommands (glManagerThread glMgr)
where
quit :: ImmutaballIO
quit = mkAtomically (writeTVar (glMgr^.glmh_doneReceived) True) mempty <>> mkAtomically (writeTVar (glMgr^.glmh_done) True) mempty <>> mempty
runOrderedGLValueless :: [GLIOF ()] -> ImmutaballIO -> ImmutaballIO
runOrderedGLValueless orderedGLCommands withUnit =
case orderedGLCommands of
[] -> withUnit
(orderedGLCommand:then_) -> mkBIO . GLIO $ runOrderedGLValueless then_ withUnit <$ orderedGLCommand