affection 0.0.0.7 → 0.0.0.9
raw patch · 26 files changed
+960/−630 lines, 26 filesdep +deepseqdep +matrixdep +nanovgdep ~sdl2new-component:exe:example01
Dependencies added: deepseq, matrix, nanovg, random, vector
Dependency ranges changed: sdl2
Files
- affection.cabal +29/−117
- examples/example00.hs +0/−138
- examples/example00/Main.hs +185/−0
- examples/example01/Main.hs +251/−0
- examples/example01/Types.hs +78/−0
- src/Affection.hs +15/−45
- src/Affection/Logging.hs +18/−6
- src/Affection/MessageBus.hs +0/−1
- src/Affection/MessageBus/Class.hs +10/−10
- src/Affection/MessageBus/Message.hs +1/−0
- src/Affection/MessageBus/Message/Class.hs +2/−2
- src/Affection/MessageBus/Message/JoystickMessage.hs +67/−0
- src/Affection/MessageBus/Message/KeyboardMessage.hs +14/−7
- src/Affection/MessageBus/Message/MouseMessage.hs +34/−22
- src/Affection/MessageBus/Message/WindowMessage.hs +53/−36
- src/Affection/MessageBus/Util.hs +0/−3
- src/Affection/MouseInteractable.hs +0/−42
- src/Affection/StateMachine.hs +6/−0
- src/Affection/Subsystems.hs +1/−0
- src/Affection/Subsystems/AffectionJoystick.hs +111/−0
- src/Affection/Subsystems/AffectionKeyboard.hs +8/−8
- src/Affection/Subsystems/AffectionMouse.hs +12/−8
- src/Affection/Subsystems/AffectionWindow.hs +15/−10
- src/Affection/Subsystems/Class.hs +3/−1
- src/Affection/Types.hs +17/−163
- src/Affection/Util.hs +30/−11
affection.cabal view
@@ -6,7 +6,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.0.0.7+version: 0.0.0.9 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL.@@ -68,21 +68,21 @@ , Affection.Logging , Affection.Types , Affection.StateMachine- , Affection.MouseInteractable , Affection.Util , Affection.MessageBus- , Affection.MessageBus.Util , Affection.MessageBus.Class , Affection.MessageBus.Message , Affection.MessageBus.Message.Class , Affection.MessageBus.Message.WindowMessage , Affection.MessageBus.Message.KeyboardMessage , Affection.MessageBus.Message.MouseMessage+ , Affection.MessageBus.Message.JoystickMessage , Affection.Subsystems , Affection.Subsystems.Class , Affection.Subsystems.AffectionWindow , Affection.Subsystems.AffectionKeyboard , Affection.Subsystems.AffectionMouse+ , Affection.Subsystems.AffectionJoystick default-extensions: OverloadedStrings -- Modules included in this library but not exported.@@ -99,7 +99,7 @@ ghc-options: -Wall -- Other library packages from which modules are imported. build-depends: base >=4.9 && < 5- , sdl2+ , sdl2 >= 2.4 && < 2.5 , linear , text , mtl@@ -112,12 +112,13 @@ , OpenGL , stm , uuid+ , vector -- This example shows the message system. only makes sense when compiling with -- verbose flag. executable example00- hs-source-dirs: examples- main-is: example00.hs+ hs-source-dirs: examples/example00+ main-is: Main.hs ghc-options: -threaded -Wall default-language: Haskell2010 default-extensions: OverloadedStrings@@ -129,114 +130,25 @@ else buildable: False --- executable example01--- hs-source-dirs: examples--- main-is: example01.hs--- ghc-options: -threaded -Wall--- default-language: Haskell2010--- default-extensions: OverloadedStrings--- if flag(examples)--- build-depends: base--- , affection--- , sdl2--- , gegl--- , babl--- , containers--- , mtl--- else--- buildable: False--- --- executable example02--- hs-source-dirs: examples--- main-is: example02.hs--- ghc-options: -threaded -Wall--- default-language: Haskell2010--- default-extensions: OverloadedStrings--- if flag(examples)--- build-depends: base--- , affection--- , sdl2--- , gegl--- , babl--- , containers--- , mtl--- , monad-parallel--- else--- buildable: False--- --- executable example02.1--- hs-source-dirs: examples--- main-is: example02.1.hs--- ghc-options: -threaded -Wall--- default-language: Haskell2010--- default-extensions: OverloadedStrings--- if flag(examples)--- build-depends: base--- , affection--- , sdl2--- , gegl--- , babl--- , containers--- , mtl--- else--- buildable: False--- --- executable example03--- hs-source-dirs: examples--- main-is: example03.hs--- ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts--- default-language: Haskell2010--- default-extensions: OverloadedStrings--- if flag(examples)--- build-depends: base--- , affection--- , sdl2--- , gegl--- , babl--- , containers--- , mtl--- , random--- else--- buildable: False--- --- executable example04--- hs-source-dirs: examples--- main-is: example04.hs--- ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts--- default-language: Haskell2010--- default-extensions: OverloadedStrings--- if flag(examples)--- build-depends: base--- , affection--- , sdl2--- , gegl--- , babl--- , containers--- , mtl--- , random--- , monad-parallel--- else--- buildable: False--- --- executable example05--- hs-source-dirs: examples--- main-is: example05.hs--- ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts--- default-language: Haskell2010--- default-extensions: OverloadedStrings--- if flag(examples)--- build-depends: base--- , affection--- , sdl2--- , gegl--- , babl--- , containers--- , unordered-containers--- , mtl--- , random--- , matrix--- , random--- , monad-parallel--- , parallel--- else--- buildable: False+-- A small game of life implementation+executable example01+ hs-source-dirs: examples/example01+ main-is: Main.hs+ other-modules: Types+ ghc-options: -threaded -Wall+ default-language: Haskell2010+ default-extensions: OverloadedStrings+ if flag(examples)+ build-depends: base >=4.9 && < 5+ , affection+ , sdl2+ , stm+ , OpenGL+ , random+ , containers+ , linear+ , matrix+ , nanovg >= 0.6.0.0+ , deepseq+ else+ buildable: False
− examples/example00.hs
@@ -1,138 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}--import Affection-import qualified SDL--import Control.Concurrent.STM-import Control.Monad.IO.Class (liftIO)--newtype StateData = StateData- { sdSubs :: Subsystems- }--data Subsystems = Subsystems- { subWindow :: Window- , subMouse :: Mouse- , subKeyboard :: Keyboard- }--newtype Window = Window (TVar [(UUID, WindowMessage -> Affection StateData ())])-newtype Mouse = Mouse (TVar [(UUID, MouseMessage -> Affection StateData ())])-newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection StateData ())])--instance Participant Window WindowMessage StateData where- partSubscribers (Window t) = do- subTups <- liftIO $ readTVarIO t- return $ map snd subTups-- partSubscribe (Window t) funct = do- uuid <- genUUID- liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)- return $ MsgId uuid MsgWindowEmptyEvent-- partUnSubscribe (Window t) (MsgId uuid _) =- liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid))--instance SDLSubsystem Window StateData where- consumeSDLEvents = consumeSDLWindowEvents--instance Participant Mouse MouseMessage StateData where- partSubscribers (Mouse t) = do- subTups <- liftIO $ readTVarIO t- return $ map snd subTups-- partSubscribe (Mouse t) funct = do- uuid <- genUUID- liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)- return $ MsgId uuid MsgMouseEmptyEvent-- partUnSubscribe (Mouse t) (MsgId uuid _) =- liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid))--instance SDLSubsystem Mouse StateData where- consumeSDLEvents = consumeSDLMouseEvents--instance Participant Keyboard KeyboardMessage StateData where- partSubscribers (Keyboard t) = do- subTups <- liftIO $ readTVarIO t- return $ map snd subTups-- partSubscribe (Keyboard t) funct = do- uuid <- genUUID- liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)- return $ MsgId uuid MsgKeyboardEmptyEvent-- partUnSubscribe (Keyboard t) (MsgId uuid _) =- liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid))--instance SDLSubsystem Keyboard StateData where- consumeSDLEvents = consumeSDLKeyboardEvents--main :: IO ()-main = do- logIO Debug "Starting"- let conf = AffectionConfig- { initComponents = All- , windowTitle = "affection: example00"- , windowConfig = SDL.defaultWindow- { SDL.windowOpenGL = Just SDL.defaultOpenGL- { SDL.glProfile = SDL.Core SDL.Normal 3 3- }- }- , initScreenMode = SDL.Windowed- , canvasSize = Nothing- , loadState = load- , preLoop = pre- , eventLoop = handle- , updateLoop = update- , drawLoop = draw- , cleanUp = clean- }- withAffection conf--load :: IO StateData-load = do- empty1 <- newTVarIO [] -- ([] :: [(UUID, WindowMessage -> Affection StateData ())])- empty2 <- newTVarIO [] -- ([] :: [(UUID, MouseMessage -> Affection StateData ())])- empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection StateData ())])- return $ StateData $ Subsystems- (Window empty1)- (Mouse empty2)- (Keyboard empty3)--pre :: Affection StateData ()-pre = do- sd <- getAffection- _ <- partSubscribe (subKeyboard $ sdSubs sd) exitOnQ- _ <- partSubscribe (subWindow $ sdSubs sd) exitOnWindowClose- return ()--exitOnQ :: KeyboardMessage -> Affection StateData ()-exitOnQ (MsgKeyboardEvent _ _ _ _ sym) =- case SDL.keysymKeycode sym of- SDL.KeycodeQ -> do- liftIO $ logIO Debug "Yo dog I heard..."- quit- _ -> return ()--exitOnWindowClose :: WindowMessage -> Affection StateData ()-exitOnWindowClose wm =- case wm of- MsgWindowClose _ _ -> do- liftIO $ logIO Debug "I heard another one..."- quit- _ -> return ()--handle :: [SDL.EventPayload] -> Affection StateData ()-handle es = do- (Subsystems a b c) <- sdSubs <$> getAffection- _ <- consumeSDLEvents a es- _ <- consumeSDLEvents b es- _ <- consumeSDLEvents c es- return ()--update _ = return ()--draw = return ()--clean _ = return ()
+ examples/example00/Main.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++import Affection as A++import Control.Concurrent.STM+import Control.Monad.IO.Class (liftIO)+import Control.Monad (when)++import qualified SDL hiding (Window(..))++import Data.Maybe (isJust, fromJust)++data StateData = StateData+ { sdSubs :: Subsystems+ , sdJoys :: [SDL.Joystick]+ }++data Subsystems = Subsystems+ { subWindow :: Main.Window+ , subMouse :: Main.Mouse+ , subKeyboard :: Main.Keyboard+ , subJoystick :: Main.Joystick+ }++newtype Window = Window (TVar [(UUID, WindowMessage -> Affection StateData ())])+newtype Mouse = Mouse (TVar [(UUID, MouseMessage -> Affection StateData ())])+newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection StateData ())])+newtype Joystick = Joystick (TVar [(UUID, JoystickMessage -> Affection StateData ())])++generalSubscribers+ :: TVar [(UUID, msg -> Affection StateData ())]+ -> Affection StateData [msg -> Affection StateData ()]+generalSubscribers t = do+ subTups <- liftIO $ readTVarIO t+ return $ map snd subTups++generalSubscribe+ :: TVar [(UUID, msg -> Affection StateData ())]+ -> (msg -> Affection StateData())+ -> Affection StateData UUID+generalSubscribe t funct = do+ uuid <- genUUID+ liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)+ return uuid++generalUnSubscribe+ :: TVar [(UUID, msg -> Affection StateData ())]+ -> UUID+ -> Affection StateData ()+generalUnSubscribe t uuid =+ liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid))++instance Participant Main.Window StateData where+ type Mesg Main.Window StateData = WindowMessage++ partSubscribers (Window t) = generalSubscribers t++ partSubscribe (Window t) = generalSubscribe t++ partUnSubscribe (Window t) = generalUnSubscribe t++instance SDLSubsystem Main.Window StateData where+ consumeSDLEvents = consumeSDLWindowEvents++instance Participant Mouse StateData where+ type Mesg Mouse StateData = MouseMessage++ partSubscribers (Mouse t) = generalSubscribers t++ partSubscribe (Mouse t) = generalSubscribe t++ partUnSubscribe (Mouse t) = generalUnSubscribe t++instance SDLSubsystem Mouse StateData where+ consumeSDLEvents = consumeSDLMouseEvents++instance Participant Keyboard StateData where+ type Mesg Keyboard StateData = KeyboardMessage++ partSubscribers (Keyboard t) = generalSubscribers t++ partSubscribe (Keyboard t) = generalSubscribe t++ partUnSubscribe (Keyboard t) = generalUnSubscribe t++instance SDLSubsystem Keyboard StateData where+ consumeSDLEvents = consumeSDLKeyboardEvents++instance Participant Joystick StateData where+ type Mesg Joystick StateData = JoystickMessage++ partSubscribers (Joystick t) = generalSubscribers t++ partSubscribe (Joystick t) = generalSubscribe t++ partUnSubscribe (Joystick t) = generalUnSubscribe t++instance SDLSubsystem Joystick StateData where+ consumeSDLEvents = consumeSDLJoystickEvents++main :: IO ()+main = do+ logIO Debug "Starting"+ let conf = AffectionConfig+ { initComponents = All+ , windowTitle = "affection: example00"+ , windowConfig = SDL.defaultWindow+ { SDL.windowOpenGL = Just SDL.defaultOpenGL+ { SDL.glProfile = SDL.Core SDL.Normal 3 3+ }+ }+ , initScreenMode = SDL.Windowed+ , canvasSize = Nothing+ , loadState = load+ , preLoop = pre+ , eventLoop = handle+ , updateLoop = update+ , drawLoop = draw+ , cleanUp = clean+ }+ withAffection conf++load :: IO StateData+load =+ StateData <$> (Subsystems+ <$> (Window <$> newTVarIO [])+ <*> (Mouse <$> newTVarIO [])+ <*> (Keyboard <$> newTVarIO [])+ <*> (Joystick <$> newTVarIO [])+ )+ <*> return []++pre :: Affection StateData ()+pre = do+ sd <- getAffection+ _ <- partSubscribe (subKeyboard $ sdSubs sd) exitOnQ+ _ <- partSubscribe (subWindow $ sdSubs sd) exitOnWindowClose+ _ <- partSubscribe (subJoystick $ sdSubs sd) joyConnectDisconnect+ return ()++exitOnQ :: KeyboardMessage -> Affection StateData ()+exitOnQ (MsgKeyboardEvent _ _ _ _ sym) =+ case SDL.keysymKeycode sym of+ SDL.KeycodeQ -> do+ liftIO $ logIO Debug "Yo dog I heard..."+ quit+ _ -> return ()++exitOnWindowClose :: WindowMessage -> Affection StateData ()+exitOnWindowClose wm =+ case wm of+ MsgWindowClose _ _ -> do+ liftIO $ logIO Debug "I heard another one..."+ quit+ _ -> return ()++joyConnectDisconnect :: JoystickMessage -> Affection StateData ()+joyConnectDisconnect msg = do+ mj <- joystickAutoConnect msg+ when (isJust mj) $ do+ sd <- getAffection+ putAffection sd+ { sdJoys = fromJust mj : sdJoys sd+ }+ sd <- getAffection+ njs <- joystickAutoDisconnect (sdJoys sd) msg+ putAffection sd+ { sdJoys = njs+ }++handle :: [SDL.EventPayload] -> Affection StateData ()+handle es = do+ (Subsystems a b c d) <- sdSubs <$> getAffection+ leftovers <- consumeSDLEvents a+ =<< consumeSDLEvents b+ =<< consumeSDLEvents c+ =<< consumeSDLEvents d es+ mapM_ (\e -> liftIO $ logIO Verbose $ "LEFTOVER: " ++ show e) leftovers++update _ = return ()++draw = return ()++clean _ = return ()
+ examples/example01/Main.hs view
@@ -0,0 +1,251 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ForeignFunctionInterface #-}++import Affection as A+import SDL (($=))+import qualified SDL+import qualified Graphics.Rendering.OpenGL as GL++import Control.Concurrent.STM+import Control.Monad.IO.Class (liftIO)+import Control.Monad (when)+import Control.DeepSeq (deepseq)++import Data.Matrix as M+import qualified Data.Set as S++import System.Random (randomRIO)++import NanoVG hiding (V2(..))++import Linear++import Foreign.C.Types (CInt(..))++-- internal imports++import Types++foreign import ccall unsafe "glewInit"+ glewInit :: IO CInt++main :: IO ()+main = do+ logIO A.Debug "Starting"+ let conf = AffectionConfig+ { initComponents = All+ , windowTitle = "affection: example01"+ , windowConfig = SDL.defaultWindow+ { SDL.windowOpenGL = Just SDL.defaultOpenGL+ { SDL.glProfile = SDL.Core SDL.Normal 3 3+ }+ , SDL.windowInitialSize = SDL.V2 600 600+ , SDL.windowResizable = True+ }+ , initScreenMode = SDL.Windowed+ , canvasSize = Nothing+ , loadState = load+ , preLoop = pre+ , eventLoop = handle+ , updateLoop = update+ , drawLoop = draw+ , cleanUp = clean+ }+ withAffection conf++load :: IO UserData+load = do+ -- emptyMatrix <- zero 60 60+ liftIO $ logIO A.Debug "init GLEW"+ _ <- glewInit+ liftIO $ logIO A.Debug "making random"+ randList <- mapM (\_ -> randomRIO (0,1)) [0..3599]+ liftIO $ logIO A.Debug "creating context"+ nanoCtx <- createGL3 (S.fromList [Antialias, StencilStrokes, NanoVG.Debug])+ let fullMatrix = fromList 60 60 randList+ -- logIO A.Debug $ prettyMatrix fullMatrix+ empty1 <- newTVarIO [] -- ([] :: [(UUID, WindowMessage -> Affection UserData ())])+ empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection UserData ())])+ return $ UserData+ { subsystems = Subsystems+ (Window empty1)+ (Keyboard empty3)+ , lifeMat = fullMatrix+ , foodMat = fromList 60 60 (repeat 10)+ , timeMat = M.zero 60 60+ , nano = nanoCtx+ , lastUpdate = 0+ }++pre :: Affection UserData ()+pre = do+ sd <- getAffection+ _ <- partSubscribe (subKeyboard $ subsystems sd) exitOnEsc+ _ <- partSubscribe (subKeyboard $ subsystems sd) reloadOnR+ _ <- partSubscribe (subKeyboard $ subsystems sd) showFPS+ _ <- partSubscribe (subWindow $ subsystems sd) exitOnWindowClose+ _ <- partSubscribe (subWindow $ subsystems sd) windowResize+ now <- getElapsedTime+ putAffection sd+ { lastUpdate = floor now+ }++exitOnEsc :: KeyboardMessage -> Affection UserData ()+exitOnEsc (MsgKeyboardEvent _ _ _ _ sym) =+ case SDL.keysymKeycode sym of+ SDL.KeycodeEscape -> do+ liftIO $ logIO A.Debug "Yo dog I heard..."+ quit+ _ -> return ()++reloadOnR :: KeyboardMessage -> Affection UserData ()+reloadOnR (MsgKeyboardEvent _ _ _ _ sym) =+ case SDL.keysymKeycode sym of+ SDL.KeycodeR -> reload+ _ -> return ()++reload :: Affection UserData ()+reload = do+ ud <- getAffection+ now <- getElapsedTime+ randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599]+ let fullMatrix = fromList 60 60 randList+ putAffection ud+ { lifeMat = fullMatrix+ , foodMat = fromList 60 60 (repeat 10)+ , timeMat = M.zero 60 60+ , lastUpdate = floor now+ }++showFPS :: KeyboardMessage -> Affection UserData ()+showFPS (MsgKeyboardEvent _ _ _ _ sym) =+ case SDL.keysymKeycode sym of+ SDL.KeycodeF -> do+ dt <- getDelta+ liftIO $ logIO A.Debug $ "FPS: " ++ show (1 / dt)+ _ -> return ()++exitOnWindowClose :: WindowMessage -> Affection UserData ()+exitOnWindowClose wm =+ case wm of+ MsgWindowClose _ _ -> do+ liftIO $ logIO A.Debug "I heard another one..."+ quit+ _ -> return ()++windowResize :: WindowMessage -> Affection UserData ()+windowResize msg = case msg of+ (MsgWindowResize _ _ (V2 w h)) -> do+ liftIO $ logIO A.Debug "Window resized"+ let nw = floor (fromIntegral h)+ dw = floor ((fromIntegral w - fromIntegral nw) / 2)+ GL.viewport $= (GL.Position dw 0, GL.Size nw h)+ _ -> return ()++handle :: [SDL.EventPayload] -> Affection UserData ()+handle es = do+ (Subsystems a b) <- subsystems <$> getAffection+ _ <- consumeSDLEvents a =<< consumeSDLEvents b es+ return ()++update :: Double -> Affection UserData ()+update _ = do+ ud <- getAffection+ newList <- mapM (\coord -> do+ let x = (coord `mod` 60) + 1+ y = (coord `div` 60) + 1+ subm+ | x == 1 && y == 1 =+ submatrix 60 60 60 60 (lifeMat ud) <|> submatrix 60 60 1 2 (lifeMat ud)+ <->+ submatrix 1 2 60 60 (lifeMat ud) <|> submatrix 1 2 1 2 (lifeMat ud)+ | x == 1 && y == 60 =+ submatrix 59 60 60 60 (lifeMat ud) <|> submatrix 59 60 1 2 (lifeMat ud)+ <->+ submatrix 1 1 60 60 (lifeMat ud) <|> submatrix 1 1 1 2 (lifeMat ud)+ | x == 60 && y == 1 =+ submatrix 60 60 59 60 (lifeMat ud) <|> submatrix 60 60 1 1 (lifeMat ud)+ <->+ submatrix 1 2 59 60 (lifeMat ud) <|> submatrix 1 2 1 1 (lifeMat ud)+ | x == 60 && y == 60 =+ submatrix 59 60 59 60 (lifeMat ud) <|> submatrix 59 60 1 1 (lifeMat ud)+ <->+ submatrix 1 1 59 60 (lifeMat ud) <|> submatrix 1 1 1 1 (lifeMat ud)+ | x == 1 =+ submatrix (y - 1) (y + 1) 60 60 (lifeMat ud)+ <|>+ submatrix (y - 1) (y + 1) 1 2 (lifeMat ud)+ | y == 1 =+ submatrix 60 60 (x - 1) (x + 1) (lifeMat ud)+ <->+ submatrix 1 2 (x - 1) (x + 1) (lifeMat ud)+ | x == 60 =+ submatrix (y - 1) (y + 1) 59 60 (lifeMat ud)+ <|>+ submatrix (y - 1) (y + 1) 1 1 (lifeMat ud)+ | y == 60 =+ submatrix 59 60 (x -1 ) (x + 1) (lifeMat ud)+ <->+ submatrix 1 1 (x - 1) (x + 1) (lifeMat ud)+ | otherwise =+ submatrix (y - 1) (y + 1) (x - 1) (x + 1) (lifeMat ud)+ life = countLife subm+ if lifeMat ud M.! (y, x) == 1+ then if life == 2 || life == 3 && foodMat ud M.! (y, x) > 0+ then return (1, (foodMat ud M.! (y, x)) - 1, 0)+ else return (0, foodMat ud M.! (y, x), 1)+ else if life == 3 && foodMat ud M.! (y, x) > 0+ then return (1, (foodMat ud M.! (y, x)) - 1, 0)+ else return+ ( 0+ , if timeMat ud M.! (y, x) > 10+ then min 10 ((foodMat ud M.! (y, x)) + 1)+ else foodMat ud M.! (y, x)+ , timeMat ud M.! (y, x) + 1+ )+ ) [0..3599]+ let newLifeMat = fromList 60 60 (map (\(x, _, _) -> x) newList)+ let newFoodMat = fromList 60 60 (map (\(_, x, _) -> x) newList)+ let newTimeMat = fromList 60 60 (map (\(_, _, x) -> x) newList)+ if newLifeMat == M.zero 60 60+ then+ reload+ else+ putAffection ((newLifeMat, newFoodMat, newTimeMat) `deepseq` ud)+ { lifeMat = newLifeMat+ , foodMat = newFoodMat+ , timeMat = newTimeMat+ -- , lastUpdate = floor now+ }++countLife :: Matrix Word -> Word+countLife mat = res - (mat M.! (2, 2))+ where+ res = foldr (flip (+)) 0 mat++draw :: Affection UserData ()+draw = do+ ud <- getAffection+ liftIO $ do+ beginFrame (nano ud) 600 600 1+ save (nano ud)+ mapM_ (\coord -> do+ let x = coord `mod` 60+ y = coord `div` 60+ ctx = nano ud+ mult = lifeMat ud M.! (x + 1, y + 1)+ -- logIO A.Debug $ show mult+ beginPath ctx+ rect ctx (fromIntegral $ x * 10) (fromIntegral $ y * 10) 10 10+ if mult == 1+ then+ fillColor ctx (rgba 255 255 255 255)+ else+ fillColor ctx (rgba 0 (fromIntegral $ 25 * (foodMat ud M.! (x+1, y+1))) 0 255)+ fill ctx+ ) [0..3599]+ restore (nano ud)+ endFrame (nano ud)++clean _ = return ()
+ examples/example01/Types.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++module Types where++import Affection++import Data.Matrix as M++import NanoVG++import Control.Concurrent.STM+import Control.Monad.IO.Class (liftIO)++data UserData = UserData+ { lifeMat :: Matrix Word+ , foodMat :: Matrix Word+ , timeMat :: Matrix Word+ , subsystems :: Subsystems+ , nano :: Context+ , lastUpdate :: Int+ }++data Subsystems = Subsystems+ { subWindow :: Types.Window+ , subKeyboard :: Types.Keyboard+ }++newtype Window = Window (TVar [(UUID, WindowMessage -> Affection UserData ())])++newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection UserData ())])++instance Participant Types.Window UserData where+ type Mesg Types.Window UserData = WindowMessage++ partSubscribers (Window t) = do+ subTups <- liftIO $ readTVarIO t+ return $ map snd subTups++ partSubscribe (Window t) = generalSubscribe t++ partUnSubscribe (Window t) = generalUnSubscribe t++instance SDLSubsystem Types.Window UserData where+ consumeSDLEvents = consumeSDLWindowEvents++instance Participant Keyboard UserData where+ type Mesg Keyboard UserData = KeyboardMessage++ partSubscribers (Keyboard t) = do+ subTups <- liftIO $ readTVarIO t+ return $ map snd subTups++ partSubscribe (Keyboard t) = generalSubscribe t++ partUnSubscribe (Keyboard t) = generalUnSubscribe t++instance SDLSubsystem Keyboard UserData where+ consumeSDLEvents = consumeSDLKeyboardEvents++generalSubscribe+ :: TVar [(UUID, msg -> Affection UserData ())]+ -> (msg -> Affection UserData ())+ -> Affection UserData UUID+generalSubscribe t funct = do+ uuid <- genUUID+ liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)+ return uuid++generalUnSubscribe+ :: TVar [(UUID, msg -> Affection UserData ())]+ -> UUID+ -> Affection UserData ()+generalUnSubscribe t uuid =+ liftIO $ atomically $ modifyTVar' t (filter (`filterMsg` uuid))+ where+ filterMsg :: (UUID, msg -> Affection UserData ()) -> UUID -> Bool+ filterMsg (u, _) p = u /= p
src/Affection.hs view
@@ -4,31 +4,32 @@ ( withAffection , get , put+ , liftIO , module A ) where import SDL (($=)) import qualified SDL+import qualified SDL.Raw.Video as SDL (glSetAttribute)+import qualified SDL.Raw.Enum as SDL import System.Clock import Control.Monad.Loops-import Control.Monad.State+import Control.Monad.State.Strict+import Control.Monad.IO.Class (liftIO) import Foreign.C.Types (CInt(..)) -import Debug.Trace- import Affection.Types as A import Affection.StateMachine as A-import Affection.MouseInteractable as A import Affection.Util as A import Affection.MessageBus as A import Affection.Subsystems as A import Affection.Logging as A -import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..))+import qualified Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) -- | Main function which bootstraps everything else.@@ -55,39 +56,14 @@ liftIO $ logIO Debug "Creating Window" window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window- -- renderer <- SDL.createRenderer- -- window (-1)- -- SDL.defaultRenderer- -- { SDL.rendererTargetTexture = True- -- }- -- surface <- SDL.createRGBSurface- -- (case canvasSize of- -- Just (cw, ch) -> SDL.V2- -- (CInt $ fromIntegral cw)- -- (CInt $ fromIntegral ch)- -- Nothing ->- -- SDL.windowInitialSize windowConfig- -- )- -- SDL.RGBA8888- -- texture <- SDL.createTexture- -- renderer- -- SDL.RGBA8888- -- SDL.TextureAccessTarget- -- (case canvasSize of- -- Just (cw, ch) -> SDL.V2- -- (CInt $ fromIntegral cw)- -- (CInt $ fromIntegral ch)- -- Nothing ->- -- SDL.windowInitialSize windowConfig- -- )- -- SDL.rendererRenderTarget renderer $= Just texture+ _ <- SDL.glSetAttribute SDL.SDL_GL_SHARE_WITH_CURRENT_CONTEXT 1 context <- SDL.glCreateContext window let SDL.V2 (CInt rw) (CInt rh) = SDL.windowInitialSize windowConfig (w, h) = case canvasSize of Just (cw, ch) -> (cw, ch) Nothing -> (fromIntegral rw, fromIntegral rh) SDL.setWindowMode window initScreenMode- SDL.swapInterval $= SDL.SynchronizedUpdates+ -- SDL.swapInterval $= SDL.SynchronizedUpdates -- <- causes Problems with windows liftIO $ logIO Debug "Getting Time" -- get current time execTime <- getTime Monotonic@@ -109,10 +85,7 @@ (_, nState) <- runStateT ( A.runState $ do liftIO $ logIO Debug "Starting Loop" preLoop- whileM_ (do- current <- get- return $ not $ A.quitEvent current- )+ whileM_ (not . A.quitEvent <$> get) (do -- get state ad <- get@@ -129,20 +102,16 @@ } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents- -- mapM_ eventLoop evs+ -- handle events eventLoop evs -- execute user defined update loop unless (pausedTime ad) (updateLoop dt) -- execute user defined draw loop- liftIO $ GL.clear [ColorBuffer, DepthBuffer, StencilBuffer]+ liftIO $ GL.clear [GL.ColorBuffer, GL.DepthBuffer, GL.StencilBuffer] drawLoop- liftIO flush- -- handle all new draw requests- ad2 <- get- -- actual drawing+ liftIO GL.flush+ -- actual displaying of newly drawn frame SDL.glSwapWindow window- -- SDL.copy renderer texture Nothing Nothing- -- SDL.present renderer -- save new time ad3 <- get when (sysTime ad == sysTime ad3) (@@ -155,6 +124,7 @@ liftIO $ logIO Debug "Loop ended. Cleaning" cleanUp $ userState nState liftIO $ logIO Debug "Destroying Window"+ SDL.glDeleteContext context SDL.destroyWindow window- SDL.quit+ -- SDL.quit -- <- This causes segfaults depending on hardware liftIO $ logIO Debug "This is the end"
src/Affection/Logging.hs view
@@ -1,15 +1,23 @@ {-# LANGUAGE CPP #-}+-- | This module defines the logging capability of Affection, whis is derived+-- from "Debug.Trace". module Affection.Logging where import Debug.Trace +-- | The log level definition data LogLevel- = Verbose- | Debug- | Warn- | Error+ = Verbose -- ^ Log everything+ | Debug -- ^ Log Debug messages and above+ | Warn -- ^ Log only Warnings and errors+ | Error -- ^ Log only errors -log :: LogLevel -> String -> a -> a+-- | Pure logging function+log+ :: LogLevel -- ^ Log level to log to+ -> String -- ^ The message string+ -> a -- ^ Arbitrary datatype to return+ -> a -- ^ Returned data #if defined(VERBOSE) log Verbose s = trace ("VERBOSE: " ++ s) #endif@@ -24,7 +32,11 @@ #endif log _ _ = id -logIO :: LogLevel -> String -> IO ()+-- | Manadic logging function residing in the 'IO' Monad+logIO+ :: LogLevel -- ^ Log level to log to+ -> String -- ^ The message string+ -> IO () #if defined(VERBOSE) logIO Verbose s = traceIO ("VERBOSE: " ++ s) #endif
src/Affection/MessageBus.hs view
@@ -5,6 +5,5 @@ import Affection.MessageBus.Class as M import Affection.MessageBus.Message as M-import Affection.MessageBus.Util as M import Affection.MessageBus.Message as Msg
src/Affection/MessageBus/Class.hs view
@@ -3,11 +3,10 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ExistentialQuantification #-}-module Affection.MessageBus.Class +module Affection.MessageBus.Class ( Participant(..) , genUUID , UUID- , MsgId(..) ) where import Affection.MessageBus.Message@@ -20,30 +19,33 @@ import Affection.Logging -class (Message msg, Show msg) => Participant prt msg us where+-- | This typeclass defines the behaviour of a participant in the message system+class (Message (Mesg prt us), Show (Mesg prt us)) => Participant prt us where+ -- | Message datatype+ type Mesg prt us :: * -- | Function to get the list of subscribers from the participant partSubscribers :: prt -- ^ the 'Participant''s subscriber storage- -> Affection us [msg -> Affection us ()]+ -> Affection us [Mesg prt us -> Affection us ()] -- ^ List of Subscriber functions -- | Subscribe to the 'Participant''s events partSubscribe :: prt -- ^ The 'Participant''s subscriber storage- -> (msg -> Affection us ())+ -> (Mesg prt us -> Affection us ()) -- ^ What to do in case of a 'Message' -- (Subscriber function)- -> Affection us (MsgId msg)+ -> Affection us UUID -- ^ 'UUID' of the registered subscriber Function -- | Unsubscribe a Subscriber function from Participant partUnSubscribe :: prt -- ^ The 'Participant''s subscriber storage to unsubscribe from- -> (MsgId msg)+ -> UUID -- ^ The subscriber function's 'UUID' -> Affection us () @@ -51,7 +53,7 @@ partEmit :: prt -- ^ The 'Participant''s subscriber storage- -> msg+ -> Mesg prt us -- ^ The 'Message' to emit -> Affection us () partEmit p m = do@@ -62,5 +64,3 @@ -- | Helper function to generate new 'UUID's genUUID :: Affection us UUID genUUID = liftIO nextRandom--data MsgId msg = (Message msg, Show msg) => MsgId UUID msg
src/Affection/MessageBus/Message.hs view
@@ -6,3 +6,4 @@ import Affection.MessageBus.Message.WindowMessage as M import Affection.MessageBus.Message.KeyboardMessage as M import Affection.MessageBus.Message.MouseMessage as M+import Affection.MessageBus.Message.JoystickMessage as M
src/Affection/MessageBus/Message/Class.hs view
@@ -1,6 +1,6 @@ module Affection.MessageBus.Message.Class where -import Data.Word (Word32(..))-+-- | Typeclass definition for messages class Message msg where+ -- | return the time when the message was sent msgTime :: msg -> Double
+ src/Affection/MessageBus/Message/JoystickMessage.hs view
@@ -0,0 +1,67 @@+module Affection.MessageBus.Message.JoystickMessage + ( JoystickMessage(..)+ -- | Vector export+ , Linear.V2+ -- | SDL exports+ , SDL.JoyHatPosition+ , SDL.JoyButtonState+ , SDL.JoyDeviceConnection+ -- | Number exports+ , Word8+ , Int16+ , Int32+ ) where++import Affection.MessageBus.Message.Class++import Data.Word (Word8)+import Data.Int (Int32, Int16)++import qualified SDL++import Linear (V2(..))++-- | Datatype for handling all possible joystick events handed over from sdl2+data JoystickMessage+ -- | Movement of a Joystick axis+ = MsgJoystickAxis+ { msgJAWhen :: Double -- ^ Time of event+ , msgJAWhich :: Int32 -- ^ Joystick identifier+ , msgJAAxis :: Word8 -- ^ Axis identifier+ , msgJAValue :: Int16 -- ^ Axis value+ }+ -- | Movement of a joystick ball controller+ | MsgJoystickBall+ { msgJBWhen :: Double -- ^ Time of event+ , msgJBWhich :: Int32 -- ^ Joystick identifier+ , msgJBBall :: Word8 -- ^ Ball identifier+ , msgJBRelMotion :: V2 Int16 -- ^ Motion relative to previous position+ }+ -- | Movement of joystick hat controller+ | MsgJoystickHat+ { msgJHWhen :: Double -- ^ Time of event+ , msgJHWhich :: Int32 -- ^ Joystick identifier+ , msgJHHat :: Word8 -- ^ Hat identifier+ , msgJHPosition :: SDL.JoyHatPosition -- ^ New hat position+ }+ -- | Joystick button event+ | MsgJoystickButton+ { msgJBWhen :: Double -- ^ Time of event+ , msgJBWhich :: Int32 -- ^ Joystick identifier+ , msgJBButton :: Word8 -- ^ Button identifier+ , msgJBState :: SDL.JoyButtonState -- ^ New Button state+ }+ -- | Joystick device event+ | MsgJoystickDevice+ { msgJDWhen :: Double -- ^ Time of event+ , msgJDWhich :: Int32 -- ^ Joystick identifier+ , msgJDConnection :: SDL.JoyDeviceConnection -- ^ Connection status+ }+ deriving (Show)++instance Message JoystickMessage where+ msgTime (MsgJoystickAxis t _ _ _) = t+ msgTime (MsgJoystickBall t _ _ _) = t+ msgTime (MsgJoystickHat t _ _ _) = t+ msgTime (MsgJoystickButton t _ _ _) = t+ msgTime (MsgJoystickDevice t _ _) = t
src/Affection/MessageBus/Message/KeyboardMessage.hs view
@@ -1,18 +1,25 @@-module Affection.MessageBus.Message.KeyboardMessage where+module Affection.MessageBus.Message.KeyboardMessage + ( KeyboardMessage(..)+ -- | SDL reexports+ , SDL.Window+ , SDL.InputMotion+ , SDL.Keysym+ ) where import Affection.MessageBus.Message.Class import qualified SDL +-- | Dataatype for handling all keyboard events haded down from SDL2 data KeyboardMessage+ -- | Arbitrary Keyboard event = MsgKeyboardEvent- { msgKbdWhen :: Double- , msgKbdWindow :: Maybe SDL.Window- , msgKbdKeyMotion :: SDL.InputMotion- , msgKbdKeyRepeat :: Bool- , msgKbdKeysym :: SDL.Keysym+ { msgKbdWhen :: Double -- ^ Message time+ , msgKbdWindow :: Maybe SDL.Window -- ^ Affected Window+ , msgKbdKeyMotion :: SDL.InputMotion -- ^ Input motion of button (pressed/released)+ , msgKbdKeyRepeat :: Bool -- ^ Is this a repeated event?+ , msgKbdKeysym :: SDL.Keysym -- ^ The button's 'SDL.Keysym' }- | MsgKeyboardEmptyEvent deriving (Show) instance Message KeyboardMessage where
src/Affection/MessageBus/Message/MouseMessage.hs view
@@ -1,42 +1,54 @@-module Affection.MessageBus.Message.MouseMessage where+module Affection.MessageBus.Message.MouseMessage+ ( MouseMessage(..)+ -- | SDL reexports+ , SDL.Window+ , SDL.MouseDevice+ , SDL.MouseButton+ , SDL.InputMotion+ , SDL.MouseScrollDirection+ ) where import Affection.MessageBus.Message.Class -import Data.Word (Word8(..))-import Data.Int (Int32(..))+import Data.Word (Word8)+import Data.Int (Int32) import qualified SDL import Linear (V2(..)) +-- Datatype for handling mouse events handed down from SDL2 data MouseMessage+ -- | Mouse motion event = MsgMouseMotion- { msgMMWhen :: Double- , msgMMWindow :: Maybe SDL.Window- , msgMMWhich :: SDL.MouseDevice- , msgMMState :: [SDL.MouseButton]- , msgMMPos :: V2 Int32- , msgMMRelMotion :: V2 Int32+ { msgMMWhen :: Double -- ^ Message time+ , msgMMWindow :: Maybe SDL.Window -- ^ Focused window (if any)+ , msgMMWhich :: SDL.MouseDevice -- ^ Mouse device identifier+ , msgMMState :: [SDL.MouseButton] -- ^ List of pressed mouse buttons+ , msgMMPos :: V2 Int32 -- ^ Absolute mouse positiom+ , msgMMRelMotion :: V2 Int32 -- ^ Mouse movement relative to previous position }+ -- | Mouse button event | MsgMouseButton- { msgMBWhen :: Double- , msgMBWindow :: Maybe SDL.Window- , msgMBWhich :: SDL.MouseDevice- , msgMBButton :: SDL.MouseButton- , msgMBClicks :: Word8- , msgMBPos :: V2 Int32+ { msgMBWhen :: Double -- ^ Message time+ , msgMBWindow :: Maybe SDL.Window -- ^ Focused window (if any)+ , msgMBMotion :: SDL.InputMotion -- ^ Button's input motion+ , msgMBWhich :: SDL.MouseDevice -- ^ Mouse device identifier+ , msgMBButton :: SDL.MouseButton -- ^ Affected mouse button+ , msgMBClicks :: Word8 -- ^ Number of clicks+ , msgMBPos :: V2 Int32 -- ^ Absolute mouse position }+ -- | Mouse wheel event | MsgMouseWheel- { msgMWWhen :: Double- , msgMWWhindow :: Maybe SDL.Window- , msgMWWhich :: SDL.MouseDevice- , msgMWPos :: V2 Int32- , msgMWDIrection :: SDL.MouseScrollDirection+ { msgMWWhen :: Double -- ^ Message time+ , msgMWWhindow :: Maybe SDL.Window -- ^ Focused window (if any)+ , msgMWWhich :: SDL.MouseDevice -- ^ Mouse device identifier+ , msgMWPos :: V2 Int32 -- ^ Absolute mouse position+ , msgMWDIrection :: SDL.MouseScrollDirection -- ^ Scroll direction }- | MsgMouseEmptyEvent deriving (Show) instance Message MouseMessage where msgTime (MsgMouseMotion t _ _ _ _ _) = t- msgTime (MsgMouseButton t _ _ _ _ _) = t+ msgTime (MsgMouseButton t _ _ _ _ _ _) = t msgTime (MsgMouseWheel t _ _ _ _) = t
src/Affection/MessageBus/Message/WindowMessage.hs view
@@ -1,84 +1,101 @@-module Affection.MessageBus.Message.WindowMessage where+module Affection.MessageBus.Message.WindowMessage+ ( WindowMessage(..)+ -- | SDL reexports+ , SDL.Window+ ) where import Affection.MessageBus.Message.Class -import Data.Int (Int32(..))+import Data.Int (Int32) import qualified SDL import Linear (V2(..)) +-- | Datatype for handling Window events handed down rom SDL2 data WindowMessage- -- = MsgEngineReady Double+ -- | Window show event = MsgWindowShow- { msgWSWhen :: Double- , msgWSWindow :: SDL.Window+ { msgWSWhen :: Double -- ^ Message time+ , msgWSWindow :: SDL.Window -- ^ Window identifier }+ -- | Window hide event | MsgWindowHide- { msgWHWhen :: Double- , msgWHWindow :: SDL.Window+ { msgWHWhen :: Double -- ^ Message time+ , msgWHWindow :: SDL.Window -- ^ Window identifier }+ -- | Window expose event | MsgWindowExpose- { msgWEWhen :: Double- , msgWEWindow :: SDL.Window+ { msgWEWhen :: Double -- ^ Message time+ , msgWEWindow :: SDL.Window -- ^ Window identifier }+ -- | Window move event | MsgWindowMove- { msgWMWhen :: Double- , msgWMWindow :: SDL.Window- , msgWMNewPos :: V2 Int32+ { msgWMWhen :: Double -- ^ Message time+ , msgWMWindow :: SDL.Window -- ^ Window identifier+ , msgWMNewPos :: V2 Int32 -- ^ New absolute window position }+ -- | Window resize event | MsgWindowResize- { msgWRWhen :: Double- , msgWRWindow :: SDL.Window- , msgWRNewSize :: V2 Int32+ { msgWRWhen :: Double -- ^ Message time+ , msgWRWindow :: SDL.Window -- ^ Window identifier+ , msgWRNewSize :: V2 Int32 -- ^ New absolute window size }+ -- | Window size change event | MsgWindowSizeChange- { msgWSCWhen :: Double- , msgWSCWindow :: SDL.Window+ { msgWSCWhen :: Double -- ^ Message time+ , msgWSCWindow :: SDL.Window -- ^ Window identifier+ , msgWSCNewSize :: V2 Int32 -- ^ New absolute window size }+ -- | Window minimize event | MsgWindowMinimize- { msgWMinWhen :: Double- , msgWMinWindow :: SDL.Window+ { msgWMinWhen :: Double -- ^ Message time+ , msgWMinWindow :: SDL.Window -- ^ Window identifier }+ -- | Window maximize event | MsgWindowMaximize- { msgWMaxWhen :: Double- , msgWMaxWindow :: SDL.Window+ { msgWMaxWhen :: Double -- ^ Message time+ , msgWMaxWindow :: SDL.Window -- ^ Window identifier }+ -- | Window restore event | MsgWindowRestore- { msgWRestWhen :: Double- , msgWRestWindow :: SDL.Window+ { msgWRestWhen :: Double -- ^ Message time+ , msgWRestWindow :: SDL.Window -- ^ Window identifier }+ -- | Window gain mouse focus event | MsgWindowGainMouseFocus- { msgWGMFWhen :: Double- , msgWGMFWindow :: SDL.Window+ { msgWGMFWhen :: Double -- ^ Message Time+ , msgWGMFWindow :: SDL.Window -- ^ Window identifier }+ -- | Window lose mouse focus event | MsgWindowLoseMouseFocus- { msgWLMFWhen :: Double- , msgWLMFWindow :: SDL.Window+ { msgWLMFWhen :: Double -- ^ Message Time+ , msgWLMFWindow :: SDL.Window -- ^ Window identifier }+ -- | Window gain keyboard focus event | MsgWindowGainKeyboardFocus- { msgWGKFWhen :: Double- , msgWGKFWindow :: SDL.Window+ { msgWGKFWhen :: Double -- ^ Message time+ , msgWGKFWindow :: SDL.Window -- ^ Window identifier }+ -- | Window lose keyboard focus event | MsgWindowLoseKeyboardFocus- { msgWLKFWhen :: Double- , msgWLKFWindow :: SDL.Window+ { msgWLKFWhen :: Double -- ^ Message time+ , msgWLKFWindow :: SDL.Window -- ^ Window identifier }+ -- | Window close event | MsgWindowClose- { msgWCWhen :: Double- , msgWCWindow :: SDL.Window+ { msgWCWhen :: Double -- ^ Message time+ , msgWCWindow :: SDL.Window -- ^ Window identifier }- | MsgWindowEmptyEvent deriving (Show) instance Message WindowMessage where- -- msgTime (MsgEngineReady t) = t msgTime (MsgWindowShow t _) = t msgTime (MsgWindowHide t _) = t msgTime (MsgWindowExpose t _) = t msgTime (MsgWindowMove t _ _) = t msgTime (MsgWindowResize t _ _) = t- msgTime (MsgWindowSizeChange t _) = t+ msgTime (MsgWindowSizeChange t _ _) = t msgTime (MsgWindowMinimize t _) = t msgTime (MsgWindowMaximize t _) = t msgTime (MsgWindowRestore t _) = t
− src/Affection/MessageBus/Util.hs
@@ -1,3 +0,0 @@-module Affection.MessageBus.Util where---- zuru zuru
− src/Affection/MouseInteractable.hs
@@ -1,42 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}--module Affection.MouseInteractable where--import Affection.Types--import qualified SDL---- class MouseHoverable a us where--- onHover :: a -> Affection us ()---- | Define a mouse clickable object-class MouseClickable a us where- onClick- :: a -- The object- -> SDL.MouseButton -- The clicked button- -> (Int, Int) -- The coordinates of the click- -> SDL.InputMotion -- The 'SDL.InputMotion' of the click- -> Int -- The number of clicks- -> Affection us ()---- | A helper function that checks wether provided clickables have been clicked.--- This function does not consume provided events, but passes them on.-handleMouseClicks- :: (Foldable t, MouseClickable clickable us)- => SDL.EventPayload -- ^ Piped event in- -> t clickable -- ^ 'MouseClickable' elemt to be checked- -> Affection us SDL.EventPayload -- ^ Unaltered event-handleMouseClicks e clickables =- case e of- SDL.MouseButtonEvent dat -> do- mapM_ (\clickable -> do- let SDL.P (SDL.V2 x y) = SDL.mouseButtonEventPos dat- onClick- clickable- (SDL.mouseButtonEventButton dat)- (fromIntegral x, fromIntegral y)- (SDL.mouseButtonEventMotion dat)- (fromIntegral $ SDL.mouseButtonEventClicks dat)- ) clickables- return e- _ -> return e
src/Affection/StateMachine.hs view
@@ -6,9 +6,15 @@ import qualified SDL +-- | Typeclass for simple scaffolding of a state machine class StateMachine a us where+ -- | State load routine smLoad :: a -> Affection us ()+ -- | state update routine smUpdate :: a -> Double -> Affection us ()+ -- | State event handler routine smEvent :: a -> [SDL.EventPayload] -> Affection us ()+ -- | State draw routine smDraw :: a -> Affection us ()+ -- | State clean routine smClean :: a -> Affection us ()
src/Affection/Subsystems.hs view
@@ -6,3 +6,4 @@ import Affection.Subsystems.AffectionKeyboard as S import Affection.Subsystems.AffectionWindow as S import Affection.Subsystems.AffectionMouse as S+import Affection.Subsystems.AffectionJoystick as S
+ src/Affection/Subsystems/AffectionJoystick.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+module Affection.Subsystems.AffectionJoystick where++import Affection.MessageBus+import Affection.Types+import Affection.Util+import Affection.Logging++import Control.Monad (filterM)+import Control.Monad.IO.Class (liftIO)++import qualified Data.Vector as V++import Foreign.C.Types (CInt(..))++import qualified SDL++-- | Helper function that consumes all Joystick-related 'SDL.EventPayload's+-- and emits appropriate 'JoystickMessage's+consumeSDLJoystickEvents+ :: forall am us. (Participant am us, Mesg am us ~ JoystickMessage)+ => am -- ^ The message system participant+ -> [SDL.EventPayload] -- ^ Incoming events+ -> Affection us [SDL.EventPayload] -- ^ Leftover SDL events+consumeSDLJoystickEvents am = doConsume+ where+ doConsume+ :: [SDL.EventPayload]+ -> Affection us [SDL.EventPayload]+ doConsume [] = return []+ doConsume (e:es) = do+ ts <- getElapsedTime+ case e of+ SDL.JoyAxisEvent dat -> do+ partEmit am (MsgJoystickAxis+ ts+ (SDL.joyAxisEventWhich dat)+ (SDL.joyAxisEventAxis dat)+ (SDL.joyAxisEventValue dat)+ )+ doConsume es+ SDL.JoyBallEvent dat -> do+ partEmit am (MsgJoystickBall+ ts+ (SDL.joyBallEventWhich dat)+ (SDL.joyBallEventBall dat)+ (SDL.joyBallEventRelMotion dat)+ )+ doConsume es+ SDL.JoyHatEvent dat -> do+ partEmit am (MsgJoystickHat+ ts+ (SDL.joyHatEventWhich dat)+ (SDL.joyHatEventHat dat)+ (SDL.joyHatEventValue dat)+ )+ doConsume es+ SDL.JoyButtonEvent dat -> do+ partEmit am (MsgJoystickButton+ ts+ (SDL.joyButtonEventWhich dat)+ (SDL.joyButtonEventButton dat)+ (SDL.joyButtonEventState dat)+ )+ doConsume es+ SDL.JoyDeviceEvent dat -> do+ partEmit am (MsgJoystickDevice+ ts+ (SDL.joyDeviceEventWhich dat)+ (SDL.joyDeviceEventConnection dat)+ )+ doConsume es+ _ -> fmap (e :) (doConsume es)++-- | Helper function to automatically connect and open newly attached joystick+-- devices+joystickAutoConnect+ :: JoystickMessage -- ^ Any 'JoystickMessage' will do,+ -- but listens only on 'MsgJoystickDevice' messages+ -> Affection us (Maybe SDL.Joystick)+ -- ^ Returns a joystick descriptor, if successful+joystickAutoConnect (MsgJoystickDevice _ which SDL.JoyDeviceAdded) = liftIO $ do+ [descr] <- V.toList <$>+ (V.filter (\(SDL.JoystickDevice _ i) -> i == CInt which) <$>+ SDL.availableJoysticks)+ logIO Verbose $ "Connecting Joystick " ++ show which ++ " " ++ show descr+ Just <$> SDL.openJoystick descr+joystickAutoConnect _ = return Nothing++-- | Helper function to automatically close and disconnect freshly detached+-- joystick devices+joystickAutoDisconnect+ :: [SDL.Joystick] -- ^ List of Joystick descriptors+ -> JoystickMessage -- ^ Any 'JoystickMessage' will do, but listens+ -- specifically to 'MsgJoystickDevice' messages+ -> Affection us [SDL.Joystick] -- ^ Returns altered list of Joystick descriptors+joystickAutoDisconnect js (MsgJoystickDevice _ which SDL.JoyDeviceRemoved) =+ liftIO $ do+ joyIds <- mapM SDL.getJoystickID js+ logIO Verbose $ "These are the Joysticks connected: " ++ show joyIds+ [d] <- filterM (\j -> fmap (== which) (SDL.getJoystickID j)) js+ logIO Verbose $ "disconnected joysticks: " ++ show d+ logIO Verbose $ "Disconnecting Joystick " ++ show which+ SDL.closeJoystick d+ njoys <- filterM (\j -> return $ d /= j) js+ logIO Verbose $ "returning joysticks: " ++ show njoys+ return njoys+joystickAutoDisconnect js _ = return js
src/Affection/Subsystems/AffectionKeyboard.hs view
@@ -1,22 +1,22 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-} module Affection.Subsystems.AffectionKeyboard where import Affection.Types import Affection.Util import Affection.MessageBus-import Affection.Subsystems.Class -import Control.Concurrent.STM as STM-import Control.Monad.IO.Class (liftIO)- import qualified SDL +-- | Helper function that consumes all Keyboard-related 'SDL.EventPayload's+-- and emits appropriate 'KeyboardMessage's consumeSDLKeyboardEvents- :: (Participant ak KeyboardMessage us)- => ak- -> [SDL.EventPayload]- -> Affection us [SDL.EventPayload]+ :: forall ak us. (Participant ak us, Mesg ak us ~ KeyboardMessage)+ => ak -- ^ The message system participant+ -> [SDL.EventPayload] -- ^ Incoming events+ -> Affection us [SDL.EventPayload] -- ^ Leftover SDL Events consumeSDLKeyboardEvents ak = doConsume where doConsume [] = return []
src/Affection/Subsystems/AffectionMouse.hs view
@@ -1,26 +1,29 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-} module Affection.Subsystems.AffectionMouse where import Affection.MessageBus-import Affection.Subsystems.Class import Affection.Types import Affection.Util -import Control.Monad.IO.Class (liftIO)-import Control.Concurrent.STM- import Linear.Affine (unP) import qualified SDL +-- | Helper funtion that consumes all Mouse-related 'SDL.Eventpayload's+-- and emits appropriate 'MouseMessage's consumeSDLMouseEvents- :: (Participant am MouseMessage us)- => am- -> [SDL.EventPayload]- -> Affection us [SDL.EventPayload]+ :: forall am us. (Participant am us, Mesg am us ~ MouseMessage)+ => am -- ^ The message system participant+ -> [SDL.EventPayload] -- ^ Incoming events+ -> Affection us [SDL.EventPayload] -- ^ Leftover SDL events consumeSDLMouseEvents am = doConsume where+ doConsume+ :: [SDL.EventPayload]+ -> Affection us [SDL.EventPayload] doConsume [] = return [] doConsume (e:es) = do ts <- getElapsedTime@@ -39,6 +42,7 @@ partEmit am (MsgMouseButton ts (SDL.mouseButtonEventWindow dat)+ (SDL.mouseButtonEventMotion dat) (SDL.mouseButtonEventWhich dat) (SDL.mouseButtonEventButton dat) (SDL.mouseButtonEventClicks dat)
src/Affection/Subsystems/AffectionWindow.hs view
@@ -1,25 +1,30 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE AllowAmbiguousTypes #-} module Affection.Subsystems.AffectionWindow where import Affection.Types import Affection.Util import Affection.MessageBus-import Affection.Subsystems.Class -import Control.Concurrent.STM as STM-import Control.Monad.IO.Class (liftIO)- import qualified SDL +-- | Helper function that consumes all Window-related 'SDL.EventPayload's+-- and emits appropriate 'WindowMessage's. consumeSDLWindowEvents- :: (Participant aw WindowMessage us)- => aw- -> [SDL.EventPayload]- -> Affection us [SDL.EventPayload]+ :: forall aw us. (Participant aw us, Mesg aw us ~ WindowMessage)+ => aw -- ^ The message system participant+ -> [SDL.EventPayload] -- ^ Incoming events+ -> Affection us [SDL.EventPayload] -- ^ Leftover SDL events consumeSDLWindowEvents aw = doConsume where+ doConsume+ :: (Mesg aw us ~ WindowMessage)+ => [SDL.EventPayload]+ -> Affection us [SDL.EventPayload] doConsume [] = return [] doConsume (e:es) = do ts <- getElapsedTime@@ -39,8 +44,8 @@ SDL.WindowResizedEvent (SDL.WindowResizedEventData window newSize) -> do partEmit aw (MsgWindowResize ts window newSize) doConsume es- SDL.WindowSizeChangedEvent (SDL.WindowSizeChangedEventData window) -> do- partEmit aw (MsgWindowSizeChange ts window)+ SDL.WindowSizeChangedEvent (SDL.WindowSizeChangedEventData window size) -> do+ partEmit aw (MsgWindowSizeChange ts window size) doConsume es SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) -> do partEmit aw (MsgWindowMinimize ts window)
src/Affection/Subsystems/Class.hs view
@@ -3,9 +3,11 @@ module Affection.Subsystems.Class where import Affection.Types-import Affection.MessageBus import qualified SDL +-- | This class denotes a Subsystem to be part of SDL class SDLSubsystem s us where+ -- | Consume the given 'SDL.EventPayload's and return only those not+ -- recognised consumeSDLEvents :: s -> [SDL.EventPayload] -> Affection us [SDL.EventPayload]
src/Affection/Types.hs view
@@ -1,57 +1,32 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-}-module Affection.Types- -- ( Affection- -- , AffectionData(..)- -- , AffectionConfig(..)- -- , AffectionState(..)- -- -- , AffectionDraw(..)- -- -- , Draw(..)- -- , AffectionStateInner- -- -- , AffectionDrawInner(..)- -- , InitComponents(..)- -- -- , Loop(..)- -- -- , RGBA(..)- -- , DrawType(..)- -- , DrawRequest(..)- -- , RequestPersist(..)- -- , Angle(..)- -- -- , ConvertAngle(..)- -- -- | Particle system- -- , Particle(..)- -- , ParticleSystem(..)- -- , ParticleStorage(..)- -- -- | Convenience exports- -- , liftIO- -- , SDL.WindowConfig(..)- -- , SDL.defaultWindow- -- -- | GEGL reexports- -- , G.GeglRectangle(..)- -- , G.GeglBuffer(..)- -- )- where+module Affection.Types + ( Affection+ , AffectionConfig(..)+ , AffectionData(..)+ , AffectionStateInner+ , AffectionState(..)+ , InitComponents(..)+ , Angle+ -- | SDL reexports+ , SDL.WindowConfig(..)+ , SDL.WindowMode(..)+ , SDL.EventPayload(..)+ , SDL.InitFlag(..)+ , SDL.Window+ , SDL.GLContext+ ) where import qualified SDL.Init as SDL import qualified SDL.Video as SDL import qualified SDL.Event as SDL import qualified Data.Text as T-import Data.Map.Strict as M --- import qualified GEGL as G--- import qualified BABL as B- import Control.Monad.IO.Class-import Control.Monad.State+import Control.Monad.State.Strict import qualified Control.Monad.Parallel as MP import System.Clock (TimeSpec)--- import Control.Monad.Reader --- import Control.Concurrent.MVar--import Foreign.Ptr (Ptr)--import Affection.MessageBus.Message- -- | Configuration for the aplication. needed at startup. data AffectionConfig us = AffectionConfig { initComponents :: InitComponents@@ -85,42 +60,19 @@ -- | Main type for defining the look, feel and action of the whole application. data AffectionData us = AffectionData- -- { affectionConfig :: AffectionConfig us -- ^ Application configuration. { quitEvent :: Bool -- ^ Loop breaker. , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window , glContext :: SDL.GLContext -- ^ OpenGL rendering context- , windowRenderer :: SDL.Renderer -- ^ Internal renderer of window- , drawTexture :: SDL.Texture -- ^ SDL Texture to draw to- -- , drawFormat :: B.BablFormatPtr -- ^ Target format , screenMode :: SDL.WindowMode -- ^ current screen mode- -- , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed , drawDimensions :: (Int, Int) -- ^ Dimensions of target surface- , drawStride :: Int -- ^ Stride of target buffer- , drawCPP :: Int -- ^ Number of components per pixel , elapsedTime :: Double -- ^ Elapsed time in seconds , deltaTime :: Double -- ^ Elapsed time in seconds since last tick , sysTime :: TimeSpec -- ^ System time (NOT the time on the clock) , pausedTime :: Bool -- ^ Should the update loop be executed?- -- , messageChannel :: Channel msg -- ^ The main broadcast channel to duplicate all others from } --- -- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated--- data DrawRequest = DrawRequest--- { requestArea :: G.GeglRectangle -- ^ The area to update--- , requestBuffer :: G.GeglBuffer -- ^ Buffer to draw--- , requestPersist :: RequestPersist -- ^ Shall the drawRequest persist--- }--- --- data RequestPersist--- = Persist--- | Kill (Maybe G.GeglNode)--- --- -- | A type for storing 'DrawRequest' results to be executed frequently. TODO--- data DrawAsset = DrawAsset- -- | Inner 'StateT' monad for the update state--- type AffectionStateInner us m a = StateT (AffectionData us) m a type AffectionStateInner us a = StateT us a -- | Affection's state monad@@ -132,102 +84,4 @@ type Affection us a = AffectionState (AffectionData us) IO a --- -- | Inner 'StateT' monad of Affection--- type AffectionInner us od a = StateT (AffectionState us od) IO a--- --- -- | Affection state monad--- newtype Affection us od a = Affection--- { runAffection :: AffectionInner us od a }--- deriving (Functor, Applicative, Monad, MonadState (AffectionState us od))--- --- -- | Inner drawing monad of Affection.--- type AffectionDrawInner ds a = ReaderT (Draw ds) a--- --- -- | Affectiondrawinf reader monad.--- newtype AffectionDraw ds a = AffectionDraw--- { runDraw :: (ds -> a) }--- deriving (Functor, Applicative, Monad, MonadReader ds)--- --- -- | Loop state monad to hold elapsed time per frame--- newtype Loop f a = Loop--- { runLoop :: f -> (a, f) }--- deriving (Functor, Applicative, Monad, MonadState (Loop f))---- data RGBA = RGBA--- { r :: Int--- , g :: Int--- , b :: Int--- , a :: Int--- }---- | Type for defining the draw type of draw functions-data DrawType- -- | Fill the specified area completely with color- = Fill- -- | only draw the outline of the area- | Line- { lineWidth :: Int -- ^ Width of line in pixels- }- type Angle = Double---- -- | Type for defining angles--- data Angle--- = Rad Double -- ^ Angle in radians--- | Deg Double -- ^ Angle in degrees--- deriving (Show)--- --- -- | Typeclass for converting Angles from 'Deg' to 'Rad' and vice versa.--- class ConvertAngle a where--- toRad :: a -> a -- Convert to 'Rad'--- toDeg :: a -> a -- Convert to 'Deg'--- --- instance ConvertAngle Angle where--- toRad (Deg x) = Rad $ x * pi / 180--- toRad x = x--- --- toDeg (Rad x) = Deg $ x * 180 / pi--- toDeg x = x--- --- instance Eq Angle where--- (==) (Deg x) (Deg y) = x == y--- (==) (Rad x) (Rad y) = x == y--- (==) dx@(Deg _) ry@(Rad _) = dx == toDeg ry--- (==) rx@(Rad _) dy@(Deg _) = toDeg rx == dy---- -- | A single particle--- data Particle = Particle--- { particleTimeToLive :: Double--- -- ^ Time to live in seconds--- , particleCreation :: Double--- -- ^ Creation time of particle in seconds form program start--- , particlePosition :: (Double, Double)--- -- ^ Position of particle on canvas--- , particleRotation :: Angle--- -- ^ Particle rotation--- , particleVelocity :: (Int, Int)--- -- ^ particle velocity as vector of pixels per second--- , particlePitchRate :: Angle--- -- ^ Rotational velocity of particle in angle per second--- , particleRootNode :: G.GeglNode--- -- ^ Root 'G.GeglNode' of 'Particle'--- , particleNodeGraph :: Map String G.GeglNode--- -- ^ Node Graph of 'G.GeglNodes' per particle--- , particleStackCont :: G.GeglNode--- -- ^ 'G.GeglNode' to connect other 'Particle's to--- , particleDrawFlange :: G.GeglNode--- -- ^ 'G.GeglNode' to connect draw actions to--- } deriving (Eq)--- --- -- | The particle system--- data ParticleSystem = ParticleSystem--- { partSysParts :: ParticleStorage--- , partSysNode :: G.GeglNode--- , partSysBuffer :: G.GeglBuffer--- }--- --- -- | The particle storage datatype--- data ParticleStorage = ParticleStorage--- { partStorLatest :: Maybe Particle -- ^ The particle stored last--- , partStorList :: [Particle] -- ^ List of particles in ascending order of remaining lifetime--- }
src/Affection/Util.hs view
@@ -1,25 +1,25 @@-module Affection.Util-where+module Affection.Util where import Affection.Types import Affection.Logging+import Affection.MessageBus.Message.WindowMessage +import SDL (($=)) import qualified SDL+import qualified Graphics.Rendering.OpenGL as GL import System.Clock import Control.Monad.State --- Prehandle SDL events in case any window events occur+-- | Prehandle SDL events preHandleEvents :: [SDL.Event] -> Affection us [SDL.EventPayload] preHandleEvents evs = return $ map SDL.eventPayload evs -- | Return the userstate to the user getAffection :: Affection us us-getAffection = do- ad <- get- return $ userState ad+getAffection = gets userState -- | Put altered user state back putAffection@@ -38,13 +38,13 @@ -- | Get time since start but always the same in the current tick. getElapsedTime :: Affection us Double-getElapsedTime =- elapsedTime <$> get+getElapsedTime = gets elapsedTime +-- | Get delta time (time elapsed from last frame) getDelta :: Affection us Double-getDelta =- deltaTime <$> get+getDelta = gets deltaTime +-- | Quit the engine loop quit :: Affection us () quit = do ad <- get@@ -63,10 +63,29 @@ SDL.setWindowMode (drawWindow ad) SDL.Windowed return SDL.Windowed x -> do- liftIO $ logIO Warn ("Unknown Screen mode: " ++ show x)+ liftIO $ logIO Warn ("Unexpected Screen mode: " ++ show x) return x now <- liftIO $ getTime Monotonic put ad { sysTime = now , screenMode = newMode }++-- | Fit the GL Viewport to Window size+fitViewport+ :: Double -- ^ Image Ratio (width / height)+ -> WindowMessage -- ^ Incoming Message. Listens only on+ -- 'MsgWindowResize' and ignores all others.+ -> Affection us ()+fitViewport ratio (MsgWindowResize _ _ (SDL.V2 w h)) = do+ liftIO $ logIO Verbose "Fitting Viewport to size"+ if (fromIntegral w / fromIntegral h) > ratio+ then do+ let nw = floor (fromIntegral h * ratio)+ dw = floor ((fromIntegral w - fromIntegral nw) / 2 :: Double)+ GL.viewport $= (GL.Position dw 0, GL.Size nw h)+ else do+ let nh = floor (fromIntegral w / ratio)+ dh = floor ((fromIntegral h - fromIntegral nh) / 2 :: Double)+ GL.viewport $= (GL.Position 0 dh, GL.Size w nh)+fitViewport _ _ = return ()