module Call.System (System, runSystem, MonadSystem(..), forkSystem) where
import Call.Component
import Call.Data.Bitmap
import Call.Picture
import Call.Types
import Control.Applicative
import Control.Concurrent
import Control.Exception
import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Object
import Control.Monad.Objective
import Data.IORef
import Data.Reflection
import Linear
import qualified Call.Internal.GLFW as G
import qualified Data.IntMap.Strict as IM
import qualified Graphics.UI.GLFW as GLFW
import qualified Call.Internal.PortAudio as PA
import Unsafe.Coerce
class (MonadIO m, MonadObjective m) => MonadSystem m where
linkMouse :: Mouse e => Address e m -> m ()
linkKeyboard :: Keyboard e => Address e m -> m ()
linkGraphic :: Graphic e => Address e m -> m ()
linkAudio :: Audio e => Address e m -> m ()
unlinkMouse :: Address e m -> m ()
unlinkKeyboard :: Address e m -> m ()
unlinkGraphic :: Address e m -> m ()
unlinkAudio :: Address e m -> m ()
stand :: m ()
wait :: Double -> m ()
newtype System s a = System (ReaderT (Foundation s) IO a) deriving (Functor, Applicative, Monad)
unSystem :: Foundation s -> System s a -> IO a
unSystem f m = unsafeCoerce m f
mkSystem :: (Foundation s -> IO a) -> System s a
mkSystem = unsafeCoerce
forkSystem :: System s () -> System s ThreadId
forkSystem m = mkSystem $ \fo -> forkIO (unSystem fo m)
runSystem :: WindowMode -> BoundingBox2 -> (forall s. System s a) -> IO (Maybe a)
runSystem mode box m = do
sys <- G.beginGLFW mode box
f <- Foundation
<$> newMVar 0
<*> pure 44100
<*> newIORef IM.empty
<*> newIORef IM.empty
<*> newIORef IM.empty
<*> newIORef IM.empty
<*> newMVar 0
<*> pure sys
<*> newIORef 60
<*> newIORef IM.empty
<*> newEmptyMVar
let win = G.theWindow sys
GLFW.setKeyCallback win $ Just $ keyCallback f
GLFW.setMouseButtonCallback win $ Just $ mouseButtonCallback f
GLFW.setCursorPosCallback win $ Just $ cursorPosCallback f
GLFW.setScrollCallback win $ Just $ scrollCallback f
ref <- newEmptyMVar
_ <- flip forkFinally (either throwIO (putMVar ref)) $ unSystem f m
PA.with 44100 512 (audioProcess f) $ liftIO $ do
GLFW.setTime 0
runGraphic f 0
G.endGLFW sys
tryTakeMVar ref
data Member c s = forall e. c e => Member (MVar (Object e (System s)))
data Foundation s = Foundation
{ newObjectId :: MVar Int
, sampleRate :: Double
, coreGraphic :: IORef (IM.IntMap (Member Graphic s))
, coreAudio :: IORef (IM.IntMap (Member Audio s))
, coreKeyboard :: IORef (IM.IntMap (Member Keyboard s))
, coreMouse :: IORef (IM.IntMap (Member Mouse s))
, theTime :: MVar Double
, theSystem :: G.System
, targetFPS :: IORef Double
, textures :: IORef (IM.IntMap G.Texture)
, theEnd :: MVar ()
}
instance MonadIO (System s) where
liftIO m = mkSystem $ const m
instance MonadObjective (System s) where
type Residence (System s) = System s
data Address e (System s) = ControlInt (MVar (Object e (System s)))
Control _ m .- e = mkSystem $ \fo -> push fo m e
new c = mkSystem $ \fo -> do
n <- takeMVar $ newObjectId fo
mc <- newMVar c
putMVar (newObjectId fo) (n + 1)
return (Control n mc)
instance MonadSystem (System s) where
linkGraphic (Control i mc) = mkSystem $ \fo -> modifyIORef (coreGraphic fo) $ IM.insert i (Member mc)
linkAudio (Control i mc) = mkSystem $ \fo -> modifyIORef (coreAudio fo) $ IM.insert i (Member mc)
linkKeyboard (Control i mc) = mkSystem $ \fo -> modifyIORef (coreKeyboard fo) $ IM.insert i (Member mc)
linkMouse (Control i mc) = mkSystem $ \fo -> modifyIORef (coreMouse fo) $ IM.insert i (Member mc)
unlinkGraphic (Control i _) = mkSystem $ \fo -> modifyIORef (coreGraphic fo) $ IM.delete i
unlinkAudio (Control i _) = mkSystem $ \fo -> modifyIORef (coreAudio fo) $ IM.delete i
unlinkMouse (Control i _) = mkSystem $ \fo -> modifyIORef (coreMouse fo) $ IM.delete i
unlinkKeyboard (Control i _) = mkSystem $ \fo -> modifyIORef (coreKeyboard fo) $ IM.delete i
wait dt = mkSystem $ \fo -> do
t0 <- takeMVar (theTime fo)
Just t <- GLFW.getTime
threadDelay $ floor $ (t0 t + dt) * 1000 * 1000
putMVar (theTime fo) $ t0 + dt
stand = mkSystem $ \fo -> takeMVar (theEnd fo)
runGraphic :: Foundation s -> Double -> IO ()
runGraphic fo t0 = do
fps <- readIORef (targetFPS fo)
let t1 = t0 + 1/fps
G.beginFrame (theSystem fo)
ms <- readIORef (coreGraphic fo)
pics <- forM (IM.elems ms) $ \(Member m) -> push fo m $ pullGraphic (1/fps)
give (TextureStorage (textures fo)) $ mapM_ runPicture pics
b <- G.endFrame (theSystem fo)
Just t' <- GLFW.getTime
threadDelay $ floor $ (t1 t') * 1000 * 1000
tryTakeMVar (theEnd fo) >>= \case
Just _ -> return ()
_ | b -> putMVar (theEnd fo) ()
| otherwise -> runGraphic fo t1
audioProcess :: Foundation s -> Int -> IO [V2 Float]
audioProcess fo n = do
let dt = fromIntegral n / sampleRate fo
ms <- readIORef (coreAudio fo)
ws <- forM (IM.elems ms) $ \(Member m) -> push fo m $ pullAudio dt n
return $ foldr (zipWith (+)) (replicate n zero) ws
push :: Foundation s -> MVar (Object e (System s)) -> e a -> IO a
push fo mc e = do
c0 <- takeMVar mc
(a, c) <- unSystem fo $ runObject c0 e
putMVar mc c
return a
keyCallback :: Foundation s -> GLFW.KeyCallback
keyCallback fo _ k _ st _ = do
ms <- readIORef (coreKeyboard fo)
forM_ (IM.elems ms) $ \(Member m) -> push fo m
$ keyEvent (toEnum . fromEnum $ k :: Key) (GLFW.KeyState'Released /= st)
mouseButtonCallback :: Foundation s -> GLFW.MouseButtonCallback
mouseButtonCallback fo _ btn st _ = do
ms <- readIORef (coreMouse fo)
forM_ (IM.elems ms) $ \(Member m) -> push fo m
$ mouseButtonEvent (fromEnum btn) (GLFW.MouseButtonState'Released /= st)
cursorPosCallback :: Foundation s -> GLFW.CursorPosCallback
cursorPosCallback fo _ x y = do
ms <- readIORef (coreMouse fo)
forM_ (IM.elems ms) $ \(Member m) -> push fo m $ cursorEvent (V2 x y)
scrollCallback :: Foundation s -> GLFW.ScrollCallback
scrollCallback fo _ x y = do
ms <- readIORef (coreMouse fo)
forM_ (IM.elems ms) $ \(Member m) -> push fo m $ scrollEvent (V2 x y)
newtype TextureStorage = TextureStorage { getTextureStorage :: IORef (IM.IntMap G.Texture) }
instance Affine IO where
translate = G.translate
rotateD = G.rotateD
rotateR t = let t' = t / pi * 180 in G.rotateD t'
scale = G.scale
instance (Given TextureStorage) => Picture2D IO where
bitmap (Bitmap bmp h) = do
m <- readIORef (getTextureStorage given)
case IM.lookup h m of
Just t -> G.drawTexture t
Nothing -> do
t <- G.installTexture bmp
writeIORef (getTextureStorage given) $ IM.insert h t m
G.drawTexture t
bitmapOnce (Bitmap bmp _) = do
t <- G.installTexture bmp
G.drawTexture t
G.releaseTexture t
circle = G.circle
circleOutline = G.circleOutline
polygon = G.polygon
polygonOutline = G.polygonOutline
line = G.line
thickness = G.thickness
color = G.color
blendMode = G.blendMode