diff options
author | 3gg <3gg@shellblade.net> | 2025-01-01 11:39:25 -0800 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2025-01-01 11:39:25 -0800 |
commit | acc954c9ac3a18e2d48e52839a7dc751597dfb15 (patch) | |
tree | e002438e1085cbda09a36ef81c4d661e0102a0d1 | |
parent | 8984aede0162f6bdcfc2dc0a54f563a3b1ff5684 (diff) |
Streamling the Game monad, use MonadIO for automatic lifting.
-rw-r--r-- | Demos/Pong/Main.hs | 62 | ||||
-rw-r--r-- | Spear/App.hs | 135 | ||||
-rw-r--r-- | Spear/Game.hs | 84 | ||||
-rw-r--r-- | Spear/Render/Core/Buffer.hs | 5 | ||||
-rw-r--r-- | Spear/Render/Core/Geometry.hs | 9 | ||||
-rw-r--r-- | Spear/Render/Core/Pipeline.hs | 24 | ||||
-rw-r--r-- | Spear/Render/Core/Shader.hs | 15 | ||||
-rw-r--r-- | Spear/Render/Immediate.hs | 17 | ||||
-rw-r--r-- | Spear/Scene/Loader.hs | 2 | ||||
-rw-r--r-- | Spear/Sound/Sound.hs | 13 | ||||
-rw-r--r-- | Spear/Sys/Timer.hsc | 21 | ||||
-rw-r--r-- | Spear/Window.hs | 36 |
12 files changed, 246 insertions, 177 deletions
diff --git a/Demos/Pong/Main.hs b/Demos/Pong/Main.hs index f77136f..eafa983 100644 --- a/Demos/Pong/Main.hs +++ b/Demos/Pong/Main.hs | |||
@@ -1,3 +1,5 @@ | |||
1 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
2 | |||
1 | module Main where | 3 | module Main where |
2 | 4 | ||
3 | import Pong | 5 | import Pong |
@@ -21,15 +23,16 @@ import Control.Monad (when) | |||
21 | import Data.Maybe (mapMaybe) | 23 | import Data.Maybe (mapMaybe) |
22 | 24 | ||
23 | 25 | ||
24 | data GameState = GameState | 26 | data Pong = Pong |
25 | { context :: AppContext | 27 | { immRenderState :: ImmRenderState |
26 | , renderCoreState :: RenderCoreState | ||
27 | , immRenderState :: ImmRenderState | ||
28 | , viewProjection :: Matrix4 | 28 | , viewProjection :: Matrix4 |
29 | , backgroundMusic :: SoundSource | 29 | , backgroundMusic :: SoundSource |
30 | , world :: [GameObject] | 30 | , world :: [GameObject] |
31 | } | 31 | } |
32 | 32 | ||
33 | type GameState = AppState Pong | ||
34 | |||
35 | |||
33 | options = defaultAppOptions { title = "Pong" } | 36 | options = defaultAppOptions { title = "Pong" } |
34 | 37 | ||
35 | app = App options initGame endGame step render resize | 38 | app = App options initGame endGame step render resize |
@@ -38,32 +41,38 @@ app = App options initGame endGame step render resize | |||
38 | main :: IO () | 41 | main :: IO () |
39 | main = runApp app | 42 | main = runApp app |
40 | 43 | ||
41 | initGame :: AppContext -> Game () GameState | 44 | initGame :: Game AppContext Pong |
42 | initGame context = do | 45 | initGame = do |
43 | (immRenderState, renderCoreState) <- runSiblingGame newImmRenderer newRenderCoreState | 46 | renderCoreState <- contextRenderCoreState <$> get |
44 | (music, soundState') <- flip runSiblingGame (appSoundState context) $ do | 47 | (immRenderState, renderCoreState') <- runSiblingGame renderCoreState newImmRenderer |
48 | -- TODO: This can work if we use FlexibleContexts and change the function signatures. | ||
49 | --immRenderState <- newImmRenderer | ||
50 | music <- siblingGame $ do | ||
45 | musicBuffer <- loadAudioFile "/home/jeanne/Casual Tiki Party Main.wav" | 51 | musicBuffer <- loadAudioFile "/home/jeanne/Casual Tiki Party Main.wav" |
46 | music <- makeSoundSource | 52 | music <- makeSoundSource |
47 | liftIO $ do | 53 | -- TODO: setSoundSourceBuffer generates an AL error for some reason, though |
48 | setSoundSourceBuffer music musicBuffer | 54 | -- the music still plays. |
49 | setSoundLoopMode music Loop | 55 | -- "user error (runALUT: There was already an AL error on entry to an ALUT function)" |
50 | playSounds [music] | 56 | setSoundSourceBuffer music musicBuffer |
57 | setSoundLoopMode music Loop | ||
58 | playSounds [music] | ||
51 | return music | 59 | return music |
52 | let context' = context { appSoundState = soundState' } | 60 | return $ Pong immRenderState Matrix4.id music newWorld |
53 | return $ GameState context' renderCoreState immRenderState Matrix4.id music newWorld | ||
54 | 61 | ||
55 | endGame :: Game GameState () | 62 | endGame :: Game GameState () |
56 | endGame = do | 63 | endGame = do |
57 | game <- get | 64 | renderCoreState <- appRenderCoreState <$> get |
58 | runSubGame' (deleteImmRenderer $ immRenderState game) (renderCoreState game) | 65 | game <- getGameState |
66 | exec' runSiblingGame renderCoreState (deleteImmRenderer $ immRenderState game) | ||
59 | 67 | ||
60 | 68 | ||
61 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool | 69 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool |
62 | step elapsed dt inputEvents = do | 70 | step elapsed dt inputEvents = do |
63 | gameState <- get | 71 | appState <- get |
64 | events <- processInput (appWindow . context $ gameState) | 72 | gameState <- getGameState |
73 | events <- processInput (appWindow appState) | ||
65 | --when (events /= []) $ liftIO . putStrLn $ show events | 74 | --when (events /= []) $ liftIO . putStrLn $ show events |
66 | modify $ \gameState -> gameState | 75 | modifyGameState $ \pong -> pong |
67 | { world = stepWorld (realToFrac elapsed) (realToFrac dt) events (world gameState) | 76 | { world = stepWorld (realToFrac elapsed) (realToFrac dt) events (world gameState) |
68 | } | 77 | } |
69 | return (not $ exitRequested inputEvents) | 78 | return (not $ exitRequested inputEvents) |
@@ -79,18 +88,17 @@ exitRequested = elem (KeyDown KEY_ESC) | |||
79 | 88 | ||
80 | render :: Game GameState () | 89 | render :: Game GameState () |
81 | render = do | 90 | render = do |
82 | gameState <- get | 91 | gameState <- getGameState |
83 | immRenderState' <- flip execSubGame (immRenderState gameState) $ do | 92 | immRenderState' <- exec runSiblingGame (immRenderState gameState) $ do |
84 | immStart | 93 | immStart |
85 | immSetViewProjectionMatrix (viewProjection gameState) | 94 | immSetViewProjectionMatrix (viewProjection gameState) |
86 | -- Clear the background to a different colour than the playable area to make | 95 | -- Clear the background to a different colour than the playable area to make |
87 | -- the latter distinguishable. | 96 | -- the latter distinguishable. |
88 | liftIO $ do | 97 | setClearColour (0.2, 0.2, 0.2, 0.0) |
89 | setClearColour (0.2, 0.2, 0.2, 0.0) | 98 | clearBuffers [ColourBuffer] |
90 | clearBuffers [ColourBuffer] | ||
91 | render' $ world gameState | 99 | render' $ world gameState |
92 | immEnd | 100 | immEnd |
93 | put $ gameState { immRenderState = immRenderState' } | 101 | putGameState $ gameState { immRenderState = immRenderState' } |
94 | 102 | ||
95 | render' :: [GameObject] -> Game ImmRenderState () | 103 | render' :: [GameObject] -> Game ImmRenderState () |
96 | render' world = do | 104 | render' world = do |
@@ -132,7 +140,7 @@ resize (ResizeEvent w h) = | |||
132 | bottom = if r > 1 then 0 else -pad | 140 | bottom = if r > 1 then 0 else -pad |
133 | top = if r > 1 then 1 else 1 + pad | 141 | top = if r > 1 then 1 else 1 + pad |
134 | in do | 142 | in do |
135 | liftIO $ setViewport 0 0 w h | 143 | setViewport 0 0 w h |
136 | modify $ \state -> state { | 144 | modifyGameState $ \pong -> pong { |
137 | viewProjection = Matrix4.ortho left right bottom top (-1) 1 | 145 | viewProjection = Matrix4.ortho left right bottom top (-1) 1 |
138 | } | 146 | } |
diff --git a/Spear/App.hs b/Spear/App.hs index 8c0371e..75bf6fa 100644 --- a/Spear/App.hs +++ b/Spear/App.hs | |||
@@ -1,26 +1,34 @@ | |||
1 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
2 | |||
1 | module Spear.App | 3 | module Spear.App |
2 | ( App(..) | 4 | ( App(..) |
3 | , AppOptions(..) | 5 | , AppOptions(..) |
4 | , AppContext(..) | 6 | , AppContext(..) |
7 | , AppState(..) | ||
5 | , Elapsed | 8 | , Elapsed |
6 | , Dt | 9 | , Dt |
7 | , Step | 10 | , Step |
8 | , defaultAppOptions | 11 | , defaultAppOptions |
12 | , getGameState | ||
13 | , putGameState | ||
14 | , modifyGameState | ||
9 | , runApp | 15 | , runApp |
10 | , loop | 16 | , loop |
11 | ) | 17 | ) |
12 | where | 18 | where |
13 | 19 | ||
14 | import Spear.Game | 20 | import Spear.Game |
21 | import Spear.Render.Core.State | ||
15 | import Spear.Sound.Sound | 22 | import Spear.Sound.Sound |
16 | import Spear.Sound.State | 23 | import Spear.Sound.State |
17 | import Spear.Sys.Timer as Timer | 24 | import Spear.Sys.Timer as Timer |
18 | import Spear.Window | 25 | import Spear.Window |
19 | 26 | ||
20 | import Control.Monad | 27 | import Control.Monad |
21 | import Data.Fixed (mod') | 28 | import Data.Fixed (mod') |
22 | import GHC.Float | 29 | import GHC.Float |
23 | 30 | ||
31 | |||
24 | -- | Time elapsed. | 32 | -- | Time elapsed. |
25 | type Elapsed = Double | 33 | type Elapsed = Double |
26 | 34 | ||
@@ -31,6 +39,8 @@ type Dt = Double | |||
31 | type Step s = Elapsed -> Dt -> [InputEvent] -> Game s Bool | 39 | type Step s = Elapsed -> Dt -> [InputEvent] -> Game s Bool |
32 | 40 | ||
33 | -- | Application options. | 41 | -- | Application options. |
42 | -- | ||
43 | -- Use `defaultOptions` for default options. | ||
34 | data AppOptions = AppOptions | 44 | data AppOptions = AppOptions |
35 | { title :: String | 45 | { title :: String |
36 | , windowWidth :: Int | 46 | , windowWidth :: Int |
@@ -40,6 +50,7 @@ data AppOptions = AppOptions | |||
40 | , enableProfiling :: Bool | 50 | , enableProfiling :: Bool |
41 | } | 51 | } |
42 | 52 | ||
53 | -- | Default application options. | ||
43 | defaultAppOptions = AppOptions | 54 | defaultAppOptions = AppOptions |
44 | { title = "Spear Application" | 55 | { title = "Spear Application" |
45 | , windowWidth = 1920 | 56 | , windowWidth = 1920 |
@@ -49,55 +60,105 @@ defaultAppOptions = AppOptions | |||
49 | , enableProfiling = False | 60 | , enableProfiling = False |
50 | } | 61 | } |
51 | 62 | ||
52 | -- | Application state. | 63 | -- | Application descriptor. |
53 | data App s = App | 64 | data App s = App |
54 | { appOptions :: AppOptions | 65 | { appOptions :: AppOptions |
55 | , initApp :: AppContext -> Game () s | 66 | , initApp :: Game AppContext s |
56 | , endApp :: Game s () | 67 | , endApp :: Game (AppState s) () |
57 | , stepApp :: Step s | 68 | , stepApp :: Step (AppState s) |
58 | , renderApp :: Game s () | 69 | , renderApp :: Game (AppState s) () |
59 | , resizeApp :: WindowEvent -> Game s () | 70 | , resizeApp :: WindowEvent -> Game (AppState s) () |
60 | } | 71 | } |
61 | 72 | ||
62 | -- | Application context. | 73 | -- | Application context. |
74 | -- | ||
75 | -- The application context is the initial state from which the application's | ||
76 | -- `AppState` is bootstrapped with `initApp`. | ||
63 | data AppContext = AppContext | 77 | data AppContext = AppContext |
64 | { appWindow :: Window | 78 | { contextWindow :: Window |
65 | , appSoundState :: SoundState | 79 | , contextRenderCoreState :: RenderCoreState |
80 | , contextSoundState :: SoundState | ||
66 | } | 81 | } |
67 | 82 | ||
83 | instance HasState AppContext RenderCoreState where | ||
84 | getInnerState = contextRenderCoreState | ||
85 | setInnerState context renderCoreState = context { contextRenderCoreState = renderCoreState } | ||
86 | |||
87 | instance HasState AppContext SoundState where | ||
88 | getInnerState = contextSoundState | ||
89 | setInnerState context soundState = context { contextSoundState = soundState } | ||
90 | |||
91 | -- | Application state. | ||
92 | data AppState s = AppState | ||
93 | { appWindow :: Window | ||
94 | , appRenderCoreState :: RenderCoreState | ||
95 | , appSoundState :: SoundState | ||
96 | , customState :: s | ||
97 | } | ||
98 | |||
99 | instance HasState (AppState s) RenderCoreState where | ||
100 | getInnerState = appRenderCoreState | ||
101 | setInnerState appState renderCoreState = appState { appRenderCoreState = renderCoreState } | ||
102 | |||
103 | instance HasState (AppState s) SoundState where | ||
104 | getInnerState = appSoundState | ||
105 | setInnerState appState soundState = appState { appSoundState = soundState } | ||
106 | |||
107 | |||
108 | -- | Get the custom state in the app state. | ||
109 | getGameState :: Game (AppState s) s | ||
110 | getGameState = customState <$> get | ||
111 | |||
112 | -- | Put the custom state in the app state. | ||
113 | putGameState :: s -> Game (AppState s) () | ||
114 | putGameState custom = do | ||
115 | appState <- get | ||
116 | put $ appState { customState = custom } | ||
117 | |||
118 | -- | Modify the custom state in the app state. | ||
119 | modifyGameState :: (s -> s) -> Game (AppState s) () | ||
120 | modifyGameState f = modify $ \appState -> appState { customState = f (customState appState )} | ||
121 | |||
68 | -- | Run the application. | 122 | -- | Run the application. |
69 | runApp :: App s -> IO () | 123 | runApp :: App s -> IO () |
70 | runApp app = | 124 | runApp app = |
71 | let ops = appOptions app | 125 | let ops = appOptions app |
72 | w = windowWidth ops | 126 | w = windowWidth ops |
73 | h = windowHeight ops | 127 | h = windowHeight ops |
74 | in withWindow (w, h) (title ops) $ \window -> | 128 | in -- Initialize subsystems. |
75 | withSoundContext $ flip evalGame () $ do | 129 | withWindow (w, h) (title ops) $ \window -> |
76 | soundState <- evalSiblingGame initSoundSystem () | 130 | withSoundContext $ eval runGame () $ do |
77 | let appContext = AppContext window soundState | 131 | -- Create initial context. |
78 | gameState <- initApp app appContext | 132 | initialSoundState <- eval runSiblingGame () initSoundSystem |
79 | (result, endGameState) <- runSubGame (loop app window) gameState | 133 | let context = AppContext window newRenderCoreState initialSoundState |
80 | runSubGame' (endApp app) endGameState | 134 | -- Create initial app state. |
81 | runSiblingGame' destroySoundSystem soundState | 135 | (gameState, context') <- runSiblingGame context (initApp app) |
82 | 136 | let appState = AppState { | |
83 | -- | Convert FPS to desired delta time. | 137 | appWindow = contextWindow context' |
84 | fpsToDdt :: Int -> TimeDelta | 138 | , appRenderCoreState = contextRenderCoreState context' |
85 | fpsToDdt fps = if fps > 0 then secToTimeDelta (1.0 / fromIntegral fps) else 0 | 139 | , appSoundState = contextSoundState context' |
140 | , customState = gameState | ||
141 | } | ||
142 | -- Run app. | ||
143 | (result, endGameState) <- runSiblingGame appState (loop app window) | ||
144 | -- Shut down. | ||
145 | exec' runSiblingGame endGameState (endApp app) | ||
146 | exec' runSiblingGame (appSoundState appState) destroySoundSystem | ||
86 | 147 | ||
87 | -- | Enter the main application loop. | 148 | -- | Enter the main application loop. |
88 | loop :: App s -> Window -> Game s () | 149 | loop :: App s -> Window -> Game (AppState s) () |
89 | loop app window = do | 150 | loop app window = do |
90 | -- For convenience, trigger an initial resize followed by a render of the | 151 | -- For convenience, trigger an initial resize followed by a render of the |
91 | -- application's initial state. | 152 | -- application's initial state. |
92 | (width, height) <- liftIO $ getWindowSize window | 153 | (width, height) <- getWindowSize window |
93 | resizeApp app (ResizeEvent width height) | 154 | resizeApp app (ResizeEvent width height) |
94 | renderApp app | 155 | renderApp app |
95 | 156 | ||
96 | let ddt = fpsToDdt . maxFPS . appOptions $ app -- Desired render time step. | 157 | let ddt = fpsToDdt . maxFPS . appOptions $ app -- Desired render time step. |
97 | let animationDdt = fpsToDdt . animationFPS . appOptions $ app -- Desired animation time step. | 158 | let animationDdt = fpsToDdt . animationFPS . appOptions $ app -- Desired animation time step. |
98 | 159 | ||
99 | timer <- liftIO newTimer | 160 | timer <- newTimer |
100 | liftIO $ Timer.start timer | 161 | Timer.start timer |
101 | let lastAnimationTime = lastTick timer | 162 | let lastAnimationTime = lastTick timer |
102 | loop' window ddt animationDdt lastAnimationTime timer app | 163 | loop' window ddt animationDdt lastAnimationTime timer app |
103 | 164 | ||
@@ -108,18 +169,18 @@ loop' :: | |||
108 | TimePoint -> -- Time point of last animation update. | 169 | TimePoint -> -- Time point of last animation update. |
109 | Timer -> | 170 | Timer -> |
110 | App s -> | 171 | App s -> |
111 | Game s () | 172 | Game (AppState s) () |
112 | loop' window renderDdt animationDdt lastAnimationTime inputTimer app = do | 173 | loop' window renderDdt animationDdt lastAnimationTime inputTimer app = do |
113 | timer <- liftIO $ tick inputTimer | 174 | timer <- tick inputTimer |
114 | windowEvents <- liftIO $ pollWindowEvents window | 175 | windowEvents <- pollWindowEvents window |
115 | close <- liftIO $ shouldWindowClose window | 176 | close <- shouldWindowClose window |
116 | 177 | ||
117 | (continue, lastAnimationTimeNextFrame) <- case animationDdt of | 178 | (continue, lastAnimationTimeNextFrame) <- case animationDdt of |
118 | 0 -> do | 179 | 0 -> do |
119 | -- Variable time step game animation. | 180 | -- Variable time step game animation. |
120 | let t = timeDeltaToSec $ runningTime timer | 181 | let t = timeDeltaToSec $ runningTime timer |
121 | let dt = timeDeltaToSec $ deltaTime timer | 182 | let dt = timeDeltaToSec $ deltaTime timer |
122 | inputEvents <- liftIO $ pollInputEvents window | 183 | inputEvents <- pollInputEvents window |
123 | continue <- stepApp app t dt inputEvents | 184 | continue <- stepApp app t dt inputEvents |
124 | return (continue, lastAnimationTime) | 185 | return (continue, lastAnimationTime) |
125 | 186 | ||
@@ -139,7 +200,7 @@ loop' window renderDdt animationDdt lastAnimationTime inputTimer app = do | |||
139 | let lastAnimationTimeNextFrame = timeAdd lastAnimationTime (steps * ddt) | 200 | let lastAnimationTimeNextFrame = timeAdd lastAnimationTime (steps * ddt) |
140 | --liftIO . print $ "Steps: " ++ show steps ++ ", Budget: " ++ show timeBudgetThisFrame ++ ", ddt: " ++ show ddt | 201 | --liftIO . print $ "Steps: " ++ show steps ++ ", Budget: " ++ show timeBudgetThisFrame ++ ", ddt: " ++ show ddt |
141 | continue <- and <$> forM [1..steps] (\i -> do | 202 | continue <- and <$> forM [1..steps] (\i -> do |
142 | inputEvents <- liftIO $ pollInputEvents window | 203 | inputEvents <- pollInputEvents window |
143 | let t = timeDeltaToSec $ elapsed + i * ddt | 204 | let t = timeDeltaToSec $ elapsed + i * ddt |
144 | stepApp app t dt inputEvents) | 205 | stepApp app t dt inputEvents) |
145 | return (continue, lastAnimationTimeNextFrame) | 206 | return (continue, lastAnimationTimeNextFrame) |
@@ -151,16 +212,20 @@ loop' window renderDdt animationDdt lastAnimationTime inputTimer app = do | |||
151 | -- For smoother resizing, render only while not resizing. | 212 | -- For smoother resizing, render only while not resizing. |
152 | unless resized $ do | 213 | unless resized $ do |
153 | renderApp app | 214 | renderApp app |
154 | liftIO $ swapBuffers window | 215 | swapBuffers window |
155 | 216 | ||
156 | -- Limit frame rate if so requested by the application. | 217 | -- Limit frame rate if so requested by the application. |
157 | -- This currently makes the rendering stutter and is not very desirable. | 218 | -- This currently makes the rendering stutter and is not very desirable. |
158 | when ((maxFPS . appOptions $ app) > 0) $ do | 219 | when ((maxFPS . appOptions $ app) > 0) $ do |
159 | frameEnd <- liftIO now | 220 | frameEnd <- now |
160 | let ddt = renderDdt | 221 | let ddt = renderDdt |
161 | let frameTime = timeDiff (lastTick timer) frameEnd | 222 | let frameTime = timeDiff (lastTick timer) frameEnd |
162 | when (frameTime < ddt) $ do | 223 | when (frameTime < ddt) $ do |
163 | liftIO $ Timer.sleep (ddt - frameTime) | 224 | Timer.sleep (ddt - frameTime) |
164 | 225 | ||
165 | when (continue && not close) $ do | 226 | when (continue && not close) $ do |
166 | loop' window renderDdt animationDdt lastAnimationTimeNextFrame timer app | 227 | loop' window renderDdt animationDdt lastAnimationTimeNextFrame timer app |
228 | |||
229 | -- | Convert FPS to desired delta time. | ||
230 | fpsToDdt :: Int -> TimeDelta | ||
231 | fpsToDdt fps = if fps > 0 then secToTimeDelta (1.0 / fromIntegral fps) else 0 | ||
diff --git a/Spear/Game.hs b/Spear/Game.hs index 92cc680..1af8e9b 100644 --- a/Spear/Game.hs +++ b/Spear/Game.hs | |||
@@ -1,8 +1,11 @@ | |||
1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | 1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} |
2 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
3 | |||
2 | module Spear.Game | 4 | module Spear.Game |
3 | ( Game | 5 | ( Game |
4 | , GameException (..) | 6 | , GameException(..) |
5 | , ResourceClass (..) | 7 | , HasState(..) |
8 | , ResourceClass(..) | ||
6 | , ReleaseKey | 9 | , ReleaseKey |
7 | -- * Game state | 10 | -- * Game state |
8 | , get | 11 | , get |
@@ -18,15 +21,12 @@ module Spear.Game | |||
18 | , catch | 21 | , catch |
19 | -- * Running and IO | 22 | -- * Running and IO |
20 | , runGame | 23 | , runGame |
21 | , evalGame | ||
22 | , runSubGame | 24 | , runSubGame |
23 | , runSubGame' | ||
24 | , evalSubGame | ||
25 | , execSubGame | ||
26 | , runSiblingGame | 25 | , runSiblingGame |
27 | , runSiblingGame' | 26 | , eval |
28 | , evalSiblingGame | 27 | , exec |
29 | , execSiblingGame | 28 | , exec' |
29 | , siblingGame | ||
30 | , liftIO | 30 | , liftIO |
31 | ) | 31 | ) |
32 | where | 32 | where |
@@ -44,7 +44,6 @@ import Control.Monad.Trans.Resource | |||
44 | class ResourceClass a where | 44 | class ResourceClass a where |
45 | getResource :: a -> ReleaseKey | 45 | getResource :: a -> ReleaseKey |
46 | 46 | ||
47 | |||
48 | -- | A game exception. | 47 | -- | A game exception. |
49 | -- | 48 | -- |
50 | -- This is mostly a convenient wrapper around `String` so that we can throw | 49 | -- This is mostly a convenient wrapper around `String` so that we can throw |
@@ -53,7 +52,6 @@ newtype GameException = GameException String deriving (Show) | |||
53 | 52 | ||
54 | instance Exception GameException | 53 | instance Exception GameException |
55 | 54 | ||
56 | |||
57 | -- | The game monad. | 55 | -- | The game monad. |
58 | -- | 56 | -- |
59 | -- The game monad performs three different roles: | 57 | -- The game monad performs three different roles: |
@@ -73,6 +71,14 @@ newtype Game s a = Game { getGame :: StateT s (ResourceT IO) a } | |||
73 | , MonadResource | 71 | , MonadResource |
74 | ) | 72 | ) |
75 | 73 | ||
74 | -- | A class used to define state hierarchies. | ||
75 | -- | ||
76 | -- By declaring `HasState s t`, a `Game s` monad can then execute actions of a | ||
77 | -- `Game t` monad more conveniently with `siblingGame`. | ||
78 | class HasState s t where | ||
79 | getInnerState :: s -> t | ||
80 | setInnerState :: s -> t -> s | ||
81 | |||
76 | 82 | ||
77 | -- | Release the given 'Resource'. | 83 | -- | Release the given 'Resource'. |
78 | release' :: ResourceClass a => a -> Game s () | 84 | release' :: ResourceClass a => a -> Game s () |
@@ -91,49 +97,39 @@ assertMaybe (Just x) _ = return x | |||
91 | -- result and its final state. | 97 | -- result and its final state. |
92 | -- | 98 | -- |
93 | -- Any resources acquired by the given game are released when this returns. | 99 | -- Any resources acquired by the given game are released when this returns. |
94 | runGame :: Game s a -> s -> IO (a, s) | 100 | runGame :: s -> Game s a -> IO (a, s) |
95 | runGame game = runResourceT . runStateT (getGame game) | 101 | runGame state game = runResourceT . runStateT (getGame game) $ state |
96 | |||
97 | -- | Run the given game and return its result. | ||
98 | evalGame :: Game s a -> s -> IO a | ||
99 | evalGame g s = fst <$> runGame g s | ||
100 | 102 | ||
101 | -- | Run the given sub-game, unrolling the full monad stack and returning the | 103 | -- | Run the given sub-game, unrolling the full monad stack and returning the |
102 | -- game's result and its final state. | 104 | -- game's result and its final state. |
103 | -- | 105 | -- |
104 | -- Like `runGame`, this frees any resources that are acquired by the sub-game. | 106 | -- Like `runGame`, this frees any resources that are acquired by the sub-game. |
105 | -- If you want to keep acquired resources, see `runSiblingGame` instead. | 107 | -- If you want to keep acquired resources, see `runSiblingGame` instead. |
106 | runSubGame :: Game s a -> s -> Game t (a, s) | 108 | runSubGame :: s -> Game s a -> Game t (a, s) |
107 | runSubGame g s = liftIO $ runGame g s | 109 | runSubGame state game = liftIO $ runGame state game |
108 | |||
109 | -- | Run the given sub-game and return its result. | ||
110 | evalSubGame :: Game s a -> s -> Game t a | ||
111 | evalSubGame g s = fst <$> runSubGame g s | ||
112 | |||
113 | -- | Like 'runSubGame', but discarding the result. | ||
114 | runSubGame' :: Game s a -> s -> Game t () | ||
115 | runSubGame' g s = void $ runSubGame g s | ||
116 | |||
117 | -- | Run the given sub-game and return its state. | ||
118 | execSubGame :: Game s a -> s -> Game t s | ||
119 | execSubGame g s = snd <$> runSubGame g s | ||
120 | 110 | ||
121 | -- | Run the given sibling game, unrolling the state transformer but not the | 111 | -- | Run the given sibling game, unrolling the state transformer but not the |
122 | -- resource transformer. | 112 | -- resource transformer. |
123 | -- | 113 | -- |
124 | -- Unlike `runSubGame`, any resources acquired by the sibling game are *not* | 114 | -- Unlike `runSubGame`, any resources acquired by the sibling game are *not* |
125 | -- released. | 115 | -- released. |
126 | runSiblingGame :: Game s a -> s -> Game t (a, s) | 116 | runSiblingGame :: s -> Game s a -> Game t (a, s) |
127 | runSiblingGame game = Game . lift . runStateT (getGame game) | 117 | runSiblingGame state game = Game . lift $ runStateT (getGame game) state |
128 | 118 | ||
129 | -- | Like 'runSiblingGame', but discarding the result. | 119 | -- | Run the given game and return its result. |
130 | runSiblingGame' :: Game s a -> s -> Game t () | 120 | --eval :: (Monad m s, Monad n s) => (m s a -> s -> n (a, s)) -> m s a -> s -> m a |
131 | runSiblingGame' g s = void $ runSiblingGame g s | 121 | eval runner game state = fst <$> runner game state |
132 | 122 | ||
133 | -- | Run the given sibling game and return its result. | 123 | -- | Run the given game and return its final state. |
134 | evalSiblingGame :: Game s a -> s -> Game t a | 124 | exec runner game state = snd <$> runner game state |
135 | evalSiblingGame g s = fst <$> runSiblingGame g s | 125 | |
136 | 126 | -- | Run the given game and ignore both its result and final state. | |
137 | -- | Run the given sibling game and return its state. | 127 | exec' runner game state = void $ runner game state |
138 | execSiblingGame :: Game s a -> s -> Game t s | 128 | |
139 | execSiblingGame g s = snd <$> runSiblingGame g s | 129 | -- | Run a sibling game on nested state. |
130 | siblingGame :: HasState s t => Game t a -> Game s a | ||
131 | siblingGame tAction = do | ||
132 | tState <- getInnerState <$> get | ||
133 | (result, tState') <- runSiblingGame tState tAction | ||
134 | modify $ \outerState -> setInnerState outerState tState' | ||
135 | return result | ||
diff --git a/Spear/Render/Core/Buffer.hs b/Spear/Render/Core/Buffer.hs index eaff475..3003987 100644 --- a/Spear/Render/Core/Buffer.hs +++ b/Spear/Render/Core/Buffer.hs | |||
@@ -14,6 +14,7 @@ import Spear.Math.Vector | |||
14 | import Spear.Render.Core.State | 14 | import Spear.Render.Core.State |
15 | 15 | ||
16 | import Control.Monad (unless, void) | 16 | import Control.Monad (unless, void) |
17 | import Control.Monad.IO.Class | ||
17 | import qualified Data.HashMap as HashMap | 18 | import qualified Data.HashMap as HashMap |
18 | import Data.Word | 19 | import Data.Word |
19 | import Foreign.C.Types | 20 | import Foreign.C.Types |
@@ -68,8 +69,8 @@ deleteBuffer buffer = do | |||
68 | release' buffer | 69 | release' buffer |
69 | 70 | ||
70 | -- TODO: use glBufferSubData for updates. | 71 | -- TODO: use glBufferSubData for updates. |
71 | updateBuffer :: Buffer -> BufferData -> IO () | 72 | updateBuffer :: MonadIO io => Buffer -> BufferData -> io () |
72 | updateBuffer buffer bufferData = | 73 | updateBuffer buffer bufferData = liftIO $ |
73 | unless (bufferEmpty bufferData) $ do | 74 | unless (bufferEmpty bufferData) $ do |
74 | glBindBuffer GL_ARRAY_BUFFER (bufferHandle buffer) | 75 | glBindBuffer GL_ARRAY_BUFFER (bufferHandle buffer) |
75 | uploadData (bufferUsage buffer) bufferData | 76 | uploadData (bufferUsage buffer) bufferData |
diff --git a/Spear/Render/Core/Geometry.hs b/Spear/Render/Core/Geometry.hs index 10ff709..05c23ec 100644 --- a/Spear/Render/Core/Geometry.hs +++ b/Spear/Render/Core/Geometry.hs | |||
@@ -21,6 +21,7 @@ import Spear.Render.Core.Buffer | |||
21 | import Spear.Render.Core.Constants | 21 | import Spear.Render.Core.Constants |
22 | import Spear.Render.Core.State | 22 | import Spear.Render.Core.State |
23 | 23 | ||
24 | import Control.Monad.IO.Class | ||
24 | import Data.HashMap as HashMap | 25 | import Data.HashMap as HashMap |
25 | import Data.IORef | 26 | import Data.IORef |
26 | import Data.Maybe (fromJust) | 27 | import Data.Maybe (fromJust) |
@@ -105,8 +106,8 @@ deleteGeometry geometry = do | |||
105 | geometries = HashMap.delete (geometryVao geometry) (geometries state) }) | 106 | geometries = HashMap.delete (geometryVao geometry) (geometries state) }) |
106 | release' geometry | 107 | release' geometry |
107 | 108 | ||
108 | renderGeometry :: Geometry -> IO () | 109 | renderGeometry :: MonadIO io => Geometry -> io () |
109 | renderGeometry geometry = do | 110 | renderGeometry geometry = liftIO $ do |
110 | gdata <- readIORef (geometryData geometry) | 111 | gdata <- readIORef (geometryData geometry) |
111 | let mode = toGLPrimitiveType $ geometryPrimitiveType gdata | 112 | let mode = toGLPrimitiveType $ geometryPrimitiveType gdata |
112 | glBindVertexArray (geometryVao geometry) | 113 | glBindVertexArray (geometryVao geometry) |
@@ -121,8 +122,8 @@ renderGeometry geometry = do | |||
121 | 122 | ||
122 | -- Functions for updating dynamic geometry. | 123 | -- Functions for updating dynamic geometry. |
123 | 124 | ||
124 | setPositions :: Geometry -> [Vector3] -> IO () | 125 | setPositions :: MonadIO io => Geometry -> [Vector3] -> io () |
125 | setPositions geometry vectors = do | 126 | setPositions geometry vectors = liftIO $ do |
126 | gdata <- readIORef $ geometryData geometry | 127 | gdata <- readIORef $ geometryData geometry |
127 | case vertexPositions gdata of | 128 | case vertexPositions gdata of |
128 | VertexPositions3d view -> do | 129 | VertexPositions3d view -> do |
diff --git a/Spear/Render/Core/Pipeline.hs b/Spear/Render/Core/Pipeline.hs index 724b391..ee9c7d2 100644 --- a/Spear/Render/Core/Pipeline.hs +++ b/Spear/Render/Core/Pipeline.hs | |||
@@ -13,8 +13,9 @@ module Spear.Render.Core.Pipeline | |||
13 | ) | 13 | ) |
14 | where | 14 | where |
15 | 15 | ||
16 | import Data.Bits ((.|.)) | 16 | import Control.Monad.IO.Class |
17 | import Data.List (foldl') | 17 | import Data.Bits ((.|.)) |
18 | import Data.List (foldl') | ||
18 | import Graphics.GL.Core46 | 19 | import Graphics.GL.Core46 |
19 | 20 | ||
20 | 21 | ||
@@ -24,7 +25,7 @@ data BufferTarget | |||
24 | | StencilBuffer | 25 | | StencilBuffer |
25 | 26 | ||
26 | 27 | ||
27 | clearBuffers :: [BufferTarget] -> IO () | 28 | clearBuffers :: MonadIO io => [BufferTarget] -> io () |
28 | clearBuffers = glClear . toBufferBitfield | 29 | clearBuffers = glClear . toBufferBitfield |
29 | where toBufferBitfield = foldl' (.|.) 0 . (<$>) toGLEnum | 30 | where toBufferBitfield = foldl' (.|.) 0 . (<$>) toGLEnum |
30 | toGLEnum target = case target of | 31 | toGLEnum target = case target of |
@@ -32,28 +33,28 @@ clearBuffers = glClear . toBufferBitfield | |||
32 | DepthBuffer -> GL_DEPTH_BUFFER_BIT | 33 | DepthBuffer -> GL_DEPTH_BUFFER_BIT |
33 | StencilBuffer -> GL_STENCIL_BUFFER_BIT | 34 | StencilBuffer -> GL_STENCIL_BUFFER_BIT |
34 | 35 | ||
35 | setBlending :: Bool -> IO () | 36 | setBlending :: MonadIO io => Bool -> io () |
36 | setBlending enable = | 37 | setBlending enable = |
37 | if enable | 38 | if enable |
38 | then glEnable GL_BLEND >> glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA | 39 | then glEnable GL_BLEND >> glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA |
39 | else glDisable GL_BLEND | 40 | else glDisable GL_BLEND |
40 | 41 | ||
41 | setClearColour :: (Float, Float, Float, Float) -> IO () | 42 | setClearColour :: MonadIO io => (Float, Float, Float, Float) -> io () |
42 | setClearColour (r,g,b,a) = glClearColor r g b a | 43 | setClearColour (r,g,b,a) = glClearColor r g b a |
43 | 44 | ||
44 | setClearDepth :: Double -> IO () | 45 | setClearDepth :: MonadIO io => Double -> io () |
45 | setClearDepth = glClearDepth | 46 | setClearDepth = glClearDepth |
46 | 47 | ||
47 | setClearStencil :: Int -> IO () | 48 | setClearStencil :: MonadIO io => Int -> io () |
48 | setClearStencil = glClearStencil . fromIntegral | 49 | setClearStencil = glClearStencil . fromIntegral |
49 | 50 | ||
50 | setCulling :: Bool -> IO () | 51 | setCulling :: MonadIO io => Bool -> io () |
51 | setCulling enable = (if enable then glEnable else glDisable) GL_CULL_FACE | 52 | setCulling enable = (if enable then glEnable else glDisable) GL_CULL_FACE |
52 | 53 | ||
53 | setDepthMask :: Bool -> IO () | 54 | setDepthMask :: MonadIO io => Bool -> io () |
54 | setDepthMask enable = glDepthMask (if enable then GL_TRUE else GL_FALSE) | 55 | setDepthMask enable = glDepthMask (if enable then GL_TRUE else GL_FALSE) |
55 | 56 | ||
56 | setPolygonOffset :: Float -> Float -> IO () | 57 | setPolygonOffset :: MonadIO io => Float -> Float -> io () |
57 | setPolygonOffset scale bias = do | 58 | setPolygonOffset scale bias = do |
58 | glPolygonOffset scale bias | 59 | glPolygonOffset scale bias |
59 | if scale /= 0 && bias /= 0 | 60 | if scale /= 0 && bias /= 0 |
@@ -61,6 +62,7 @@ setPolygonOffset scale bias = do | |||
61 | else glDisable GL_POLYGON_OFFSET_FILL | 62 | else glDisable GL_POLYGON_OFFSET_FILL |
62 | 63 | ||
63 | setViewport :: | 64 | setViewport :: |
65 | MonadIO io => | ||
64 | -- | x | 66 | -- | x |
65 | Int -> | 67 | Int -> |
66 | -- | y | 68 | -- | y |
@@ -69,6 +71,6 @@ setViewport :: | |||
69 | Int -> | 71 | Int -> |
70 | -- | height | 72 | -- | height |
71 | Int -> | 73 | Int -> |
72 | IO () | 74 | io () |
73 | setViewport x y width height = | 75 | setViewport x y width height = |
74 | glViewport (fromIntegral x) (fromIntegral y) (fromIntegral width) (fromIntegral height) | 76 | glViewport (fromIntegral x) (fromIntegral y) (fromIntegral width) (fromIntegral height) |
diff --git a/Spear/Render/Core/Shader.hs b/Spear/Render/Core/Shader.hs index 21db66f..32a3cb1 100644 --- a/Spear/Render/Core/Shader.hs +++ b/Spear/Render/Core/Shader.hs | |||
@@ -20,6 +20,7 @@ import Spear.Math.Vector | |||
20 | import Spear.Render.Core.State | 20 | import Spear.Render.Core.State |
21 | 21 | ||
22 | import Control.Monad (mapM_) | 22 | import Control.Monad (mapM_) |
23 | import Control.Monad.IO.Class | ||
23 | import Data.Bits | 24 | import Data.Bits |
24 | import Data.Hashable | 25 | import Data.Hashable |
25 | import Data.HashMap as HashMap | 26 | import Data.HashMap as HashMap |
@@ -146,22 +147,22 @@ deleteShaderProgram program = do | |||
146 | shaderPrograms = HashMap.delete (shaderProgramHash program) (shaderPrograms state)}) | 147 | shaderPrograms = HashMap.delete (shaderProgramHash program) (shaderPrograms state)}) |
147 | release' program | 148 | release' program |
148 | 149 | ||
149 | activateShaderProgram :: ShaderProgram -> IO () | 150 | activateShaderProgram :: MonadIO io => ShaderProgram -> io () |
150 | activateShaderProgram program = do | 151 | activateShaderProgram program = do |
151 | glUseProgram . shaderProgramHandle $ program | 152 | glUseProgram . shaderProgramHandle $ program |
152 | applyUniforms program | 153 | applyUniforms program |
153 | 154 | ||
154 | deactivateShaderProgram :: ShaderProgram -> IO () | 155 | deactivateShaderProgram :: MonadIO io => ShaderProgram -> io () |
155 | deactivateShaderProgram _ = glUseProgram 0 | 156 | deactivateShaderProgram _ = glUseProgram 0 |
156 | 157 | ||
157 | setUniform :: ShaderUniform -> ShaderProgram -> IO () | 158 | setUniform :: MonadIO io => ShaderUniform -> ShaderProgram -> io () |
158 | setUniform uniform program = | 159 | setUniform uniform program = liftIO $ |
159 | modifyIORef (shaderProgramUniforms program) (setUniform' . removeUniform) | 160 | modifyIORef (shaderProgramUniforms program) (setUniform' . removeUniform) |
160 | where removeUniform = deleteBy matchesUniform uniform | 161 | where removeUniform = deleteBy matchesUniform uniform |
161 | matchesUniform uniform u = uniformName u == uniformName uniform | 162 | matchesUniform uniform u = uniformName u == uniformName uniform |
162 | setUniform' = (:) uniform | 163 | setUniform' = (:) uniform |
163 | 164 | ||
164 | applyUniforms :: ShaderProgram -> IO () | 165 | applyUniforms :: MonadIO io => ShaderProgram -> io () |
165 | applyUniforms program = | 166 | applyUniforms program = |
166 | let update (FloatUniform name value) = | 167 | let update (FloatUniform name value) = |
167 | glGetUniformLocation' handle name >>= | 168 | glGetUniformLocation' handle name >>= |
@@ -181,7 +182,7 @@ applyUniforms program = | |||
181 | \location -> withArray mat4s $ \ptrMat4s -> | 182 | \location -> withArray mat4s $ \ptrMat4s -> |
182 | glUniformMatrix4fv location (fromIntegral $ length mat4s) GL_FALSE (unsafeCoerce ptrMat4s) | 183 | glUniformMatrix4fv location (fromIntegral $ length mat4s) GL_FALSE (unsafeCoerce ptrMat4s) |
183 | handle = shaderProgramHandle program | 184 | handle = shaderProgramHandle program |
184 | in do | 185 | in liftIO $ do |
185 | uniforms <- readIORef (shaderProgramUniforms program) | 186 | uniforms <- readIORef (shaderProgramUniforms program) |
186 | mapM_ update uniforms | 187 | mapM_ update uniforms |
187 | writeIORef (shaderProgramUniforms program) [] | 188 | writeIORef (shaderProgramUniforms program) [] |
@@ -189,7 +190,7 @@ applyUniforms program = | |||
189 | -- Private | 190 | -- Private |
190 | 191 | ||
191 | glGetUniformLocation' :: GLuint -> String -> IO GLint | 192 | glGetUniformLocation' :: GLuint -> String -> IO GLint |
192 | glGetUniformLocation' handle name = | 193 | glGetUniformLocation' handle name = liftIO $ |
193 | withCString name $ \nameCStr -> | 194 | withCString name $ \nameCStr -> |
194 | glGetUniformLocation (fromIntegral handle) (unsafeCoerce nameCStr) | 195 | glGetUniformLocation (fromIntegral handle) (unsafeCoerce nameCStr) |
195 | 196 | ||
diff --git a/Spear/Render/Immediate.hs b/Spear/Render/Immediate.hs index 26f6513..b3a8998 100644 --- a/Spear/Render/Immediate.hs +++ b/Spear/Render/Immediate.hs | |||
@@ -79,22 +79,21 @@ deleteImmRenderer immState = do | |||
79 | immStart :: Game ImmRenderState () | 79 | immStart :: Game ImmRenderState () |
80 | immStart = do | 80 | immStart = do |
81 | state <- get | 81 | state <- get |
82 | liftIO $ activateShaderProgram (shader state) | 82 | activateShaderProgram (shader state) |
83 | 83 | ||
84 | immEnd :: Game ImmRenderState () | 84 | immEnd :: Game ImmRenderState () |
85 | immEnd = do | 85 | immEnd = do |
86 | state <- get | 86 | state <- get |
87 | liftIO $ deactivateShaderProgram (shader state) | 87 | deactivateShaderProgram (shader state) |
88 | 88 | ||
89 | immDrawTriangles :: [Vector3] -> Game ImmRenderState () | 89 | immDrawTriangles :: [Vector3] -> Game ImmRenderState () |
90 | immDrawTriangles vertices = do | 90 | immDrawTriangles vertices = do |
91 | unless (null vertices) $ do | 91 | unless (null vertices) $ do |
92 | loadMatrixStack | 92 | loadMatrixStack |
93 | state <- get | 93 | state <- get |
94 | liftIO $ do | 94 | setPositions (triangles state) vertices |
95 | setPositions (triangles state) vertices | 95 | applyUniforms (shader state) |
96 | applyUniforms (shader state) | 96 | renderGeometry (triangles state) |
97 | renderGeometry (triangles state) | ||
98 | 97 | ||
99 | -- NOTE: consider using triangle strips for quads. This will require a separate | 98 | -- NOTE: consider using triangle strips for quads. This will require a separate |
100 | -- Geometry. Using Vector3 for everything currently makes this simple. | 99 | -- Geometry. Using Vector3 for everything currently makes this simple. |
@@ -137,17 +136,17 @@ immPreservingMatrix f = do | |||
137 | immSetColour :: Vector4 -> Game ImmRenderState () | 136 | immSetColour :: Vector4 -> Game ImmRenderState () |
138 | immSetColour colour = do | 137 | immSetColour colour = do |
139 | state <- get | 138 | state <- get |
140 | liftIO $ setUniform (Vec4Uniform "Colour" colour) (shader state) | 139 | setUniform (Vec4Uniform "Colour" colour) (shader state) |
141 | 140 | ||
142 | immSetModelMatrix :: Matrix4 -> Game ImmRenderState () | 141 | immSetModelMatrix :: Matrix4 -> Game ImmRenderState () |
143 | immSetModelMatrix model = do | 142 | immSetModelMatrix model = do |
144 | state <- get | 143 | state <- get |
145 | liftIO $ setUniform (Mat4Uniform "Model" model) (shader state) | 144 | setUniform (Mat4Uniform "Model" model) (shader state) |
146 | 145 | ||
147 | immSetViewProjectionMatrix :: Matrix4 -> Game ImmRenderState () | 146 | immSetViewProjectionMatrix :: Matrix4 -> Game ImmRenderState () |
148 | immSetViewProjectionMatrix viewProjection = do | 147 | immSetViewProjectionMatrix viewProjection = do |
149 | state <- get | 148 | state <- get |
150 | liftIO $ setUniform (Mat4Uniform "ViewProjection" viewProjection) (shader state) | 149 | setUniform (Mat4Uniform "ViewProjection" viewProjection) (shader state) |
151 | 150 | ||
152 | -- Private | 151 | -- Private |
153 | 152 | ||
diff --git a/Spear/Scene/Loader.hs b/Spear/Scene/Loader.hs index 4bbbde0..5f96f8c 100644 --- a/Spear/Scene/Loader.hs +++ b/Spear/Scene/Loader.hs | |||
@@ -58,7 +58,7 @@ validate _ = Nothing | |||
58 | 58 | ||
59 | -- | Load the scene described by the given 'SceneGraph'. | 59 | -- | Load the scene described by the given 'SceneGraph'. |
60 | resourceMap :: SceneGraph -> Game s SceneResources | 60 | resourceMap :: SceneGraph -> Game s SceneResources |
61 | resourceMap g = execSubGame (resourceMap' g) emptySceneResources | 61 | resourceMap g = exec runSubGame emptySceneResources (resourceMap' g) |
62 | 62 | ||
63 | resourceMap' :: SceneGraph -> Loader () | 63 | resourceMap' :: SceneGraph -> Loader () |
64 | resourceMap' node@(SceneLeaf nid props) = do | 64 | resourceMap' node@(SceneLeaf nid props) = do |
diff --git a/Spear/Sound/Sound.hs b/Spear/Sound/Sound.hs index 53a1a46..832ffb8 100644 --- a/Spear/Sound/Sound.hs +++ b/Spear/Sound/Sound.hs | |||
@@ -16,9 +16,10 @@ where | |||
16 | import Spear.Game | 16 | import Spear.Game |
17 | import Spear.Sound.State | 17 | import Spear.Sound.State |
18 | 18 | ||
19 | import Data.Set as Set | 19 | import Control.Monad.IO.Class |
20 | import Data.StateVar (($=)) | 20 | import Data.Set as Set |
21 | import qualified Sound.ALUT as AL | 21 | import Data.StateVar (($=)) |
22 | import qualified Sound.ALUT as AL | ||
22 | 23 | ||
23 | 24 | ||
24 | data LoopMode | 25 | data LoopMode |
@@ -86,16 +87,16 @@ deleteSoundSource source = do | |||
86 | release' source | 87 | release' source |
87 | 88 | ||
88 | -- | Set the sound that the sound source emits. | 89 | -- | Set the sound that the sound source emits. |
89 | setSoundSourceBuffer :: SoundSource -> SoundBuffer -> IO () | 90 | setSoundSourceBuffer :: MonadIO io => SoundSource -> SoundBuffer -> io () |
90 | setSoundSourceBuffer source buffer = | 91 | setSoundSourceBuffer source buffer = |
91 | AL.buffer (alSource source) $= Just (alBuffer buffer) | 92 | AL.buffer (alSource source) $= Just (alBuffer buffer) |
92 | 93 | ||
93 | -- | Set the sound's loop mode. | 94 | -- | Set the sound's loop mode. |
94 | setSoundLoopMode :: SoundSource -> LoopMode -> IO () | 95 | setSoundLoopMode :: MonadIO io => SoundSource -> LoopMode -> io () |
95 | setSoundLoopMode source mode = AL.loopingMode (alSource source) $= alMode mode | 96 | setSoundLoopMode source mode = AL.loopingMode (alSource source) $= alMode mode |
96 | where alMode SingleShot = AL.OneShot | 97 | where alMode SingleShot = AL.OneShot |
97 | alMode Loop = AL.Looping | 98 | alMode Loop = AL.Looping |
98 | 99 | ||
99 | -- | Play the sound sources. | 100 | -- | Play the sound sources. |
100 | playSounds :: [SoundSource] -> IO () | 101 | playSounds :: MonadIO io => [SoundSource] -> io () |
101 | playSounds = AL.play . (alSource <$>) | 102 | playSounds = AL.play . (alSource <$>) |
diff --git a/Spear/Sys/Timer.hsc b/Spear/Sys/Timer.hsc index fb18521..46a692d 100644 --- a/Spear/Sys/Timer.hsc +++ b/Spear/Sys/Timer.hsc | |||
@@ -25,6 +25,7 @@ import Foreign.Marshal.Alloc (alloca) | |||
25 | import Foreign.Ptr | 25 | import Foreign.Ptr |
26 | import Foreign.Storable | 26 | import Foreign.Storable |
27 | import Control.Monad | 27 | import Control.Monad |
28 | import Control.Monad.IO.Class | ||
28 | import System.IO.Unsafe | 29 | import System.IO.Unsafe |
29 | 30 | ||
30 | #include "Timer/timer.h" | 31 | #include "Timer/timer.h" |
@@ -134,22 +135,22 @@ withTimer' c_func timer = alloca $ \ptr -> do | |||
134 | peek ptr | 135 | peek ptr |
135 | 136 | ||
136 | -- | Construct a new timer. | 137 | -- | Construct a new timer. |
137 | newTimer :: IO Timer | 138 | newTimer :: MonadIO io => io Timer |
138 | newTimer = alloca $ \ptr -> do | 139 | newTimer = liftIO . alloca $ \ptr -> do |
139 | c_timer_make ptr | 140 | c_timer_make ptr |
140 | peek ptr | 141 | peek ptr |
141 | 142 | ||
142 | -- | Start the timer. | 143 | -- | Start the timer. |
143 | start :: Timer -> IO () | 144 | start :: MonadIO io => Timer -> io () |
144 | start = withTimer c_timer_start | 145 | start = liftIO . withTimer c_timer_start |
145 | 146 | ||
146 | -- | Update the timer. | 147 | -- | Update the timer. |
147 | tick :: Timer -> IO Timer | 148 | tick :: MonadIO io => Timer -> io Timer |
148 | tick = withTimer' c_timer_tick | 149 | tick = liftIO . withTimer' c_timer_tick |
149 | 150 | ||
150 | -- | Get the current time. | 151 | -- | Get the current time. |
151 | now :: IO TimePoint | 152 | now :: MonadIO io => io TimePoint |
152 | now = alloca $ \ptr -> do | 153 | now = liftIO . alloca $ \ptr -> do |
153 | c_time_now ptr | 154 | c_time_now ptr |
154 | peek ptr | 155 | peek ptr |
155 | 156 | ||
@@ -186,5 +187,5 @@ timeAdd t dt = unsafeDupablePerformIO $ | |||
186 | peek ptr | 187 | peek ptr |
187 | 188 | ||
188 | -- | Put the caller thread to sleep for the given amount of time. | 189 | -- | Put the caller thread to sleep for the given amount of time. |
189 | sleep :: TimeDelta -> IO () | 190 | sleep :: MonadIO io => TimeDelta -> io () |
190 | sleep = c_time_sleep | 191 | sleep = liftIO . c_time_sleep |
diff --git a/Spear/Window.hs b/Spear/Window.hs index 75a38f7..a873362 100644 --- a/Spear/Window.hs +++ b/Spear/Window.hs | |||
@@ -34,6 +34,7 @@ import Spear.Game | |||
34 | import Control.Concurrent.MVar | 34 | import Control.Concurrent.MVar |
35 | import Control.Exception | 35 | import Control.Exception |
36 | import Control.Monad (foldM, unless, void, when) | 36 | import Control.Monad (foldM, unless, void, when) |
37 | import Control.Monad.IO.Class | ||
37 | import Data.Functor ((<&>)) | 38 | import Data.Functor ((<&>)) |
38 | import Data.Maybe (fromJust, fromMaybe, isJust) | 39 | import Data.Maybe (fromJust, fromMaybe, isJust) |
39 | import qualified Graphics.UI.GLFW as GLFW | 40 | import qualified Graphics.UI.GLFW as GLFW |
@@ -76,12 +77,8 @@ data Window = Window | |||
76 | } | 77 | } |
77 | 78 | ||
78 | 79 | ||
79 | withWindow :: | 80 | withWindow :: MonadIO io => Dimensions -> WindowTitle -> (Window -> IO a) -> io a |
80 | Dimensions -> | 81 | withWindow dim@(w, h) windowTitle run = liftIO $ do |
81 | WindowTitle -> | ||
82 | (Window -> IO a) -> | ||
83 | IO a | ||
84 | withWindow dim@(w, h) windowTitle run = do | ||
85 | window <- do | 82 | window <- do |
86 | success <- GLFW.init | 83 | success <- GLFW.init |
87 | unless success $ throw (WindowException "GLFW.initialize failed") | 84 | unless success $ throw (WindowException "GLFW.initialize failed") |
@@ -91,11 +88,8 @@ withWindow dim@(w, h) windowTitle run = do | |||
91 | GLFW.terminate | 88 | GLFW.terminate |
92 | return result | 89 | return result |
93 | 90 | ||
94 | setup :: | 91 | setup :: MonadIO io => Dimensions -> WindowTitle -> io Window |
95 | Dimensions -> | 92 | setup (w, h) windowTitle = liftIO $ do |
96 | WindowTitle -> | ||
97 | IO Window | ||
98 | setup (w, h) windowTitle = do | ||
99 | closeRequest <- newEmptyMVar | 93 | closeRequest <- newEmptyMVar |
100 | windowEvents <- newEmptyMVar | 94 | windowEvents <- newEmptyMVar |
101 | inputEvents <- newEmptyMVar | 95 | inputEvents <- newEmptyMVar |
@@ -125,14 +119,14 @@ setup (w, h) windowTitle = do | |||
125 | return $ Window window closeRequest inputEvents windowEvents | 119 | return $ Window window closeRequest inputEvents windowEvents |
126 | 120 | ||
127 | -- | Poll for input events. | 121 | -- | Poll for input events. |
128 | pollInputEvents :: Window -> IO [InputEvent] | 122 | pollInputEvents :: MonadIO io => Window -> io [InputEvent] |
129 | pollInputEvents window = do | 123 | pollInputEvents window = liftIO $ do |
130 | GLFW.pollEvents | 124 | GLFW.pollEvents |
131 | getEvents (inputEventsMVar window) | 125 | getEvents (inputEventsMVar window) |
132 | 126 | ||
133 | -- | Poll for window events. | 127 | -- | Poll for window events. |
134 | pollWindowEvents :: Window -> IO [WindowEvent] | 128 | pollWindowEvents :: MonadIO io => Window -> io [WindowEvent] |
135 | pollWindowEvents window = do | 129 | pollWindowEvents window = liftIO $ do |
136 | GLFW.pollEvents | 130 | GLFW.pollEvents |
137 | getEvents (windowEventsMVar window) | 131 | getEvents (windowEventsMVar window) |
138 | 132 | ||
@@ -144,16 +138,16 @@ getEvents mvar = tryTakeMVar mvar >>= \xs -> do | |||
144 | Just events -> return events | 138 | Just events -> return events |
145 | 139 | ||
146 | -- | Return true when the user requests to close the window. | 140 | -- | Return true when the user requests to close the window. |
147 | shouldWindowClose :: Window -> IO Bool | 141 | shouldWindowClose :: MonadIO io => Window -> io Bool |
148 | shouldWindowClose = getRequest . closeRequestMVar | 142 | shouldWindowClose = liftIO . getRequest . closeRequestMVar |
149 | 143 | ||
150 | -- | Swaps buffers. | 144 | -- | Swaps buffers. |
151 | swapBuffers :: Window -> IO () | 145 | swapBuffers :: MonadIO io => Window -> io () |
152 | swapBuffers = GLFW.swapBuffers . glfwWindow | 146 | swapBuffers = liftIO . GLFW.swapBuffers . glfwWindow |
153 | 147 | ||
154 | -- | Get the window's size. | 148 | -- | Get the window's size. |
155 | getWindowSize :: Window -> IO (Width, Height) | 149 | getWindowSize :: MonadIO io => Window -> io (Width, Height) |
156 | getWindowSize = GLFW.getWindowSize . glfwWindow | 150 | getWindowSize = liftIO . GLFW.getWindowSize . glfwWindow |
157 | 151 | ||
158 | getRequest :: MVar Bool -> IO Bool | 152 | getRequest :: MVar Bool -> IO Bool |
159 | getRequest mvar = | 153 | getRequest mvar = |