diff options
author | 3gg <3gg@shellblade.net> | 2023-09-14 09:12:19 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2023-09-14 09:12:25 -0700 |
commit | 4fc298a611785ddac55cb0679953411638679edc (patch) | |
tree | 991fdc91f4c3c8734842309f835d68861364ed9f | |
parent | f10147a471427b6556ecad6f5e0a68dead188f25 (diff) |
New Timer module and game loop with semi-fixed time step.
-rw-r--r-- | Demos/Pong/Main.hs | 22 | ||||
-rw-r--r-- | Spear.cabal | 19 | ||||
-rw-r--r-- | Spear/App.hs | 92 | ||||
-rw-r--r-- | Spear/Sys/Timer.hsc | 255 | ||||
-rw-r--r-- | Spear/Sys/Timer/Timer.h | 130 | ||||
-rw-r--r-- | Spear/Sys/Timer/ctimer.c | 157 | ||||
-rw-r--r-- | Spear/Sys/Timer/timer.c | 101 | ||||
-rw-r--r-- | Spear/Sys/Timer/timer.h | 64 | ||||
-rw-r--r-- | Spear/Window.hs | 105 |
9 files changed, 450 insertions, 495 deletions
diff --git a/Demos/Pong/Main.hs b/Demos/Pong/Main.hs index a49efec..ac0feab 100644 --- a/Demos/Pong/Main.hs +++ b/Demos/Pong/Main.hs | |||
@@ -18,9 +18,11 @@ data GameState = GameState | |||
18 | world :: [GameObject] | 18 | world :: [GameObject] |
19 | } | 19 | } |
20 | 20 | ||
21 | app = App step render resize | ||
22 | |||
21 | main = | 23 | main = |
22 | withWindow (900, 600) (2, 0) (Just "Pong") initGame $ | 24 | withWindow (900, 600) (2, 0) (Just "Pong") initGame $ |
23 | loop step | 25 | loop app |
24 | 26 | ||
25 | initGame :: Window -> Game () GameState | 27 | initGame :: Window -> Game () GameState |
26 | initGame window = return $ GameState window newWorld | 28 | initGame window = return $ GameState window newWorld |
@@ -28,16 +30,18 @@ initGame window = return $ GameState window newWorld | |||
28 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool | 30 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool |
29 | step elapsed dt inputEvents = do | 31 | step elapsed dt inputEvents = do |
30 | gs <- getGameState | 32 | gs <- getGameState |
31 | gameIO . process $ inputEvents | ||
32 | let events = translateEvents inputEvents | 33 | let events = translateEvents inputEvents |
33 | modifyGameState $ \gs -> | 34 | modifyGameState $ \gs -> |
34 | gs | 35 | gs |
35 | { world = stepWorld (realToFrac elapsed) dt events (world gs) | 36 | { world = stepWorld (realToFrac elapsed) (realToFrac dt) events (world gs) |
36 | } | 37 | } |
37 | getGameState >>= \gs -> gameIO . render $ world gs | ||
38 | return (not $ exitRequested inputEvents) | 38 | return (not $ exitRequested inputEvents) |
39 | 39 | ||
40 | render world = do | 40 | render :: Game GameState () |
41 | render = getGameState >>= \gs -> gameIO . render' $ world gs | ||
42 | |||
43 | render' :: [GameObject] -> IO () | ||
44 | render' world = do | ||
41 | -- Clear the background to a different colour than the playable area to make | 45 | -- Clear the background to a different colour than the playable area to make |
42 | -- the latter distinguishable. | 46 | -- the latter distinguishable. |
43 | GL.clearColor $= GL.Color4 0.2 0.2 0.2 0.0 | 47 | GL.clearColor $= GL.Color4 0.2 0.2 0.2 0.0 |
@@ -74,22 +78,20 @@ renderGO go = do | |||
74 | GL.vertex (GL.Vertex2 xmax ymax) | 78 | GL.vertex (GL.Vertex2 xmax ymax) |
75 | GL.vertex (GL.Vertex2 xmax ymin) | 79 | GL.vertex (GL.Vertex2 xmax ymin) |
76 | 80 | ||
77 | process = mapM_ procEvent | 81 | resize :: WindowEvent -> Game s () |
78 | 82 | resize (ResizeEvent w h) = | |
79 | procEvent (Resize w h) = | ||
80 | let r = fromIntegral w / fromIntegral h | 83 | let r = fromIntegral w / fromIntegral h |
81 | pad = if r > 1 then (r-1) / 2 else (1/r - 1) / 2 | 84 | pad = if r > 1 then (r-1) / 2 else (1/r - 1) / 2 |
82 | left = if r > 1 then -pad else 0 | 85 | left = if r > 1 then -pad else 0 |
83 | right = if r > 1 then 1 + pad else 1 | 86 | right = if r > 1 then 1 + pad else 1 |
84 | bottom = if r > 1 then 0 else -pad | 87 | bottom = if r > 1 then 0 else -pad |
85 | top = if r > 1 then 1 else 1 + pad | 88 | top = if r > 1 then 1 else 1 + pad |
86 | in do | 89 | in gameIO $ do |
87 | GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral w) (fromIntegral h)) | 90 | GL.viewport $= (GL.Position 0 0, GL.Size (fromIntegral w) (fromIntegral h)) |
88 | GL.matrixMode $= GL.Projection | 91 | GL.matrixMode $= GL.Projection |
89 | GL.loadIdentity | 92 | GL.loadIdentity |
90 | GL.ortho left right bottom top (-1) 1 | 93 | GL.ortho left right bottom top (-1) 1 |
91 | GL.matrixMode $= GL.Modelview 0 | 94 | GL.matrixMode $= GL.Modelview 0 |
92 | procEvent _ = return () | ||
93 | 95 | ||
94 | translateEvents = mapMaybe translateEvents' | 96 | translateEvents = mapMaybe translateEvents' |
95 | where translateEvents' (KeyDown KEY_LEFT) = Just MoveLeft | 97 | where translateEvents' (KeyDown KEY_LEFT) = Just MoveLeft |
diff --git a/Spear.cabal b/Spear.cabal index 448f7f4..40b625d 100644 --- a/Spear.cabal +++ b/Spear.cabal | |||
@@ -83,14 +83,15 @@ library | |||
83 | 83 | ||
84 | cc-options: -O2 -g -Wno-unused-result | 84 | cc-options: -O2 -g -Wno-unused-result |
85 | 85 | ||
86 | c-sources: Spear/Assets/Image/Image.c | 86 | c-sources: |
87 | Spear/Assets/Image/BMP/BMP_load.c | 87 | Spear/Assets/Image/Image.c |
88 | Spear/Assets/Model/Model.c | 88 | Spear/Assets/Image/BMP/BMP_load.c |
89 | Spear/Assets/Model/MD2/MD2_load.c | 89 | Spear/Assets/Model/Model.c |
90 | Spear/Assets/Model/OBJ/cvector.c | 90 | Spear/Assets/Model/MD2/MD2_load.c |
91 | Spear/Assets/Model/OBJ/OBJ_load.c | 91 | Spear/Assets/Model/OBJ/cvector.c |
92 | Spear/Render/RenderModel.c | 92 | Spear/Assets/Model/OBJ/OBJ_load.c |
93 | Spear/Sys/Timer/ctimer.c | 93 | Spear/Render/RenderModel.c |
94 | Spear/Sys/Timer/timer.c | ||
94 | 95 | ||
95 | includes: | 96 | includes: |
96 | Spear/Assets/Image/BMP/BMP_load.h | 97 | Spear/Assets/Image/BMP/BMP_load.h |
@@ -104,7 +105,7 @@ library | |||
104 | Spear/Assets/Model/Model_error_code.h | 105 | Spear/Assets/Model/Model_error_code.h |
105 | Spear/Assets/Model/sys_types.h | 106 | Spear/Assets/Model/sys_types.h |
106 | Spear/Render/RenderModel.h | 107 | Spear/Render/RenderModel.h |
107 | Timer/Timer.h | 108 | Timer/timer.h |
108 | 109 | ||
109 | include-dirs: | 110 | include-dirs: |
110 | . | 111 | . |
diff --git a/Spear/App.hs b/Spear/App.hs index ca9a355..41a338b 100644 --- a/Spear/App.hs +++ b/Spear/App.hs | |||
@@ -1,5 +1,6 @@ | |||
1 | module Spear.App | 1 | module Spear.App |
2 | ( Elapsed, | 2 | ( App(..), |
3 | Elapsed, | ||
3 | Dt, | 4 | Dt, |
4 | Step, | 5 | Step, |
5 | loop, | 6 | loop, |
@@ -7,6 +8,7 @@ module Spear.App | |||
7 | where | 8 | where |
8 | 9 | ||
9 | import Control.Monad | 10 | import Control.Monad |
11 | import Data.Fixed (mod') | ||
10 | import GHC.Float | 12 | import GHC.Float |
11 | import Spear.Game | 13 | import Spear.Game |
12 | import Spear.Sys.Timer as Timer | 14 | import Spear.Sys.Timer as Timer |
@@ -14,49 +16,77 @@ import Spear.Window | |||
14 | 16 | ||
15 | maxFPS = 60 | 17 | maxFPS = 60 |
16 | 18 | ||
17 | -- | Time elapsed since the application started. | 19 | -- | Time elapsed. |
18 | type Elapsed = Double | 20 | type Elapsed = Double |
19 | 21 | ||
20 | -- | Time elapsed since the last frame. | 22 | -- | Time elapsed since the last frame. |
21 | type Dt = Float | 23 | type Dt = Double |
22 | 24 | ||
23 | -- | Return true if the application should continue running, false otherwise. | 25 | -- | Return true if the application should continue running, false otherwise. |
24 | type Step s = Elapsed -> Dt -> [InputEvent] -> Game s Bool | 26 | type Step s = Elapsed -> Dt -> [InputEvent] -> Game s Bool |
25 | 27 | ||
28 | -- | Application functions. | ||
29 | data App s = App | ||
30 | { stepApp :: Step s | ||
31 | , renderApp :: Game s () | ||
32 | , resizeApp :: WindowEvent -> Game s () | ||
33 | } | ||
34 | |||
26 | -- | Enter the main application loop. | 35 | -- | Enter the main application loop. |
27 | loop :: Step s -> Window -> Game s () | 36 | loop :: App s -> Window -> Game s () |
28 | loop step window = do | 37 | loop app window = do |
38 | -- For convenience, trigger an initial resize followed by a render of the | ||
39 | -- application's initial state. | ||
40 | (width, height) <- gameIO $ getWindowSize window | ||
41 | resizeApp app (ResizeEvent width height) | ||
42 | renderApp app | ||
43 | |||
29 | let ddt = 1.0 / fromIntegral maxFPS -- Desired delta time. | 44 | let ddt = 1.0 / fromIntegral maxFPS -- Desired delta time. |
30 | frameTimer <- gameIO $ start newTimer | 45 | timer <- gameIO newTimer |
31 | controlTimer <- gameIO $ start newTimer | 46 | gameIO $ Timer.start timer |
32 | loop' window ddt frameTimer controlTimer 0 step | 47 | loop' window ddt timer 0 0 app |
33 | return () | ||
34 | 48 | ||
35 | loop' :: | 49 | loop' :: |
36 | Window -> | 50 | Window -> |
37 | Dt -> | 51 | Dt -> |
38 | Timer -> | 52 | Timer -> |
39 | Timer -> | ||
40 | Elapsed -> | 53 | Elapsed -> |
41 | Step s -> | 54 | Double -> -- Time budget. |
55 | App s -> | ||
42 | Game s () | 56 | Game s () |
43 | loop' window ddt frameTimer controlTimer elapsed step = do | 57 | loop' window ddt inputTimer elapsed timeBudget app = do |
44 | controlTimer' <- gameIO $ tick controlTimer | 58 | timer <- gameIO $ tick inputTimer |
45 | frameTimer' <- gameIO $ tick frameTimer | 59 | |
46 | let dt = getDelta frameTimer' | 60 | (Events inputEvents windowEvents) <- gameIO $ pollEvents window |
47 | let elapsed' = elapsed + float2Double dt | 61 | |
48 | inputEvents <- gameIO $ pollEvents window | 62 | let timeBudgetThisFrame = timeBudget + timeDeltaToSec (deltaTime timer) |
49 | continue <- step elapsed' dt inputEvents | 63 | |
50 | gameIO $ swapBuffers window | 64 | let steps = floor (timeBudgetThisFrame / ddt) |
51 | close <- gameIO $ shouldWindowClose window | 65 | continue <- and <$> forM [1..steps] (\i -> |
52 | controlTimer'' <- gameIO $ tick controlTimer' | 66 | stepApp app (elapsed + fromIntegral i * ddt) ddt inputEvents) |
53 | let dt = getDelta controlTimer'' | 67 | |
54 | when (dt < ddt) $ gameIO $ Timer.sleep (ddt - dt) | 68 | let elapsed' = elapsed + fromIntegral steps * ddt |
55 | when (continue && not close) $ | 69 | let timeBudget' = timeBudgetThisFrame `mod'` ddt |
56 | loop' | 70 | |
57 | window | 71 | when continue $ do |
58 | ddt | 72 | forM_ windowEvents $ \event -> case event of |
59 | frameTimer' | 73 | ResizeEvent {} -> resizeApp app event |
60 | controlTimer'' | 74 | renderApp app |
61 | elapsed' | 75 | gameIO $ swapBuffers window |
62 | step | 76 | |
77 | -- TODO: Conversion of TimeDelta to/from double should be unnecessary here. | ||
78 | -- We ideally need ddt expressed in TimeDelta. | ||
79 | frameEnd <- gameIO now | ||
80 | let frameTime = timeDeltaToSec $ timeDiff (lastTick timer) frameEnd | ||
81 | when (frameTime < ddt) $ do | ||
82 | gameIO $ Timer.sleep (timeSecToDelta (ddt - frameTime)) | ||
83 | |||
84 | close <- gameIO $ shouldWindowClose window | ||
85 | when (continue && not close) $ | ||
86 | loop' | ||
87 | window | ||
88 | ddt | ||
89 | timer | ||
90 | elapsed' | ||
91 | timeBudget' | ||
92 | app | ||
diff --git a/Spear/Sys/Timer.hsc b/Spear/Sys/Timer.hsc index 85718ce..2c806d8 100644 --- a/Spear/Sys/Timer.hsc +++ b/Spear/Sys/Timer.hsc | |||
@@ -1,19 +1,22 @@ | |||
1 | {-# LANGUAGE CPP, ForeignFunctionInterface #-} | 1 | {-# LANGUAGE CPP #-} |
2 | {-# LANGUAGE ForeignFunctionInterface #-} | ||
3 | |||
2 | module Spear.Sys.Timer | 4 | module Spear.Sys.Timer |
3 | ( | 5 | ( |
4 | Timer | 6 | Timer(..) |
5 | , newTimer | 7 | , newTimer |
6 | , tick | ||
7 | , start | 8 | , start |
8 | , stop | 9 | , tick |
9 | , reset | 10 | , now |
10 | , getTime | 11 | , timeDiff |
11 | , getDelta | 12 | , timeDeltaToSec |
12 | , isRunning | 13 | , timeSecToDelta |
14 | , timePointToNs | ||
13 | , sleep | 15 | , sleep |
14 | ) | 16 | ) |
15 | where | 17 | where |
16 | 18 | ||
19 | import Data.Word | ||
17 | import Foreign.C.Types | 20 | import Foreign.C.Types |
18 | import Foreign.Marshal.Alloc (alloca) | 21 | import Foreign.Marshal.Alloc (alloca) |
19 | import Foreign.Ptr | 22 | import Foreign.Ptr |
@@ -21,130 +24,152 @@ import Foreign.Storable | |||
21 | import Control.Monad | 24 | import Control.Monad |
22 | import System.IO.Unsafe | 25 | import System.IO.Unsafe |
23 | 26 | ||
27 | #include "Timer/timer.h" | ||
28 | |||
29 | |||
24 | #ifdef WIN32 | 30 | #ifdef WIN32 |
25 | type TimeReading = CULLong | 31 | type TimePoint = Word64 |
26 | #else | 32 | #else |
27 | type TimeReading = CDouble | 33 | {- |
34 | struct timespec { | ||
35 | time_t tv_sec; /* seconds */ | ||
36 | long tv_nsec; /* nanoseconds */ | ||
37 | }; | ||
38 | -} | ||
39 | data TimeSpec = TimeSpec { | ||
40 | tvSec :: {-# UNPACK #-} !CTime, | ||
41 | tvNsec :: {-# UNPACK #-} !CLong | ||
42 | } | ||
43 | type TimePoint = TimeSpec | ||
28 | #endif | 44 | #endif |
29 | 45 | ||
46 | type TimeDelta = Word64 | ||
47 | |||
30 | data Timer = Timer | 48 | data Timer = Timer |
31 | { getBaseTime :: TimeReading | 49 | { startTime :: {-# UNPACK #-} !TimePoint |
32 | , getPausedTime :: TimeReading | 50 | , lastTick :: {-# UNPACK #-} !TimePoint |
33 | , getStopTime :: TimeReading | 51 | , runningTime :: {-# UNPACK #-} !TimeDelta |
34 | , getPrevTime :: TimeReading | 52 | , deltaTime :: {-# UNPACK #-} !TimeDelta |
35 | , getCurTime :: TimeReading | 53 | } |
36 | , getDeltaTime :: CFloat | 54 | |
37 | , getRunning :: CChar | 55 | |
38 | } | 56 | #ifndef WIN32 |
57 | instance Storable TimeSpec where | ||
58 | sizeOf _ = #{size struct timespec} | ||
59 | alignment _ = #{alignment struct timespec} | ||
60 | |||
61 | peek ptr = do | ||
62 | tvSec <- #{peek struct timespec, tv_sec} ptr | ||
63 | tvNsec <- #{peek struct timespec, tv_nsec} ptr | ||
64 | return $ TimeSpec tvSec tvNsec | ||
65 | |||
66 | poke ptr (TimeSpec tvSec tvNsec) = do | ||
67 | #{poke struct timespec, tv_sec} ptr tvSec | ||
68 | #{poke struct timespec, tv_nsec} ptr tvNsec | ||
69 | #endif | ||
39 | 70 | ||
40 | #include "Timer/Timer.h" | ||
41 | 71 | ||
42 | instance Storable Timer where | 72 | instance Storable Timer where |
43 | sizeOf _ = #{size Timer} | 73 | --sizeOf _ = #{size Timer} |
44 | alignment _ = alignment (undefined :: TimeReading) | 74 | sizeOf _ = #{size struct Timer} |
45 | 75 | --alignment _ = alignment (undefined :: Timer) | |
46 | peek ptr = do | 76 | alignment _ = #{alignment struct Timer} |
47 | baseTime <- #{peek Timer, baseTime} ptr | 77 | |
48 | pausedTime <- #{peek Timer, pausedTime} ptr | 78 | peek ptr = do |
49 | stopTime <- #{peek Timer, stopTime} ptr | 79 | startTime <- #{peek struct Timer, start_time} ptr |
50 | prevTime <- #{peek Timer, prevTime} ptr | 80 | lastTick <- #{peek struct Timer, last_tick} ptr |
51 | curTime <- #{peek Timer, curTime} ptr | 81 | runningTime <- #{peek struct Timer, running_time} ptr |
52 | deltaTime <- #{peek Timer, deltaTime} ptr | 82 | deltaTime <- #{peek struct Timer, delta_time} ptr |
53 | stopped <- #{peek Timer, stopped} ptr | 83 | return $ Timer startTime lastTick runningTime deltaTime |
54 | return $ Timer baseTime pausedTime stopTime prevTime curTime deltaTime stopped | 84 | |
55 | 85 | poke ptr (Timer startTime lastTick runningTime deltaTime) = do | |
56 | poke ptr (Timer baseTime pausedTime stopTime prevTime curTime deltaTime stopped) = do | 86 | #{poke struct Timer, start_time} ptr startTime |
57 | #{poke Timer, baseTime} ptr baseTime | 87 | #{poke struct Timer, last_tick} ptr lastTick |
58 | #{poke Timer, pausedTime} ptr pausedTime | 88 | #{poke struct Timer, running_time} ptr runningTime |
59 | #{poke Timer, stopTime} ptr stopTime | 89 | #{poke struct Timer, delta_time} ptr deltaTime |
60 | #{poke Timer, prevTime} ptr prevTime | ||
61 | #{poke Timer, curTime} ptr curTime | ||
62 | #{poke Timer, deltaTime} ptr deltaTime | ||
63 | #{poke Timer, stopped} ptr stopped | ||
64 | |||
65 | foreign import ccall unsafe "Timer.h timer_init" | ||
66 | c_timer_init :: Ptr Timer -> IO () | ||
67 | |||
68 | foreign import ccall unsafe "Timer.h timer_tick" | ||
69 | c_timer_tick :: Ptr Timer -> IO () | ||
70 | 90 | ||
71 | foreign import ccall unsafe "Timer.h timer_start" | 91 | |
92 | foreign import ccall unsafe "timer.h timer_make" | ||
93 | c_timer_make :: Ptr Timer -> IO () | ||
94 | |||
95 | foreign import ccall unsafe "timer.h timer_start" | ||
72 | c_timer_start :: Ptr Timer -> IO () | 96 | c_timer_start :: Ptr Timer -> IO () |
73 | 97 | ||
74 | foreign import ccall unsafe "Timer.h timer_stop" | 98 | foreign import ccall unsafe "timer.h timer_tick" |
75 | c_timer_stop :: Ptr Timer -> IO () | 99 | c_timer_tick :: Ptr Timer -> IO () |
76 | 100 | ||
77 | foreign import ccall unsafe "Timer.h timer_reset" | 101 | foreign import ccall unsafe "timer.h time_now" |
78 | c_timer_reset :: Ptr Timer -> IO () | 102 | c_time_now :: Ptr TimePoint -> IO () |
79 | 103 | ||
80 | foreign import ccall unsafe "Timer.h timer_get_time" | 104 | foreign import ccall safe "timer.h time_diff" |
81 | c_timer_get_time :: Ptr Timer -> IO (CDouble) | 105 | c_time_diff :: Ptr TimePoint -> Ptr TimePoint -> TimeDelta |
82 | 106 | ||
83 | foreign import ccall unsafe "Timer.h timer_get_delta" | 107 | foreign import ccall safe "timer.h time_delta_to_sec" |
84 | c_timer_get_delta :: Ptr Timer -> IO (CFloat) | 108 | c_time_delta_to_sec :: TimeDelta -> Double |
85 | 109 | ||
86 | foreign import ccall unsafe "Timer.h timer_is_running" | 110 | foreign import ccall safe "timer.h sec_to_time_delta" |
87 | c_timer_is_running :: Ptr Timer -> IO (CChar) | 111 | c_sec_to_time_delta :: Double -> TimeDelta |
88 | 112 | ||
89 | foreign import ccall "Timer.h timer_sleep" | 113 | foreign import ccall safe "timer.h time_point_to_ns" |
90 | c_timer_sleep :: CFloat -> IO () | 114 | c_time_point_to_ns :: Ptr TimePoint -> Word64 |
91 | 115 | ||
92 | -- | Construct a new timer. | 116 | foreign import ccall "timer.h time_sleep" |
93 | newTimer :: Timer | 117 | c_time_sleep :: TimeDelta -> IO () |
94 | newTimer = unsafePerformIO . unsafeInterleaveIO . alloca $ \tptr -> do | ||
95 | c_timer_init tptr | ||
96 | peek tptr | ||
97 | 118 | ||
98 | -- | Update the timer. | 119 | |
99 | tick :: Timer -> IO (Timer) | 120 | withTimer c_func timer = alloca $ \ptr -> do |
100 | tick t = alloca $ \tptr -> do | 121 | poke ptr timer |
101 | poke tptr t | 122 | c_func ptr |
102 | c_timer_tick tptr | 123 | |
103 | peek tptr | 124 | |
125 | withTimer' c_func timer = alloca $ \ptr -> do | ||
126 | poke ptr timer | ||
127 | c_func ptr | ||
128 | peek ptr | ||
129 | |||
130 | -- | Construct a new timer. | ||
131 | newTimer :: IO Timer | ||
132 | newTimer = alloca $ \ptr -> do | ||
133 | c_timer_make ptr | ||
134 | peek ptr | ||
104 | 135 | ||
105 | -- | Start the timer. | 136 | -- | Start the timer. |
106 | start :: Timer -> IO (Timer) | 137 | start :: Timer -> IO () |
107 | start t = alloca $ \tptr -> do | 138 | start = withTimer c_timer_start |
108 | poke tptr t | 139 | |
109 | c_timer_start tptr | 140 | -- | Update the timer. |
110 | t' <- peek tptr | 141 | tick :: Timer -> IO Timer |
111 | return t' | 142 | tick = withTimer' c_timer_tick |
112 | 143 | ||
113 | -- | Stop the timer. | 144 | -- | Get the current time. |
114 | stop :: Timer -> IO (Timer) | 145 | now :: IO TimePoint |
115 | stop t = alloca $ \tptr -> do | 146 | now = alloca $ \ptr -> do |
116 | poke tptr t | 147 | c_time_now ptr |
117 | c_timer_stop tptr | 148 | peek ptr |
118 | peek tptr | 149 | |
119 | 150 | -- | Get the time delta between two timepoints. | |
120 | -- | Reset the timer. | 151 | timeDiff :: TimePoint -> TimePoint -> TimeDelta |
121 | reset :: Timer -> IO (Timer) | 152 | timeDiff t1 t2 = unsafeDupablePerformIO $ |
122 | reset t = alloca $ \tptr -> do | 153 | alloca $ \ptr1 -> |
123 | poke tptr t | 154 | alloca $ \ptr2 -> do |
124 | c_timer_reset tptr | 155 | poke ptr1 t1 |
125 | peek tptr | 156 | poke ptr2 t2 |
126 | 157 | return $ c_time_diff ptr1 ptr2 | |
127 | -- | Get the timer's total running time. | 158 | |
128 | getTime :: Timer -> Double | 159 | -- | Get the time elapsed in seconds. |
129 | getTime t = unsafeDupablePerformIO . alloca $ \tptr -> do | 160 | timeDeltaToSec :: TimeDelta -> Double |
130 | poke tptr t | 161 | timeDeltaToSec = c_time_delta_to_sec |
131 | time <- c_timer_get_time tptr | 162 | |
132 | return (realToFrac time) | 163 | -- | Convert the time elapsed in seconds to a time delta. |
133 | 164 | timeSecToDelta :: Double -> TimeDelta | |
134 | -- | Get the time elapsed between the last two ticks. | 165 | timeSecToDelta = c_sec_to_time_delta |
135 | getDelta :: Timer -> Float | 166 | |
136 | getDelta t = unsafeDupablePerformIO . alloca $ \tptr -> do | 167 | -- | Convert the time point to nanoseconds. |
137 | poke tptr t | 168 | timePointToNs :: TimePoint -> Word64 |
138 | dt <- c_timer_get_delta tptr | 169 | timePointToNs t = unsafeDupablePerformIO $ alloca $ \ptr -> do |
139 | return (realToFrac dt) | 170 | poke ptr t |
140 | 171 | return $ c_time_point_to_ns ptr | |
141 | -- | Return true if the timer is running (not stopped), false otherwise. | 172 | |
142 | isRunning :: Timer -> Bool | 173 | -- | Put the caller thread to sleep for the given amount of time. |
143 | isRunning t = unsafeDupablePerformIO . alloca $ \tptr -> do | 174 | sleep :: TimeDelta -> IO () |
144 | poke tptr t | 175 | sleep = c_time_sleep |
145 | running <- c_timer_is_running tptr | ||
146 | return (running /= 0) | ||
147 | |||
148 | -- | Put the caller thread to sleep for the given number of seconds. | ||
149 | sleep :: Float -> IO () | ||
150 | sleep = c_timer_sleep . realToFrac | ||
diff --git a/Spear/Sys/Timer/Timer.h b/Spear/Sys/Timer/Timer.h deleted file mode 100644 index 308509c..0000000 --- a/Spear/Sys/Timer/Timer.h +++ /dev/null | |||
@@ -1,130 +0,0 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #ifdef WIN32 | ||
4 | #ifdef _MSC_VER | ||
5 | typedef __int64 timeReading; | ||
6 | #else | ||
7 | typedef __UINT64_TYPE__ timeReading; | ||
8 | #endif | ||
9 | #else | ||
10 | typedef __UINT64_TYPE__ timeReading; | ||
11 | #endif | ||
12 | |||
13 | #ifdef __cplusplus | ||
14 | extern "C" { | ||
15 | #endif | ||
16 | |||
17 | /* | ||
18 | Header: Timer | ||
19 | A high resolution timer module. | ||
20 | */ | ||
21 | |||
22 | /* | ||
23 | Struct: Timer | ||
24 | */ | ||
25 | typedef struct | ||
26 | { | ||
27 | timeReading baseTime; // The instant since we start timing. | ||
28 | timeReading stopTime; // The instant the timer is stopped. | ||
29 | timeReading prevTime; // The instant the timer was ticked prior to the last tick. | ||
30 | timeReading curTime; // The instant the timer was last ticked. | ||
31 | timeReading pausedTime; // Amount of time the timer has been stopped for. | ||
32 | float deltaTime; // Amount of time elapsed since the last call to tick. | ||
33 | char stopped; | ||
34 | } Timer; | ||
35 | |||
36 | /* | ||
37 | Function: timer_init | ||
38 | Construct a new timer. | ||
39 | |||
40 | The timer is initialised by making a call to reset(). Since time | ||
41 | calculations are measured from the instant the timer is reset (base time), | ||
42 | you probably want to make a manual call to reset() at the start of | ||
43 | your application, otherwise the application will be measuring times | ||
44 | from the instant the timer's constructor is called, which can be error prone. | ||
45 | |||
46 | A call to start() must be made prior to any time calculations, as the | ||
47 | timer is initialised as stopped. | ||
48 | */ | ||
49 | void timer_init (Timer*); | ||
50 | |||
51 | /* | ||
52 | Function: timer_tick | ||
53 | Update the timer's values. | ||
54 | |||
55 | This function updates the timer's running time and caches the time | ||
56 | elapsed since the last tick or since the start if this is the first | ||
57 | tick after the last call to start(). | ||
58 | |||
59 | This function has no effect on a stopped ticker. | ||
60 | */ | ||
61 | void timer_tick (Timer*); | ||
62 | |||
63 | /* | ||
64 | Function: timer_start | ||
65 | Start the timer. | ||
66 | |||
67 | This function starts the timer for the first time or resumes it | ||
68 | after a call to stop(). | ||
69 | |||
70 | Note that this function does not reset the timer's base time; | ||
71 | it's only a mechanism to resume a stopped timer. | ||
72 | */ | ||
73 | void timer_start (Timer*); | ||
74 | |||
75 | /* | ||
76 | Function: timer_stop | ||
77 | Stop the timer. | ||
78 | |||
79 | This function essentially freezes time; any values dependent on | ||
80 | the timer will behave as if time had not passed since the moment | ||
81 | the timer was stopped. | ||
82 | |||
83 | To resume the timer call start(). | ||
84 | */ | ||
85 | void timer_stop (Timer*); | ||
86 | |||
87 | /* | ||
88 | Function: timer_reset | ||
89 | Reset the timer. | ||
90 | |||
91 | This function resets all of the timer's values such as running and | ||
92 | stop times and sets the timer to stopped. The total running time is | ||
93 | then measured from the instant the timer is reset, making the timer | ||
94 | behave as a newly constructed one. | ||
95 | |||
96 | A call to start() must be made prior to any further time calculations. | ||
97 | */ | ||
98 | void timer_reset (Timer*); | ||
99 | |||
100 | /* | ||
101 | Function: timer_get_time | ||
102 | Get the total running time. | ||
103 | |||
104 | The amount of time the timer has been stopped for is not taken | ||
105 | into account. | ||
106 | */ | ||
107 | double timer_get_time (const Timer*); | ||
108 | |||
109 | /* | ||
110 | Function: timer_get_delta | ||
111 | Get the time elapsed since the last tick, or since the start if | ||
112 | this is the first tick. | ||
113 | */ | ||
114 | float timer_get_delta (const Timer*); | ||
115 | |||
116 | /* | ||
117 | Function: timer_is_running | ||
118 | Return true if the timer is running (not stopped), false otherwise. | ||
119 | */ | ||
120 | char timer_is_running (const Timer*); | ||
121 | |||
122 | /* | ||
123 | Function: timer_sleep | ||
124 | Put the caller thread to sleep for the given number of seconds. | ||
125 | */ | ||
126 | void timer_sleep (float seconds); | ||
127 | |||
128 | #ifdef __cplusplus | ||
129 | } | ||
130 | #endif | ||
diff --git a/Spear/Sys/Timer/ctimer.c b/Spear/Sys/Timer/ctimer.c deleted file mode 100644 index 8c059c0..0000000 --- a/Spear/Sys/Timer/ctimer.c +++ /dev/null | |||
@@ -1,157 +0,0 @@ | |||
1 | #include "Timer.h" | ||
2 | #include <stdlib.h> | ||
3 | |||
4 | #ifdef __APPLE__ | ||
5 | #include <mach/mach_time.h> | ||
6 | #elif WIN32 | ||
7 | #define WIN32_LEAN_AND_MEAN | ||
8 | #include <Windows.h> | ||
9 | #else // Linux | ||
10 | #include <time.h> | ||
11 | const double NSEC_TO_SEC = 1.0 / 1000000000.0; | ||
12 | const double SEC_TO_NSECd = 1000000000.0; | ||
13 | const timeReading SEC_TO_NSEC = 1000000000; | ||
14 | #endif | ||
15 | |||
16 | static double secondsPerCount; | ||
17 | |||
18 | static void timer_initialise_subsystem () | ||
19 | { | ||
20 | #ifdef WIN32 | ||
21 | __int64 countsPerSec; | ||
22 | QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec); | ||
23 | secondsPerCount = 1.0 / (double)countsPerSec; | ||
24 | #else | ||
25 | struct timespec ts; | ||
26 | clock_getres(CLOCK_REALTIME, &ts); | ||
27 | secondsPerCount = (double)ts.tv_sec + ((double)ts.tv_nsec * NSEC_TO_SEC); | ||
28 | #endif | ||
29 | } | ||
30 | |||
31 | static timeReading now () | ||
32 | { | ||
33 | timeReading t; | ||
34 | #ifdef __APPLE__ | ||
35 | t = mach_absolute_time(); | ||
36 | #elif WIN32 | ||
37 | QueryPerformanceCounter((LARGE_INTEGER*)&t); | ||
38 | #else | ||
39 | struct timespec ts; | ||
40 | clock_gettime(CLOCK_REALTIME, &ts); | ||
41 | t = ts.tv_sec*SEC_TO_NSEC + ts.tv_nsec; | ||
42 | #endif | ||
43 | return t; | ||
44 | } | ||
45 | |||
46 | void timer_init (Timer* timer) | ||
47 | { | ||
48 | timer_initialise_subsystem(); | ||
49 | timer_reset (timer); | ||
50 | } | ||
51 | |||
52 | void timer_tick (Timer* timer) | ||
53 | { | ||
54 | if (timer->stopped) | ||
55 | { | ||
56 | timer->deltaTime = 0.0; | ||
57 | return; | ||
58 | } | ||
59 | |||
60 | //Get the time on this frame. | ||
61 | timer->curTime = now(); | ||
62 | |||
63 | //Time delta between the current frame and the previous. | ||
64 | timer->deltaTime = (float) ((timer->curTime - timer->prevTime) * secondsPerCount); | ||
65 | |||
66 | //Update for next frame. | ||
67 | timer->prevTime = timer->curTime; | ||
68 | |||
69 | // Force nonnegative. The DXSDK's CDXUTTimer mentions that if the | ||
70 | // processor goes into a power save mode or we get shuffled to | ||
71 | // another processor, then the delta time can be negative. | ||
72 | if(timer->deltaTime < 0.0f) | ||
73 | { | ||
74 | timer->deltaTime = 0.0f; | ||
75 | } | ||
76 | } | ||
77 | |||
78 | void timer_reset (Timer* timer) | ||
79 | { | ||
80 | timeReading n = now(); | ||
81 | timer->baseTime = n; | ||
82 | timer->stopTime = n; | ||
83 | timer->prevTime = n; | ||
84 | timer->curTime = n; | ||
85 | timer->pausedTime = 0; | ||
86 | timer->deltaTime = 0.0f; | ||
87 | timer->stopped = 1; | ||
88 | } | ||
89 | |||
90 | void timer_stop (Timer* timer) | ||
91 | { | ||
92 | // Don't do anything if we are already stopped. | ||
93 | if (!timer->stopped) | ||
94 | { | ||
95 | // Grab the stop time. | ||
96 | timer->stopTime = now(); | ||
97 | |||
98 | // Now we are stopped. | ||
99 | timer->stopped = 1; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | void timer_start (Timer* timer) | ||
104 | { | ||
105 | // Only start if we are stopped. | ||
106 | if (timer->stopped) | ||
107 | { | ||
108 | timeReading startTime = now(); | ||
109 | |||
110 | // Accumulate the paused time. | ||
111 | timer->pausedTime = timer->pausedTime + startTime - timer->stopTime; | ||
112 | |||
113 | // Make the previous time valid. | ||
114 | timer->prevTime = startTime; | ||
115 | |||
116 | //Now we are running. | ||
117 | timer->stopTime = 0; | ||
118 | timer->stopped = 0; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | double timer_get_time (const Timer* timer) | ||
123 | { | ||
124 | // If we are stopped, we do not count the time we have been stopped for. | ||
125 | if (timer->stopped) | ||
126 | { | ||
127 | return (double)((timer->stopTime - timer->baseTime) * secondsPerCount); | ||
128 | } | ||
129 | // Otherwise return the time elapsed since the start but without | ||
130 | // taking into account the time we have been stopped for. | ||
131 | else | ||
132 | { | ||
133 | return (double)((timer->curTime - timer->baseTime - timer->pausedTime) * secondsPerCount); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | float timer_get_delta (const Timer* timer) | ||
138 | { | ||
139 | return timer->deltaTime; | ||
140 | } | ||
141 | |||
142 | char timer_is_running (const Timer* timer) | ||
143 | { | ||
144 | return !timer->stopped; | ||
145 | } | ||
146 | |||
147 | void timer_sleep (float seconds) | ||
148 | { | ||
149 | #ifdef WIN32 | ||
150 | Sleep((DWORD)(seconds * 1000)); | ||
151 | #else | ||
152 | struct timespec ts; | ||
153 | ts.tv_sec = (int) seconds; | ||
154 | ts.tv_nsec = (long) ((double)(seconds - (int)seconds) * SEC_TO_NSECd); | ||
155 | nanosleep(&ts, NULL); | ||
156 | #endif | ||
157 | } | ||
diff --git a/Spear/Sys/Timer/timer.c b/Spear/Sys/Timer/timer.c new file mode 100644 index 0000000..8487f48 --- /dev/null +++ b/Spear/Sys/Timer/timer.c | |||
@@ -0,0 +1,101 @@ | |||
1 | #include "timer.h" | ||
2 | |||
3 | #include <stdlib.h> | ||
4 | #ifdef _WIN32 | ||
5 | #define WIN32_LEAN_AND_MEAN | ||
6 | #include <Windows.h> | ||
7 | #endif | ||
8 | |||
9 | #ifdef _WIN32 | ||
10 | static const int64_t microseconds = 1000000; | ||
11 | #endif | ||
12 | static const int64_t nanoseconds = 1000000000; | ||
13 | |||
14 | #ifdef _WIN32 | ||
15 | static double seconds_per_count; | ||
16 | #endif | ||
17 | |||
18 | static void timer_initialise() { | ||
19 | #ifdef _WIN32 | ||
20 | __int64 counts_per_sec; | ||
21 | QueryPerformanceFrequency((LARGE_INTEGER*)&counts_per_sec); | ||
22 | seconds_per_count = 1.0 / (double)counts_per_sec; | ||
23 | #endif | ||
24 | } | ||
25 | |||
26 | void timer_make(Timer* timer) { | ||
27 | timer_initialise(); | ||
28 | *timer = (Timer){0}; | ||
29 | timer_start(timer); | ||
30 | } | ||
31 | |||
32 | void timer_start(Timer* timer) { | ||
33 | time_now(&timer->start_time); | ||
34 | timer->last_tick = timer->start_time; | ||
35 | timer->running_time = 0; | ||
36 | timer->delta_time = 0; | ||
37 | } | ||
38 | |||
39 | void timer_tick(Timer* timer) { | ||
40 | time_point this_tick; | ||
41 | time_now(&this_tick); | ||
42 | timer->running_time = time_diff(&timer->start_time, &this_tick); | ||
43 | timer->delta_time = time_diff(&timer->last_tick, &this_tick); | ||
44 | timer->last_tick = this_tick; | ||
45 | } | ||
46 | |||
47 | void time_now(time_point* t) { | ||
48 | #ifdef _WIN32 | ||
49 | QueryPerformanceCounter((LARGE_INTEGER*)t); | ||
50 | #else | ||
51 | clock_gettime(CLOCK_REALTIME, t); | ||
52 | #endif | ||
53 | } | ||
54 | |||
55 | time_delta time_diff(time_point* start, time_point* end) { | ||
56 | #ifdef _WIN32 | ||
57 | // Force nonnegative. The DXSDK's CDXUTTimer mentions that if the | ||
58 | // processor goes into a power save mode or we get shuffled to | ||
59 | // another processor, then the delta time can be negative. | ||
60 | return std::max(0, *end - *start); | ||
61 | #else | ||
62 | return (end->tv_sec - start->tv_sec) * 1e9 + (end->tv_nsec - start->tv_nsec); | ||
63 | #endif | ||
64 | } | ||
65 | |||
66 | double time_delta_to_sec(time_delta dt) { | ||
67 | #ifdef _WIN32 | ||
68 | return (double)dt * seconds_per_count; | ||
69 | #else | ||
70 | return (double)dt * 1.0e-9; | ||
71 | #endif | ||
72 | } | ||
73 | |||
74 | time_delta sec_to_time_delta(double seconds) { | ||
75 | #ifdef _WIN32 | ||
76 | return (time_delta)(seconds / seconds_per_count); | ||
77 | #else | ||
78 | return (time_delta)(seconds * 1.0e9); | ||
79 | #endif | ||
80 | } | ||
81 | |||
82 | uint64_t time_point_to_ns(time_point* t) { | ||
83 | #ifdef _WIN32 | ||
84 | return (uint64_t)((double)*t * seconds_per_count * 1.0e+9); | ||
85 | #else | ||
86 | return (uint64_t)t->tv_sec * 1e+9 + (uint64_t)t->tv_nsec; | ||
87 | #endif | ||
88 | } | ||
89 | |||
90 | void time_sleep(time_delta dt) { | ||
91 | #ifdef _WIN32 | ||
92 | const int64_t ms = dt / microseconds; | ||
93 | Sleep((DWORD)(ms)); | ||
94 | #else | ||
95 | const int64_t sec = dt / nanoseconds; | ||
96 | struct timespec ts; | ||
97 | ts.tv_sec = (long)sec; | ||
98 | ts.tv_nsec = (long)(dt % nanoseconds); | ||
99 | nanosleep(&ts, NULL); | ||
100 | #endif | ||
101 | } | ||
diff --git a/Spear/Sys/Timer/timer.h b/Spear/Sys/Timer/timer.h new file mode 100644 index 0000000..e426135 --- /dev/null +++ b/Spear/Sys/Timer/timer.h | |||
@@ -0,0 +1,64 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include <stdint.h> | ||
4 | |||
5 | /// A particular point in time. | ||
6 | #ifdef _WIN32 | ||
7 | typedef uint64_t time_point; | ||
8 | #else | ||
9 | // Need to macro to make CLOCK_REALTIME available when compiling with ISO C11. | ||
10 | // The constant is only needed in the source file, but the header file needs to | ||
11 | // include time.h too. | ||
12 | #ifndef __USE_POSIX199309 | ||
13 | #define __USE_POSIX199309 | ||
14 | #endif // | ||
15 | #include <time.h> | ||
16 | typedef struct timespec time_point; | ||
17 | #endif | ||
18 | |||
19 | /// Time elapsed between two time points. | ||
20 | typedef uint64_t time_delta; | ||
21 | |||
22 | /// A high resolution timer. | ||
23 | typedef struct Timer { | ||
24 | time_point start_time; // The instant the timer was last started. | ||
25 | time_point last_tick; // The instant the timer was last ticked. | ||
26 | time_delta running_time; // Time elapsed since the timer was last started. | ||
27 | time_delta delta_time; // Time elapsed since the last tick. | ||
28 | } Timer; | ||
29 | |||
30 | /// Construct a new timer. | ||
31 | void timer_make(Timer*); | ||
32 | |||
33 | /// Start the timer. | ||
34 | /// This sets the time point from which time deltas are measured. | ||
35 | /// Calling this multilple times resets the timer. | ||
36 | void timer_start(Timer*); | ||
37 | |||
38 | /// Update the timer's running and delta times. | ||
39 | void timer_tick(Timer*); | ||
40 | |||
41 | /// Get the current time. | ||
42 | void time_now(time_point*); | ||
43 | |||
44 | /// Return the time elapsed between two timestamps. | ||
45 | time_delta time_diff(time_point* start, time_point* end); | ||
46 | |||
47 | /// Return the time elapsed in seconds. | ||
48 | double time_delta_to_sec(time_delta dt); | ||
49 | |||
50 | /// Convert the time elapsed in seconds to a time delta. | ||
51 | time_delta sec_to_time_delta(double seconds); | ||
52 | |||
53 | /// Convert the time point to nanoseconds. | ||
54 | uint64_t time_point_to_ns(time_point*); | ||
55 | |||
56 | /// Put the caller thread to sleep for the given amount of time. | ||
57 | void time_sleep(time_delta dt); | ||
58 | |||
59 | /// The time point 0. | ||
60 | #ifdef _WIN32 | ||
61 | static const time_point time_zero = 0; | ||
62 | #else | ||
63 | static const time_point time_zero = {0, 0}; | ||
64 | #endif | ||
diff --git a/Spear/Window.hs b/Spear/Window.hs index 336910b..b130f5c 100644 --- a/Spear/Window.hs +++ b/Spear/Window.hs | |||
@@ -9,10 +9,13 @@ module Spear.Window | |||
9 | Width, | 9 | Width, |
10 | Height, | 10 | Height, |
11 | Init, | 11 | Init, |
12 | WindowEvent(..), | ||
13 | Events(..), | ||
12 | withWindow, | 14 | withWindow, |
13 | pollEvents, | 15 | pollEvents, |
14 | shouldWindowClose, | 16 | shouldWindowClose, |
15 | swapBuffers, | 17 | swapBuffers, |
18 | getWindowSize, | ||
16 | 19 | ||
17 | -- * Input | 20 | -- * Input |
18 | whenKeyDown, | 21 | whenKeyDown, |
@@ -48,8 +51,6 @@ type Context = (Int, Int) | |||
48 | 51 | ||
49 | type WindowTitle = String | 52 | type WindowTitle = String |
50 | 53 | ||
51 | type CloseRequest = MVar Bool | ||
52 | |||
53 | -- | Game initialiser. | 54 | -- | Game initialiser. |
54 | type Init s = Window -> Game () s | 55 | type Init s = Window -> Game () s |
55 | 56 | ||
@@ -58,11 +59,28 @@ newtype WindowException = WindowException String deriving (Show) | |||
58 | 59 | ||
59 | instance Exception WindowException | 60 | instance Exception WindowException |
60 | 61 | ||
62 | data WindowEvent | ||
63 | = ResizeEvent Width Height | ||
64 | |||
65 | data InputEvent | ||
66 | = KeyDown Key | ||
67 | | KeyUp Key | ||
68 | | MouseDown MouseButton | ||
69 | | MouseUp MouseButton | ||
70 | | MouseMove MousePos MouseDelta | ||
71 | deriving (Eq, Show) | ||
72 | |||
73 | data Events = Events | ||
74 | { inputEvents :: [InputEvent] | ||
75 | , windowEvents :: [WindowEvent] | ||
76 | } | ||
77 | |||
61 | -- | A window. | 78 | -- | A window. |
62 | data Window = Window | 79 | data Window = Window |
63 | { glfwWindow :: GLFW.Window, | 80 | { glfwWindow :: GLFW.Window |
64 | closeRequest :: CloseRequest, | 81 | , closeRequestMVar :: MVar Bool |
65 | inputEvents :: MVar [InputEvent] | 82 | , inputEventsMVar :: MVar [InputEvent] |
83 | , windowEventsMVar :: MVar [WindowEvent] | ||
66 | } | 84 | } |
67 | 85 | ||
68 | withWindow :: | 86 | withWindow :: |
@@ -93,58 +111,71 @@ setup :: | |||
93 | IO Window | 111 | IO Window |
94 | setup (w, h) (major, minor) windowTitle = do | 112 | setup (w, h) (major, minor) windowTitle = do |
95 | closeRequest <- newEmptyMVar | 113 | closeRequest <- newEmptyMVar |
96 | inputEvents <- newEmptyMVar | 114 | windowEvents <- newEmptyMVar |
97 | let onResize' = onResize inputEvents | 115 | inputEvents <- newEmptyMVar |
98 | let title = fromMaybe "" windowTitle | 116 | mousePos <- newEmptyMVar -- To compute deltas between mouse positions. |
99 | let monitor = Nothing | 117 | |
100 | maybeWindow <- do | 118 | maybeWindow <- do |
101 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMajor major | 119 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMajor major |
102 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMinor minor | 120 | GLFW.windowHint $ GLFW.WindowHint'ContextVersionMinor minor |
103 | when (major >= 3) $ GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Compat | 121 | when (major >= 3) $ GLFW.windowHint $ GLFW.WindowHint'OpenGLProfile GLFW.OpenGLProfile'Compat |
104 | GLFW.createWindow w h title monitor Nothing | 122 | GLFW.createWindow w h (fromMaybe "Spear" windowTitle) Nothing Nothing |
123 | |||
105 | unless (isJust maybeWindow) $ throwIO (WindowException "GLFW.openWindow failed") | 124 | unless (isJust maybeWindow) $ throwIO (WindowException "GLFW.openWindow failed") |
106 | let window = fromJust maybeWindow | 125 | let window = fromJust maybeWindow |
126 | |||
107 | GLFW.setWindowCloseCallback window . Just $ onWindowClose closeRequest | 127 | GLFW.setWindowCloseCallback window . Just $ onWindowClose closeRequest |
108 | GLFW.setWindowSizeCallback window . Just $ onResize' | 128 | GLFW.setWindowSizeCallback window . Just $ onResize windowEvents |
109 | GLFW.setKeyCallback window . Just $ onKey inputEvents | 129 | GLFW.setKeyCallback window . Just $ onKey inputEvents |
110 | GLFW.setCharCallback window . Just $ onChar inputEvents | 130 | GLFW.setCharCallback window . Just $ onChar inputEvents |
111 | GLFW.setMouseButtonCallback window . Just $ onMouseButton inputEvents | 131 | GLFW.setMouseButtonCallback window . Just $ onMouseButton inputEvents |
112 | onMouseMove inputEvents >>= GLFW.setCursorPosCallback window . Just | 132 | GLFW.setCursorPosCallback window . Just $ onMouseMove mousePos inputEvents |
113 | onResize' window w h | 133 | |
114 | return $ Spear.Window.Window window closeRequest inputEvents | 134 | return $ Window window closeRequest inputEvents windowEvents |
115 | 135 | ||
116 | -- | Poll the window's events. | 136 | -- | Poll the window's events. |
117 | pollEvents :: Window -> IO [InputEvent] | 137 | pollEvents :: Window -> IO Events |
118 | pollEvents window = do | 138 | pollEvents window = do |
119 | GLFW.pollEvents | 139 | GLFW.pollEvents |
120 | events <- | 140 | inputEvents <- getEvents (inputEventsMVar window) |
121 | tryTakeMVar (inputEvents window) >>= \xs -> case xs of | 141 | windowEvents <- getEvents (windowEventsMVar window) |
122 | Nothing -> return [] | 142 | return (Events inputEvents windowEvents) |
123 | Just events -> return events | 143 | |
124 | putMVar (inputEvents window) [] | 144 | getEvents :: MVar [a] -> IO [a] |
125 | return events | 145 | getEvents mvar = tryTakeMVar mvar >>= \xs -> do |
146 | putMVar mvar [] -- Clear the events. | ||
147 | case xs of | ||
148 | Nothing -> return [] | ||
149 | Just events -> return events | ||
126 | 150 | ||
127 | -- | Return true when the user requests to close the window. | 151 | -- | Return true when the user requests to close the window. |
128 | shouldWindowClose :: Window -> IO Bool | 152 | shouldWindowClose :: Window -> IO Bool |
129 | shouldWindowClose = getRequest . closeRequest | 153 | shouldWindowClose = getRequest . closeRequestMVar |
130 | 154 | ||
131 | -- | Swaps buffers. | 155 | -- | Swaps buffers. |
132 | swapBuffers :: Window -> IO () | 156 | swapBuffers :: Window -> IO () |
133 | swapBuffers = GLFW.swapBuffers . glfwWindow | 157 | swapBuffers = GLFW.swapBuffers . glfwWindow |
134 | 158 | ||
159 | -- | Get the window's size. | ||
160 | getWindowSize :: Window -> IO (Width, Height) | ||
161 | getWindowSize = GLFW.getWindowSize . glfwWindow | ||
162 | |||
135 | getRequest :: MVar Bool -> IO Bool | 163 | getRequest :: MVar Bool -> IO Bool |
136 | getRequest mvar = | 164 | getRequest mvar = |
137 | tryTakeMVar mvar >>= \x -> return $ fromMaybe False x | 165 | tryTakeMVar mvar >>= \x -> return $ fromMaybe False x |
138 | 166 | ||
139 | onWindowClose :: MVar Bool -> GLFW.WindowCloseCallback | 167 | onWindowClose :: MVar Bool -> GLFW.WindowCloseCallback |
140 | onWindowClose closeRequest window = do putMVar closeRequest True | 168 | onWindowClose closeRequest window = putMVar closeRequest True |
141 | 169 | ||
142 | onResize :: MVar [InputEvent] -> GLFW.WindowSizeCallback | 170 | -- Since the only WindowEvent right now is ResizeEvent, and all ResizeEvents but |
143 | onResize events window w h = addEvent events $ Resize w h | 171 | -- the last in a poll can be ignored, we just replace the contents of the mvar |
172 | -- here instead of adding the event to the list. | ||
173 | onResize :: MVar [WindowEvent] -> GLFW.WindowSizeCallback | ||
174 | onResize windowEvents window w h = putMVar windowEvents [ResizeEvent w h] | ||
144 | 175 | ||
145 | onKey :: MVar [InputEvent] -> GLFW.KeyCallback | 176 | onKey :: MVar [InputEvent] -> GLFW.KeyCallback |
146 | onKey events window key _ GLFW.KeyState'Pressed _ = addEvent events $ KeyDown (fromGLFWkey key) | 177 | onKey events window key _ GLFW.KeyState'Pressed _ = addEvent events $ KeyDown (fromGLFWkey key) |
147 | onKey events window key _ GLFW.KeyState'Released _ = addEvent events $ KeyUp (fromGLFWkey key) | 178 | onKey events window key _ GLFW.KeyState'Released _ = addEvent events $ KeyUp (fromGLFWkey key) |
148 | onKey events window key _ GLFW.KeyState'Repeating _ = return () | 179 | onKey events window key _ GLFW.KeyState'Repeating _ = return () |
149 | 180 | ||
150 | onChar :: MVar [InputEvent] -> GLFW.CharCallback | 181 | onChar :: MVar [InputEvent] -> GLFW.CharCallback |
@@ -154,11 +185,8 @@ onMouseButton :: MVar [InputEvent] -> GLFW.MouseButtonCallback | |||
154 | onMouseButton events window button GLFW.MouseButtonState'Pressed _ = addEvent events $ MouseDown (fromGLFWbutton button) | 185 | onMouseButton events window button GLFW.MouseButtonState'Pressed _ = addEvent events $ MouseDown (fromGLFWbutton button) |
155 | onMouseButton events window button GLFW.MouseButtonState'Released _ = addEvent events $ MouseUp (fromGLFWbutton button) | 186 | onMouseButton events window button GLFW.MouseButtonState'Released _ = addEvent events $ MouseUp (fromGLFWbutton button) |
156 | 187 | ||
157 | onMouseMove :: MVar [InputEvent] -> IO GLFW.CursorPosCallback | 188 | onMouseMove :: MVar MousePos -> MVar [InputEvent] -> GLFW.CursorPosCallback |
158 | onMouseMove events = newEmptyMVar <&> flip onMouseMove' events | 189 | onMouseMove oldPos events window x y = do |
159 | |||
160 | onMouseMove' :: MVar MousePos -> MVar [InputEvent] -> GLFW.CursorPosCallback | ||
161 | onMouseMove' oldPos events window x y = do | ||
162 | (old_x, old_y) <- | 190 | (old_x, old_y) <- |
163 | tryTakeMVar oldPos >>= \old -> case old of | 191 | tryTakeMVar oldPos >>= \old -> case old of |
164 | Nothing -> return (x, y) | 192 | Nothing -> return (x, y) |
@@ -215,15 +243,6 @@ processButtons window = foldM f [] | |||
215 | button | 243 | button |
216 | return $ if isDown then result : acc else acc | 244 | return $ if isDown then result : acc else acc |
217 | 245 | ||
218 | data InputEvent | ||
219 | = Resize Width Height | ||
220 | | KeyDown Key | ||
221 | | KeyUp Key | ||
222 | | MouseDown MouseButton | ||
223 | | MouseUp MouseButton | ||
224 | | MouseMove MousePos MouseDelta | ||
225 | deriving (Eq, Show) | ||
226 | |||
227 | data Key | 246 | data Key |
228 | = KEY_A | 247 | = KEY_A |
229 | | KEY_B | 248 | | KEY_B |