Compare commits

...

57 commits

Author SHA1 Message Date
d0465fa3e1 small update of flake and project URL 2023-11-24 18:42:15 +01:00
Amedeo Molnár
523265b2dc rework flake to include haskell build inputs 2023-04-20 10:33:18 +02:00
Amedeo Molnár
f92949b7f0 make threaded 2023-04-20 10:32:51 +02:00
1976a6c4c1 add cabal.project 2023-04-20 07:05:50 +02:00
086eb3e1f2 flake update 2023-04-20 06:47:54 +02:00
818a1e424c add .envrc 2023-04-20 06:37:05 +02:00
445b5807e0 ignore result symlink 2023-03-25 21:51:50 +01:00
f7e7c9dcc8 update 2023-03-25 13:40:07 +01:00
5bef189c30 get working flake 2022-12-01 19:56:25 +01:00
efbb9b4c5e remove ballast 2022-12-01 16:34:40 +01:00
76225dff4b more flakyness 2022-12-01 16:23:43 +01:00
eda79eb626 fix flake 2022-12-01 15:45:21 +01:00
04d6905de1 remove ballast 2022-12-01 15:45:07 +01:00
b56ed86e45 revert 2022-09-14 19:30:22 +02:00
af0dcc16da fix typo 2022-09-14 19:29:46 +02:00
81a76d4eb3 flakify 2022-09-14 18:53:44 +02:00
1b8d754cd6 introduce resourcet 2022-07-10 04:18:22 +02:00
60d38217c8 hunting warnings and other nasties 2022-07-09 23:26:37 +02:00
17978b0bd7 deconfuse the haskell language server 2022-07-09 22:16:34 +02:00
9b7b1c6167 fix unexpected eventlessness 2022-05-13 16:13:12 +02:00
2696dedbb4 some little updates 2022-05-12 19:07:08 +02:00
eb3357cfac remove double export 2021-01-11 12:01:24 +01:00
4a5cc6d0e0 make affection strict 2021-01-03 18:21:12 +01:00
e81408e54f notes turned obsolete. modular design preferred. 2020-12-07 00:42:33 +01:00
ccd8b7eeaf more verbose messages from engine on startup 2020-12-05 14:28:57 +01:00
cd662da0b7 move gl call after context creation 2020-10-16 06:06:39 +02:00
bca6fd69e1 typo 2020-10-16 06:00:30 +02:00
2ea955d37d whopsie 2020-10-15 19:45:30 +02:00
ef1ba1f988 add package 2020-10-15 19:39:32 +02:00
3332370012 add sync and GL infos 2020-10-15 18:49:04 +02:00
fc6bfa1ca5 fix state machine 2020-05-04 21:31:42 +02:00
3b9a366830 update notes 2020-05-04 13:42:30 +02:00
0aa971a343 add project file 2020-05-04 13:41:47 +02:00
bc08ca55a4 hunt warnings 2020-05-04 13:41:21 +02:00
8b26781217 bump version 2020-05-04 13:08:27 +02:00
f52e94e82a made example02 affectionate and working! 2020-05-04 13:06:36 +02:00
8785405420 removed unneeded code 2020-05-04 13:06:10 +02:00
d6804f95fb update screenmode toggle function 2020-05-04 11:00:49 +02:00
a7eac3e392 make example01 affectionate and working 2020-05-04 08:23:30 +02:00
9ac25d5fca fix dt computation 2020-05-04 08:23:03 +02:00
7df3bb8cf6 make example00 affectionate and working! 2020-05-04 06:23:22 +02:00
c8d3ad6c56 add class 2020-05-04 05:44:58 +02:00
8af8193a0a remove userstate reference from state monad 2020-05-04 05:44:19 +02:00
44ec3cc8e0 comments and formatting 2020-05-04 05:26:49 +02:00
786bfe1b21 get Affectionate class to work 2020-05-04 05:17:44 +02:00
13f67c6cd1 start with Affectionate 2020-05-04 01:37:21 +02:00
65d47f7477 back to old build system 2020-05-04 01:28:37 +02:00
e4103e11da modernize 2020-05-04 01:27:44 +02:00
18775515d8 take .gitignore into account 2020-01-05 02:37:44 +01:00
9ece773de8 twiddling the clock 2019-12-20 11:54:33 +01:00
2dda8f9db0 drawback 2019-12-20 11:53:13 +01:00
6d4f7d3227 modernization 2019-12-20 11:51:02 +01:00
d73609297c move logging to text 2019-10-28 17:11:27 +01:00
25fd9acffd push examples to new sdl version 2019-10-28 17:10:56 +01:00
8ff4b216c3 bump sdl2 version 2019-10-28 16:55:26 +01:00
c1ed74bb66
Merge pull request #8 from nek0/multiwindow
Multiwindow
2019-04-29 18:59:44 +02:00
2ac3b007a9 name module 2019-04-29 18:38:52 +02:00
26 changed files with 804 additions and 738 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

2
.gitignore vendored
View file

@ -16,3 +16,5 @@ cabal.project.local*
dist*/
report.html
*.bak
.direnv/
result

View file

@ -6,7 +6,7 @@ name: affection
-- PVP summary: +-+------- breaking API changes
-- | | +----- non-breaking API additions
-- | | | +--- code changes with no API change
version: 0.0.0.9
version: 0.0.0.10
synopsis: A simple Game Engine using SDL
description: This package contains Affection, a simple game engine
written in Haskell using SDL.
@ -16,7 +16,7 @@ homepage: https://github.com/nek0/affection#readme
license: LGPL-3
license-file: LICENSE
author: nek0
maintainer: nek0@chelnok.de
maintainer: nek0@nek0.eu
category: Game
build-type: Simple
extra-source-files: ChangeLog.md
@ -25,11 +25,11 @@ extra-source-files: ChangeLog.md
-- README.
-- extra-source-files:
cabal-version: >=1.10
cabal-version: >=2.0
source-repository head
type: git
location: https://github.com/nek0/affection
location: https://gitea.nek0.eu/nek0/affection.git
flag verbose
description: Enable verbose debug messages
@ -67,6 +67,7 @@ library
exposed-modules: Affection
, Affection.Logging
, Affection.Types
, Affection.Class
, Affection.StateMachine
, Affection.Util
, Affection.MessageBus
@ -84,35 +85,39 @@ library
, Affection.Subsystems.AffectionMouse
, Affection.Subsystems.AffectionJoystick
default-extensions: OverloadedStrings
, TypeFamilies
, AllowAmbiguousTypes
-- Modules included in this library but not exported.
-- other-modules:
-- LANGUAGE extensions used by modules in this package.
other-extensions: GADTs
, KindSignatures
, FlexibleInstances
, MultiParamTypeClasses
, UndecidableInstances
-- other-extensions: GADTs
-- , KindSignatures
-- , FlexibleInstances
-- , MultiParamTypeClasses
-- , UndecidableInstances
hs-source-dirs: src
default-language: Haskell2010
ghc-options: -Wall
ghc-options: -threaded -Wall
-- Other library packages from which modules are imported.
build-depends: base >=4.9 && < 5
, sdl2 >= 2.4 && < 2.5
, sdl2 >= 2.5
, linear
, text
, mtl
, monad-loops
, monad-parallel
, containers
, clock
, clock >= 0.8
, glib
, bytestring
, OpenGL
, OpenGLRaw
, stm
, uuid
, vector
, resourcet
-- This example shows the message system. only makes sense when compiling with
-- verbose flag.
@ -122,12 +127,11 @@ executable example00
ghc-options: -threaded -Wall
default-language: Haskell2010
default-extensions: OverloadedStrings
if flag(examples)
build-depends: base >=4.9 && < 5
, affection
, sdl2
, stm
else
build-depends: base >=4.9 && < 5
, affection
, sdl2 >= 2.5
, stm
if !flag(examples)
buildable: False
-- A small game of life implementation
@ -138,19 +142,18 @@ executable example01
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
build-depends: base >=4.9 && < 5
, affection
, sdl2 >= 2.5
, stm
, OpenGL
, random
, containers
, linear
, matrix
, nanovg >= 0.6.0.0
, deepseq
if !flag(examples)
buildable: False
-- Another small game of life implementation
@ -161,17 +164,16 @@ executable example02
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
build-depends: base >=4.9 && < 5
, affection
, sdl2 >= 2.5
, stm
, OpenGL
, random
, containers
, linear
, matrix
, nanovg >= 0.6.0.0
, deepseq
if !flag(examples)
buildable: False

4
cabal.project Normal file
View file

@ -0,0 +1,4 @@
packages: ./*.cabal
package affection
flags: +examples

View file

@ -1,21 +1,34 @@
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Affection as A
import Control.Concurrent.STM
import Control.Monad.IO.Class (liftIO)
import Control.Monad (when)
import Control.Concurrent.MVar
import Control.Monad
import qualified SDL hiding (Window(..))
import qualified SDL
import Data.Maybe (isJust, fromJust)
import Data.String
data StateData = StateData
{ sdSubs :: Subsystems
, sdJoys :: [SDL.Joystick]
, sdJoys :: MVar [SDL.Joystick]
, doNextStep :: MVar Bool
}
instance Affectionate StateData where
loadState = load
preLoop = pre
handleEvents = handle
update = Main.update
draw = Main.draw
cleanUp = clean
hasNextStep = liftIO . readMVar . doNextStep
data Subsystems = Subsystems
{ subWindow :: Main.Window
, subMouse :: Main.Mouse
@ -23,36 +36,13 @@ data Subsystems = Subsystems
, 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 ())])
newtype Window = Window (TVar [(UUID, WindowMessage -> Affection ())])
newtype Mouse = Mouse (TVar [(UUID, MouseMessage -> Affection ())])
newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection ())])
newtype Joystick = Joystick (TVar [(UUID, JoystickMessage -> Affection ())])
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
instance Participant Main.Window where
type Mesg Main.Window = WindowMessage
partSubscribers (Window t) = generalSubscribers t
@ -60,11 +50,11 @@ instance Participant Main.Window StateData where
partUnSubscribe (Window t) = generalUnSubscribe t
instance SDLSubsystem Main.Window StateData where
instance SDLSubsystem Main.Window where
consumeSDLEvents = consumeSDLWindowEvents
instance Participant Mouse StateData where
type Mesg Mouse StateData = MouseMessage
instance Participant Mouse where
type Mesg Mouse = MouseMessage
partSubscribers (Mouse t) = generalSubscribers t
@ -72,11 +62,11 @@ instance Participant Mouse StateData where
partUnSubscribe (Mouse t) = generalUnSubscribe t
instance SDLSubsystem Mouse StateData where
instance SDLSubsystem Mouse where
consumeSDLEvents = consumeSDLMouseEvents
instance Participant Keyboard StateData where
type Mesg Keyboard StateData = KeyboardMessage
instance Participant Keyboard where
type Mesg Keyboard = KeyboardMessage
partSubscribers (Keyboard t) = generalSubscribers t
@ -84,11 +74,11 @@ instance Participant Keyboard StateData where
partUnSubscribe (Keyboard t) = generalUnSubscribe t
instance SDLSubsystem Keyboard StateData where
instance SDLSubsystem Keyboard where
consumeSDLEvents = consumeSDLKeyboardEvents
instance Participant Joystick StateData where
type Mesg Joystick StateData = JoystickMessage
instance Participant Joystick where
type Mesg Joystick = JoystickMessage
partSubscribers (Joystick t) = generalSubscribers t
@ -96,7 +86,7 @@ instance Participant Joystick StateData where
partUnSubscribe (Joystick t) = generalUnSubscribe t
instance SDLSubsystem Joystick StateData where
instance SDLSubsystem Joystick where
consumeSDLEvents = consumeSDLJoystickEvents
main :: IO ()
@ -109,21 +99,14 @@ main = do
[
( 0
, SDL.defaultWindow
{ SDL.windowOpenGL = Just SDL.defaultOpenGL
{ SDL.windowGraphicsContext = SDL.OpenGLContext SDL.defaultOpenGL
{ SDL.glProfile = SDL.Core SDL.Normal 3 3
}
}
, SDL.Windowed
)
]
, initScreenMode = SDL.Windowed
, canvasSize = Nothing
, loadState = load
, preLoop = pre
, eventLoop = handle
, updateLoop = update
, drawLoop = draw
, cleanUp = clean
}
} :: AffectionConfig StateData
withAffection conf
load :: IO StateData
@ -134,57 +117,80 @@ load =
<*> (Keyboard <$> newTVarIO [])
<*> (Joystick <$> newTVarIO [])
)
<*> return []
<*> newMVar []
<*> newMVar True
pre :: Affection StateData ()
pre = do
sd <- getAffection
_ <- partSubscribe (subKeyboard $ sdSubs sd) exitOnQ
_ <- partSubscribe (subWindow $ sdSubs sd) exitOnWindowClose
_ <- partSubscribe (subJoystick $ sdSubs sd) joyConnectDisconnect
pre :: StateData -> Affection ()
pre sd = do
_ <- partSubscribe (subKeyboard $ sdSubs sd) (exitOnQ (doNextStep sd))
_ <- partSubscribe (subWindow $ sdSubs sd) (exitOnWindowClose (doNextStep sd))
_ <- partSubscribe (subJoystick $ sdSubs sd) (joyConnectDisconnect (sdJoys sd))
return ()
exitOnQ :: KeyboardMessage -> Affection StateData ()
exitOnQ (MsgKeyboardEvent _ _ _ _ sym) =
exitOnQ :: MVar Bool -> KeyboardMessage -> Affection ()
exitOnQ nextStep (MsgKeyboardEvent _ _ _ _ sym) =
case SDL.keysymKeycode sym of
SDL.KeycodeQ -> do
liftIO $ logIO Debug "Yo dog I heard..."
quit
void $ liftIO $ swapMVar nextStep False
_ -> return ()
exitOnWindowClose :: WindowMessage -> Affection StateData ()
exitOnWindowClose wm =
exitOnWindowClose :: MVar Bool -> WindowMessage -> Affection ()
exitOnWindowClose nextStep wm =
case wm of
MsgWindowClose _ _ -> do
liftIO $ logIO Debug "I heard another one..."
quit
void $ liftIO $ swapMVar nextStep False
_ -> return ()
joyConnectDisconnect :: JoystickMessage -> Affection StateData ()
joyConnectDisconnect msg = do
joyConnectDisconnect :: MVar [SDL.Joystick] -> JoystickMessage -> Affection ()
joyConnectDisconnect mvjs 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
}
js <- liftIO $ readMVar mvjs
void $ liftIO $ swapMVar mvjs (fromJust mj : js)
js <- liftIO $ readMVar mvjs
njs <- joystickAutoDisconnect js msg
liftIO $ putMVar mvjs njs
handle :: [SDL.EventPayload] -> Affection StateData ()
handle es = do
(Subsystems a b c d) <- sdSubs <$> getAffection
handle :: StateData -> [SDL.EventPayload] -> Affection ()
handle sd es = do
let (Subsystems a b c d) = sdSubs sd
leftovers <- consumeSDLEvents a
=<< consumeSDLEvents b
=<< consumeSDLEvents c
=<< consumeSDLEvents d es
mapM_ (\e -> liftIO $ logIO Verbose $ "LEFTOVER: " ++ show e) leftovers
mapM_ (\e -> liftIO $ logIO Verbose $ "LEFTOVER: " <> fromString (show e))
leftovers
update _ = return ()
update :: StateData -> Double -> Affection ()
update _ _ = return ()
draw = return ()
draw :: StateData -> Affection ()
draw _ = return ()
clean :: StateData -> IO ()
clean _ = return ()
generalSubscribers
:: TVar [(UUID, msg -> Affection ())]
-> Affection [msg -> Affection ()]
generalSubscribers t = do
subTups <- liftIO $ readTVarIO t
return $ map snd subTups
generalSubscribe
:: TVar [(UUID, msg -> Affection ())]
-> (msg -> Affection())
-> Affection UUID
generalSubscribe t funct = do
uuid <- genUUID
liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)
return uuid
generalUnSubscribe
:: TVar [(UUID, msg -> Affection ())]
-> UUID
-> Affection ()
generalUnSubscribe t uuid =
liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid))

View file

@ -3,30 +3,36 @@
{-# 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.Concurrent.MVar
import Control.Monad
import Control.DeepSeq (deepseq)
import Data.Matrix as M
import qualified Data.Set as S
import Data.String
import System.Random (randomRIO)
import NanoVG hiding (V2(..))
import Linear
import Foreign.C.Types (CInt(..))
-- internal imports
import Types
instance Affectionate UserData where
loadState = load
preLoop = pre
handleEvents = handle
update = Main.update
draw = Main.draw
cleanUp = clean
hasNextStep = liftIO . readMVar . doNextStep
foreign import ccall unsafe "glewInit"
glewInit :: IO CInt
@ -40,23 +46,16 @@ main = do
[
( 0
, SDL.defaultWindow
{ SDL.windowOpenGL = Just SDL.defaultOpenGL
{ SDL.windowGraphicsContext = SDL.OpenGLContext SDL.defaultOpenGL
{ SDL.glProfile = SDL.Core SDL.Normal 3 3
}
, SDL.windowInitialSize = SDL.V2 600 600
, SDL.windowResizable = True
}
, SDL.Windowed
)
]
, initScreenMode = SDL.Windowed
, canvasSize = Nothing
, loadState = load
, preLoop = pre
, eventLoop = handle
, updateLoop = update
, drawLoop = draw
, cleanUp = clean
}
} :: AffectionConfig UserData
withAffection conf
load :: IO UserData
@ -65,156 +64,146 @@ load = do
liftIO $ logIO A.Debug "init GLEW"
_ <- glewInit
liftIO $ logIO A.Debug "making random"
randList <- mapM (\_ -> randomRIO (0,1)) [0..3599]
randList <- mapM (\_ -> randomRIO (0,1)) [0 .. (3599 :: Int)]
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
empty1 <- newTVarIO [] -- ([] :: [(UUID, WindowMessage -> Affection ())])
empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection ())])
step <- newMVar True
(\life food time -> UserData
{ subsystems = Subsystems
(Window empty1)
(Keyboard empty3)
, lifeMat = fullMatrix
, foodMat = fromList 60 60 (repeat 10)
, timeMat = M.zero 60 60
, lifeMat = life
, foodMat = food
, timeMat = time
, nano = nanoCtx
, doNextStep = step
}
)
<$> newMVar fullMatrix
<*> (newMVar (fromList 60 60 (repeat 10)))
<*> (newMVar (M.zero 60 60))
pre :: Affection UserData ()
pre = do
sd <- getAffection
_ <- partSubscribe (subKeyboard $ subsystems sd) exitOnEsc
_ <- partSubscribe (subKeyboard $ subsystems sd) reloadOnR
_ <- partSubscribe (subKeyboard $ subsystems sd) showFPS
_ <- partSubscribe (subKeyboard $ subsystems sd) toggleFullScreen
_ <- partSubscribe (subWindow $ subsystems sd) exitOnWindowClose
_ <- partSubscribe (subWindow $ subsystems sd) (fitViewport (600/600))
now <- getElapsedTime
putAffection sd
pre :: UserData -> Affection ()
pre ud = do
void $ partSubscribe (subKeyboard $ subsystems ud) (exitOnEsc (doNextStep ud))
void $ partSubscribe (subKeyboard $ subsystems ud) (reloadOnR ud)
void $ partSubscribe (subKeyboard $ subsystems ud) showFPS
void $ partSubscribe (subKeyboard $ subsystems ud) toggleFullScreen
void $ partSubscribe (subWindow $ subsystems ud) (exitOnWindowClose (doNextStep ud))
void $ partSubscribe (subWindow $ subsystems ud) (fitViewport (600/600))
toggleFullScreen :: KeyboardMessage -> Affection UserData ()
toggleFullScreen :: KeyboardMessage -> Affection ()
toggleFullScreen (MsgKeyboardEvent _ _ SDL.Pressed False sym)
| SDL.keysymKeycode sym == SDL.KeycodeF11 = toggleScreen
| SDL.keysymKeycode sym == SDL.KeycodeF11 = toggleScreen 0
| otherwise = return ()
toggleFullScreen _ = return ()
exitOnEsc :: KeyboardMessage -> Affection UserData ()
exitOnEsc (MsgKeyboardEvent _ _ SDL.Pressed False sym) =
exitOnEsc :: MVar Bool -> KeyboardMessage -> Affection ()
exitOnEsc step (MsgKeyboardEvent _ _ SDL.Pressed False sym) =
case SDL.keysymKeycode sym of
SDL.KeycodeEscape -> do
liftIO $ logIO A.Debug "Yo dog I heard..."
quit
void $ liftIO $ swapMVar step False
_ -> return ()
exitOnEsc _ = return ()
exitOnEsc _ _ = return ()
reloadOnR :: KeyboardMessage -> Affection UserData ()
reloadOnR (MsgKeyboardEvent _ _ _ _ sym) =
reloadOnR :: UserData -> KeyboardMessage -> Affection ()
reloadOnR ud (MsgKeyboardEvent _ _ _ _ sym) =
case SDL.keysymKeycode sym of
SDL.KeycodeR -> reload
SDL.KeycodeR -> reload ud
_ -> return ()
reloadOnR _ = return ()
reload :: Affection UserData ()
reload = do
ud <- getAffection
now <- getElapsedTime
randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599]
reload :: UserData -> Affection ()
reload ud = do
randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0 .. (3599 :: Int)]
let fullMatrix = fromList 60 60 randList
putAffection ud
{ lifeMat = fullMatrix
, foodMat = fromList 60 60 (repeat 10)
, timeMat = M.zero 60 60
}
void $ liftIO $ swapMVar (lifeMat ud) fullMatrix
void $ liftIO $ swapMVar (foodMat ud) (fromList 60 60 (repeat 10))
void $ liftIO $ swapMVar (timeMat ud) (M.zero 60 60)
showFPS :: KeyboardMessage -> Affection UserData ()
showFPS :: KeyboardMessage -> Affection ()
showFPS (MsgKeyboardEvent _ _ _ _ sym) =
case SDL.keysymKeycode sym of
SDL.KeycodeF -> do
dt <- getDelta
liftIO $ logIO A.Debug $ "FPS: " ++ show (1 / dt)
liftIO $ logIO A.Debug $ "FPS: " <> fromString (show (1 / dt))
_ -> return ()
showFPS _ = return ()
exitOnWindowClose :: WindowMessage -> Affection UserData ()
exitOnWindowClose wm =
exitOnWindowClose :: MVar Bool -> WindowMessage -> Affection ()
exitOnWindowClose step wm =
case wm of
MsgWindowClose _ _ -> do
liftIO $ logIO A.Debug "I heard another one..."
quit
void $ liftIO $ swapMVar step False
_ -> return ()
exitOnWindowClose _ = return ()
handle :: [SDL.EventPayload] -> Affection UserData ()
handle es = do
(Subsystems a b) <- subsystems <$> getAffection
handle :: UserData -> [SDL.EventPayload] -> Affection ()
handle ud es = do
let (Subsystems a b) = subsystems ud
_ <- consumeSDLEvents a =<< consumeSDLEvents b es
return ()
update :: Double -> Affection UserData ()
update _ = do
ud <- getAffection
update :: UserData -> Double -> Affection ()
update ud _ = do
-- liftIO $ logIO A.Debug ("FPS: " <> fromString (show (1/dt)))
pastLife <- liftIO $ readMVar (lifeMat ud)
pastFood <- liftIO $ readMVar (foodMat ud)
pastTime <- liftIO $ readMVar (timeMat ud)
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 60 60 60 60 pastLife <|> submatrix 60 60 1 2 pastLife)
<->
(submatrix 1 2 60 60 (lifeMat ud) <|> submatrix 1 2 1 2 (lifeMat ud))
(submatrix 1 2 60 60 pastLife <|> submatrix 1 2 1 2 pastLife)
| x == 1 && y == 60 =
(submatrix 59 60 60 60 (lifeMat ud) <|> submatrix 59 60 1 2 (lifeMat ud))
(submatrix 59 60 60 60 pastLife <|> submatrix 59 60 1 2 pastLife)
<->
(submatrix 1 1 60 60 (lifeMat ud) <|> submatrix 1 1 1 2 (lifeMat ud))
(submatrix 1 1 60 60 pastLife <|> submatrix 1 1 1 2 pastLife)
| x == 60 && y == 1 =
(submatrix 60 60 59 60 (lifeMat ud) <|> submatrix 60 60 1 1 (lifeMat ud))
(submatrix 60 60 59 60 pastLife <|> submatrix 60 60 1 1 pastLife)
<->
(submatrix 1 2 59 60 (lifeMat ud) <|> submatrix 1 2 1 1 (lifeMat ud))
(submatrix 1 2 59 60 pastLife <|> submatrix 1 2 1 1 pastLife)
| x == 60 && y == 60 =
(submatrix 59 60 59 60 (lifeMat ud) <|> submatrix 59 60 1 1 (lifeMat ud))
(submatrix 59 60 59 60 pastLife <|> submatrix 59 60 1 1 pastLife)
<->
(submatrix 1 1 59 60 (lifeMat ud) <|> submatrix 1 1 1 1 (lifeMat ud))
(submatrix 1 1 59 60 pastLife <|> submatrix 1 1 1 1 pastLife)
| x == 1 =
(submatrix (y - 1) (y + 1) 60 60 (lifeMat ud))
(submatrix (y - 1) (y + 1) 60 60 pastLife)
<|>
(submatrix (y - 1) (y + 1) 1 2 (lifeMat ud))
(submatrix (y - 1) (y + 1) 1 2 pastLife)
| y == 1 =
(submatrix 60 60 (x - 1) (x + 1) (lifeMat ud))
(submatrix 60 60 (x - 1) (x + 1) pastLife)
<->
(submatrix 1 2 (x - 1) (x + 1) (lifeMat ud))
(submatrix 1 2 (x - 1) (x + 1) pastLife)
| x == 60 =
(submatrix (y - 1) (y + 1) 59 60 (lifeMat ud))
(submatrix (y - 1) (y + 1) 59 60 pastLife)
<|>
(submatrix (y - 1) (y + 1) 1 1 (lifeMat ud))
(submatrix (y - 1) (y + 1) 1 1 pastLife)
| y == 60 =
(submatrix 59 60 (x -1 ) (x + 1) (lifeMat ud))
(submatrix 59 60 (x -1 ) (x + 1) pastLife)
<->
(submatrix 1 1 (x - 1) (x + 1) (lifeMat ud))
(submatrix 1 1 (x - 1) (x + 1) pastLife)
| otherwise =
(submatrix (y - 1) (y + 1) (x - 1) (x + 1) (lifeMat ud))
(submatrix (y - 1) (y + 1) (x - 1) (x + 1) pastLife)
life = countLife subm
ret
| life == 0 && lifeMat ud M.! (y, x) == 0 =
( 0
, if timeMat ud M.! (y, x) >= 10
then min 10 (foodMat ud M.! (y, x) + 1)
else min 10 (foodMat ud M.! (y, x))
, timeMat ud M.! (y, x) + 1
)
| otherwise = (1, 1, 1)
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), 0)
else if life == 3 && foodMat ud M.! (y, x) > 0
then return (1, (foodMat ud M.! (y, x)) - 1, 0)
if pastLife M.! (y, x) == 1
then if (life == 2 || life == 3) && pastFood M.! (y, x) > 0
then return (1, (pastFood M.! (y, x)) - 1, 0)
else return (0, pastFood M.! (y, x), 0)
else if life == 3 && pastFood M.! (y, x) > 0
then return (1, (pastFood 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 min 10 (foodMat ud M.! (y, x))
, timeMat ud M.! (y, x) + 1
( (0 :: Word)
, if pastTime M.! (y, x) > 10
then min 10 ((pastFood M.! (y, x)) + 1)
else min 10 (pastFood M.! (y, x))
, pastTime M.! (y, x) + 1
)
) [0..3599]
let newLifeMat = fromList 60 60 (map (\(x, _, _) -> x) newList)
@ -222,22 +211,21 @@ update _ = do
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
}
reload ud
else do
((newLifeMat, newFoodMat, newTimeMat) `deepseq` return ())
void $ liftIO $ swapMVar (lifeMat ud) newLifeMat
void $ liftIO $ swapMVar (timeMat ud) newTimeMat
countLife :: Matrix Word -> Word
countLife mat = res - (mat M.! (2, 2))
where
res = foldr (flip (+)) 0 mat
draw :: Affection UserData ()
draw = do
ud <- getAffection
draw :: UserData -> Affection ()
draw ud = do
life <- liftIO $ readMVar (lifeMat ud)
food <- liftIO $ readMVar (foodMat ud)
liftIO $ do
beginFrame (nano ud) 600 600 1
save (nano ud)
@ -245,7 +233,7 @@ draw = do
let x = coord `mod` 60
y = coord `div` 60
ctx = nano ud
mult = lifeMat ud M.! (x + 1, y + 1)
mult = life M.! (x + 1, y + 1)
-- logIO A.Debug $ show mult
beginPath ctx
rect ctx (fromIntegral $ x * 10) (fromIntegral $ y * 10) 10 10
@ -253,10 +241,11 @@ draw = do
then
fillColor ctx (rgba 255 255 255 255)
else
fillColor ctx (rgba 0 (fromIntegral $ 25 * (foodMat ud M.! (x+1, y+1))) 0 255)
fillColor ctx (rgba 0 (fromIntegral $ 25 * (food M.! (x+1, y+1))) 0 255)
fill ctx
) [0..3599]
restore (nano ud)
endFrame (nano ud)
clean :: UserData -> IO ()
clean _ = return ()

View file

@ -10,14 +10,15 @@ import Data.Matrix as M
import NanoVG
import Control.Concurrent.STM
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent.MVar
data UserData = UserData
{ lifeMat :: Matrix Word
, foodMat :: Matrix Word
, timeMat :: Matrix Word
{ lifeMat :: MVar (Matrix Word)
, foodMat :: MVar (Matrix Word)
, timeMat :: MVar (Matrix Word)
, subsystems :: Subsystems
, nano :: Context
, doNextStep :: MVar Bool
}
data Subsystems = Subsystems
@ -25,12 +26,12 @@ data Subsystems = Subsystems
, subKeyboard :: Types.Keyboard
}
newtype Window = Window (TVar [(UUID, WindowMessage -> Affection UserData ())])
newtype Window = Window (TVar [(UUID, WindowMessage -> Affection ())])
newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection UserData ())])
newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection ())])
instance Participant Types.Window UserData where
type Mesg Types.Window UserData = WindowMessage
instance Participant Types.Window where
type Mesg Types.Window = WindowMessage
partSubscribers (Window t) = do
subTups <- liftIO $ readTVarIO t
@ -40,11 +41,11 @@ instance Participant Types.Window UserData where
partUnSubscribe (Window t) = generalUnSubscribe t
instance SDLSubsystem Types.Window UserData where
instance SDLSubsystem Types.Window where
consumeSDLEvents = consumeSDLWindowEvents
instance Participant Keyboard UserData where
type Mesg Keyboard UserData = KeyboardMessage
instance Participant Keyboard where
type Mesg Keyboard = KeyboardMessage
partSubscribers (Keyboard t) = do
subTups <- liftIO $ readTVarIO t
@ -54,24 +55,24 @@ instance Participant Keyboard UserData where
partUnSubscribe (Keyboard t) = generalUnSubscribe t
instance SDLSubsystem Keyboard UserData where
instance SDLSubsystem Keyboard where
consumeSDLEvents = consumeSDLKeyboardEvents
generalSubscribe
:: TVar [(UUID, msg -> Affection UserData ())]
-> (msg -> Affection UserData ())
-> Affection UserData UUID
:: TVar [(UUID, msg -> Affection ())]
-> (msg -> Affection ())
-> Affection UUID
generalSubscribe t funct = do
uuid <- genUUID
liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)
return uuid
generalUnSubscribe
:: TVar [(UUID, msg -> Affection UserData ())]
:: TVar [(UUID, msg -> Affection ())]
-> UUID
-> Affection UserData ()
-> Affection ()
generalUnSubscribe t uuid =
liftIO $ atomically $ modifyTVar' t (filter (`filterMsg` uuid))
where
filterMsg :: (UUID, msg -> Affection UserData ()) -> UUID -> Bool
filterMsg :: (UUID, msg -> Affection ()) -> UUID -> Bool
filterMsg (u, _) p = u /= p

View file

@ -3,30 +3,36 @@
{-# 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.Concurrent.MVar
import Control.Monad
import Control.DeepSeq (deepseq)
import Data.Matrix as M
import qualified Data.Set as S
import Data.String
import System.Random (randomRIO)
import NanoVG hiding (V2(..))
import Linear
import Foreign.C.Types (CInt(..))
-- internal imports
import Types
instance Affectionate UserData where
loadState = load
preLoop = pre
handleEvents = handle
update = Main.update
draw = Main.draw
cleanUp = clean
hasNextStep = liftIO . readMVar . doNextStep
foreign import ccall unsafe "glewInit"
glewInit :: IO CInt
@ -43,23 +49,16 @@ main = do
[
( 0
, SDL.defaultWindow
{ SDL.windowOpenGL = Just SDL.defaultOpenGL
{ SDL.windowGraphicsContext = SDL.OpenGLContext SDL.defaultOpenGL
{ SDL.glProfile = SDL.Core SDL.Normal 3 3
}
, SDL.windowInitialSize = SDL.V2 600 600
, SDL.windowResizable = True
}
, SDL.Windowed
)
]
, initScreenMode = SDL.Windowed
, canvasSize = Nothing
, loadState = load
, preLoop = pre
, eventLoop = handle
, updateLoop = update
, drawLoop = draw
, cleanUp = clean
}
} :: AffectionConfig UserData
withAffection conf
load :: IO UserData
@ -68,156 +67,145 @@ load = do
liftIO $ logIO A.Debug "init GLEW"
_ <- glewInit
liftIO $ logIO A.Debug "making random"
randList <- mapM (\_ -> randomRIO (0,1)) [0..3599]
randList <- mapM (\_ -> randomRIO (0,1)) [0 .. (3599 :: Int)]
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
empty1 <- newTVarIO [] -- ([] :: [(UUID, WindowMessage -> Affection ())])
empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection ())])
(\life food time nextStep -> UserData
{ subsystems = Subsystems
(Window empty1)
(Keyboard empty3)
, lifeMat = fullMatrix
, foodMat = fromList 60 60 (repeat maxFood)
, timeMat = M.zero 60 60
, lifeMat = life
, foodMat = food
, timeMat = time
, nano = nanoCtx
, doNextStep = nextStep
}
)
<$> newMVar fullMatrix
<*> newMVar (fromList 60 60 (repeat maxFood))
<*> newMVar (M.zero 60 60)
<*> newMVar True
pre :: Affection UserData ()
pre = do
sd <- getAffection
_ <- partSubscribe (subKeyboard $ subsystems sd) exitOnEsc
_ <- partSubscribe (subKeyboard $ subsystems sd) reloadOnR
_ <- partSubscribe (subKeyboard $ subsystems sd) showFPS
_ <- partSubscribe (subKeyboard $ subsystems sd) toggleFullScreen
_ <- partSubscribe (subWindow $ subsystems sd) exitOnWindowClose
_ <- partSubscribe (subWindow $ subsystems sd) (fitViewport (600/600))
now <- getElapsedTime
putAffection sd
pre :: UserData -> Affection ()
pre ud = do
void $ partSubscribe (subKeyboard $ subsystems ud) (exitOnEsc (doNextStep ud))
void $ partSubscribe (subKeyboard $ subsystems ud) (reloadOnR ud)
void $ partSubscribe (subKeyboard $ subsystems ud) showFPS
void $ partSubscribe (subKeyboard $ subsystems ud) toggleFullScreen
void $ partSubscribe (subWindow $ subsystems ud) (exitOnWindowClose (doNextStep ud))
void $ partSubscribe (subWindow $ subsystems ud) (fitViewport (600/600))
toggleFullScreen :: KeyboardMessage -> Affection UserData ()
toggleFullScreen :: KeyboardMessage -> Affection ()
toggleFullScreen (MsgKeyboardEvent _ _ SDL.Pressed False sym)
| SDL.keysymKeycode sym == SDL.KeycodeF11 = toggleScreen
| SDL.keysymKeycode sym == SDL.KeycodeF11 = toggleScreen 0
| otherwise = return ()
toggleFullScreen _ = return ()
exitOnEsc :: KeyboardMessage -> Affection UserData ()
exitOnEsc (MsgKeyboardEvent _ _ SDL.Pressed False sym) =
exitOnEsc :: MVar Bool -> KeyboardMessage -> Affection ()
exitOnEsc step (MsgKeyboardEvent _ _ SDL.Pressed False sym) =
case SDL.keysymKeycode sym of
SDL.KeycodeEscape -> do
liftIO $ logIO A.Debug "Yo dog I heard..."
quit
void $ liftIO $ swapMVar step False
_ -> return ()
exitOnEsc _ = return ()
exitOnEsc _ _ = return ()
reloadOnR :: KeyboardMessage -> Affection UserData ()
reloadOnR (MsgKeyboardEvent _ _ _ _ sym) =
reloadOnR :: UserData -> KeyboardMessage -> Affection ()
reloadOnR ud (MsgKeyboardEvent _ _ _ _ sym) =
case SDL.keysymKeycode sym of
SDL.KeycodeR -> reload
SDL.KeycodeR -> reload ud
_ -> return ()
reloadOnR _ = return ()
reload :: Affection UserData ()
reload = do
ud <- getAffection
now <- getElapsedTime
randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599]
reload :: UserData -> Affection ()
reload ud = do
randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0 .. (3599 :: Int)]
let fullMatrix = fromList 60 60 randList
putAffection ud
{ lifeMat = fullMatrix
, foodMat = fromList 60 60 (repeat maxFood)
, timeMat = M.zero 60 60
}
void $ liftIO $ swapMVar (lifeMat ud) fullMatrix
void $ liftIO $ swapMVar (foodMat ud) (fromList 60 60 (repeat maxFood))
void $ liftIO $ swapMVar (timeMat ud) (M.zero 60 60)
showFPS :: KeyboardMessage -> Affection UserData ()
showFPS :: KeyboardMessage -> Affection ()
showFPS (MsgKeyboardEvent _ _ _ _ sym) =
case SDL.keysymKeycode sym of
SDL.KeycodeF -> do
dt <- getDelta
liftIO $ logIO A.Debug $ "FPS: " ++ show (1 / dt)
liftIO $ logIO A.Debug $ "FPS: " <> fromString (show (1 / dt))
_ -> return ()
showFPS _ = return ()
exitOnWindowClose :: WindowMessage -> Affection UserData ()
exitOnWindowClose wm =
exitOnWindowClose :: MVar Bool -> WindowMessage -> Affection ()
exitOnWindowClose step wm =
case wm of
MsgWindowClose _ _ -> do
liftIO $ logIO A.Debug "I heard another one..."
quit
void $ liftIO $ swapMVar step False
_ -> return ()
exitOnWindowClose _ = return ()
handle :: [SDL.EventPayload] -> Affection UserData ()
handle es = do
(Subsystems a b) <- subsystems <$> getAffection
handle :: UserData -> [SDL.EventPayload] -> Affection ()
handle ud es = do
let (Subsystems a b) = subsystems ud
_ <- consumeSDLEvents a =<< consumeSDLEvents b es
return ()
update :: Double -> Affection UserData ()
update _ = do
ud <- getAffection
update :: UserData -> Double -> Affection ()
update ud _ = do
pastLife <- liftIO $ readMVar (lifeMat ud)
pastFood <- liftIO $ readMVar (foodMat ud)
pastTime <- liftIO $ readMVar (timeMat ud)
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 60 60 60 60 pastLife <|> submatrix 60 60 1 2 pastLife)
<->
(submatrix 1 2 60 60 (lifeMat ud) <|> submatrix 1 2 1 2 (lifeMat ud))
(submatrix 1 2 60 60 pastLife <|> submatrix 1 2 1 2 pastLife)
| x == 1 && y == 60 =
(submatrix 59 60 60 60 (lifeMat ud) <|> submatrix 59 60 1 2 (lifeMat ud))
(submatrix 59 60 60 60 pastLife <|> submatrix 59 60 1 2 pastLife)
<->
(submatrix 1 1 60 60 (lifeMat ud) <|> submatrix 1 1 1 2 (lifeMat ud))
(submatrix 1 1 60 60 pastLife <|> submatrix 1 1 1 2 pastLife)
| x == 60 && y == 1 =
(submatrix 60 60 59 60 (lifeMat ud) <|> submatrix 60 60 1 1 (lifeMat ud))
(submatrix 60 60 59 60 pastLife <|> submatrix 60 60 1 1 pastLife)
<->
(submatrix 1 2 59 60 (lifeMat ud) <|> submatrix 1 2 1 1 (lifeMat ud))
(submatrix 1 2 59 60 pastLife <|> submatrix 1 2 1 1 pastLife)
| x == 60 && y == 60 =
(submatrix 59 60 59 60 (lifeMat ud) <|> submatrix 59 60 1 1 (lifeMat ud))
(submatrix 59 60 59 60 pastLife <|> submatrix 59 60 1 1 pastLife)
<->
(submatrix 1 1 59 60 (lifeMat ud) <|> submatrix 1 1 1 1 (lifeMat ud))
(submatrix 1 1 59 60 pastLife <|> submatrix 1 1 1 1 pastLife)
| x == 1 =
(submatrix (y - 1) (y + 1) 60 60 (lifeMat ud))
(submatrix (y - 1) (y + 1) 60 60 pastLife)
<|>
(submatrix (y - 1) (y + 1) 1 2 (lifeMat ud))
(submatrix (y - 1) (y + 1) 1 2 pastLife)
| y == 1 =
(submatrix 60 60 (x - 1) (x + 1) (lifeMat ud))
(submatrix 60 60 (x - 1) (x + 1) pastLife)
<->
(submatrix 1 2 (x - 1) (x + 1) (lifeMat ud))
(submatrix 1 2 (x - 1) (x + 1) pastLife)
| x == 60 =
(submatrix (y - 1) (y + 1) 59 60 (lifeMat ud))
(submatrix (y - 1) (y + 1) 59 60 pastLife)
<|>
(submatrix (y - 1) (y + 1) 1 1 (lifeMat ud))
(submatrix (y - 1) (y + 1) 1 1 pastLife)
| y == 60 =
(submatrix 59 60 (x -1 ) (x + 1) (lifeMat ud))
(submatrix 59 60 (x -1 ) (x + 1) pastLife)
<->
(submatrix 1 1 (x - 1) (x + 1) (lifeMat ud))
(submatrix 1 1 (x - 1) (x + 1) pastLife)
| otherwise =
(submatrix (y - 1) (y + 1) (x - 1) (x + 1) (lifeMat ud))
(submatrix (y - 1) (y + 1) (x - 1) (x + 1) pastLife)
life = countLife subm
ret
| life == 0 && lifeMat ud M.! (y, x) == 0 =
( 0
, if timeMat ud M.! (y, x) >= 10
then min 10 (foodMat ud M.! (y, x) + 1)
else min 10 (foodMat ud M.! (y, x))
, timeMat ud M.! (y, x) + 1
)
| otherwise = (1, 1, 1)
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), 0)
else if life == 3 && foodMat ud M.! (y, x) > 0
then return (1, (foodMat ud M.! (y, x)) - 1, 0)
if pastLife M.! (y, x) == 1
then if (life == 2 || life == 3) && pastFood M.! (y, x) > 0
then return (1, (pastFood M.! (y, x)) - 1, 0)
else return (0, pastFood M.! (y, x), 0)
else if life == 3 && pastFood M.! (y, x) > 0
then return (1, (pastFood M.! (y, x)) - 1, 0)
else return
( 0
, if timeMat ud M.! (y, x) > 10
then min maxFood ((foodMat ud M.! (y, x)) + 1)
else min maxFood (foodMat ud M.! (y, x))
, timeMat ud M.! (y, x) + 1
( 0 :: Word
, if pastTime M.! (y, x) > 10
then min maxFood ((pastFood M.! (y, x)) + 1)
else min maxFood (pastFood M.! (y, x))
, pastTime M.! (y, x) + 1
)
) [0..3599]
let newLifeMat = fromList 60 60 (map (\(x, _, _) -> x) newList)
@ -225,41 +213,41 @@ update _ = do
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
}
reload ud
else do
((newLifeMat, newFoodMat, newTimeMat) `deepseq` return ())
void $ liftIO $ swapMVar (lifeMat ud) newLifeMat
void $ liftIO $ swapMVar (timeMat ud) newTimeMat
void $ liftIO $ swapMVar (foodMat ud) newFoodMat
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
draw :: UserData -> Affection ()
draw ud = liftIO $ do
pastLife <- readMVar (lifeMat ud)
pastFood <- readMVar (foodMat ud)
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)
multiplicator = pastLife 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
if multiplicator == 1
then
fillColor ctx (rgba 255 255 255 255)
else
fillColor ctx (rgba 0 (fromIntegral $ (255 `div` maxFood) * (foodMat ud M.! (x+1, y+1))) 0 255)
fillColor ctx (rgba 0 (fromIntegral $ (255 `div` maxFood) * (pastFood M.! (x+1, y+1))) 0 255)
fill ctx
) [0..3599]
restore (nano ud)
endFrame (nano ud)
clean :: UserData -> IO ()
clean _ = return ()

View file

@ -10,14 +10,15 @@ import Data.Matrix as M
import NanoVG
import Control.Concurrent.STM
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent.MVar
data UserData = UserData
{ lifeMat :: Matrix Word
, foodMat :: Matrix Word
, timeMat :: Matrix Word
{ lifeMat :: MVar (Matrix Word)
, foodMat :: MVar (Matrix Word)
, timeMat :: MVar (Matrix Word)
, subsystems :: Subsystems
, nano :: Context
, doNextStep :: MVar Bool
}
data Subsystems = Subsystems
@ -25,12 +26,12 @@ data Subsystems = Subsystems
, subKeyboard :: Types.Keyboard
}
newtype Window = Window (TVar [(UUID, WindowMessage -> Affection UserData ())])
newtype Window = Window (TVar [(UUID, WindowMessage -> Affection ())])
newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection UserData ())])
newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection ())])
instance Participant Types.Window UserData where
type Mesg Types.Window UserData = WindowMessage
instance Participant Types.Window where
type Mesg Types.Window = WindowMessage
partSubscribers (Window t) = do
subTups <- liftIO $ readTVarIO t
@ -40,11 +41,11 @@ instance Participant Types.Window UserData where
partUnSubscribe (Window t) = generalUnSubscribe t
instance SDLSubsystem Types.Window UserData where
instance SDLSubsystem Types.Window where
consumeSDLEvents = consumeSDLWindowEvents
instance Participant Keyboard UserData where
type Mesg Keyboard UserData = KeyboardMessage
instance Participant Keyboard where
type Mesg Keyboard = KeyboardMessage
partSubscribers (Keyboard t) = do
subTups <- liftIO $ readTVarIO t
@ -54,24 +55,24 @@ instance Participant Keyboard UserData where
partUnSubscribe (Keyboard t) = generalUnSubscribe t
instance SDLSubsystem Keyboard UserData where
instance SDLSubsystem Keyboard where
consumeSDLEvents = consumeSDLKeyboardEvents
generalSubscribe
:: TVar [(UUID, msg -> Affection UserData ())]
-> (msg -> Affection UserData ())
-> Affection UserData UUID
:: TVar [(UUID, msg -> Affection ())]
-> (msg -> Affection ())
-> Affection UUID
generalSubscribe t funct = do
uuid <- genUUID
liftIO $ atomically $ modifyTVar' t ((uuid, funct) :)
return uuid
generalUnSubscribe
:: TVar [(UUID, msg -> Affection UserData ())]
:: TVar [(UUID, msg -> Affection ())]
-> UUID
-> Affection UserData ()
-> Affection ()
generalUnSubscribe t uuid =
liftIO $ atomically $ modifyTVar' t (filter (`filterMsg` uuid))
where
filterMsg :: (UUID, msg -> Affection UserData ()) -> UUID -> Bool
filterMsg :: (UUID, msg -> Affection ()) -> UUID -> Bool
filterMsg (u, _) p = u /= p

60
flake.lock Normal file
View file

@ -0,0 +1,60 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1700844339,
"narHash": "sha256-VRlPYk0eRn1ao75hoA99LGehYhxZH5RlkP1YXpVROHM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "33576fdfce2d11204067d6cfa99a2f990ef2169a",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

54
flake.nix Normal file
View file

@ -0,0 +1,54 @@
{
description = "A game stub written in Haskell";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
haskellPackages = pkgs.haskellPackages;
jailbreakUnbreak = pkg:
pkgs.haskell.lib.doJailbreak (pkg.overrideAttrs (_: { meta = { }; }));
packageName = "affection";
in rec {
packages.${packageName} = # (ref:haskell-package-def)
haskellPackages.callCabal2nixWithOptions packageName self "--flag examples" {
# Dependency overrides go here
};
defaultPackage = self.packages.${system}.${packageName};
apps = {
example00 = {
type = "app";
program = "${self.packages.${system}.${packageName}}/bin/example00";
};
example01 = {
type = "app";
program = "${self.packages.${system}.${packageName}}/bin/example01";
};
example02 = {
type = "app";
program = "${self.packages.${system}.${packageName}}/bin/example02";
};
};
devShell = haskellPackages.shellFor {
packages = p: [ defaultPackage ];
withHoogle = true;
buildInputs = with haskellPackages; [
haskell-language-server
ghcid
cabal-install
] ++ (builtins.attrValues self.packages.${system});
};
});
}

View file

@ -1,9 +0,0 @@
# TODO
Implement following things in approximately this Order:
* Message bus
* Console
* GUI
* Framework
* Everything else

124
shell.nix
View file

@ -1,123 +1,7 @@
{ nixpkgs ? import <nixpkgs> {}, compiler ? "default", doBenchmark ? false }:
{ pkgs ? import <nixpkgs> {}}:
let
inherit (nixpkgs) pkgs;
nanovgNeko = with haskellPackages; callPackage (
{ mkDerivation, base, bytestring, c2hs, containers, glew
, hspec, inline-c, libGL, libGLU, QuickCheck, stdenv, text, vector
}:
mkDerivation {
pname = "nanovg";
version = "0.6.0.0";
src = ../nanovg-hs;
isLibrary = true;
isExecutable = true;
doCheck = false;
libraryHaskellDepends = [ base bytestring containers text vector ];
librarySystemDepends = [ glew libGL libGLU ];
libraryPkgconfigDepends = [ glew ];
libraryToolDepends = [ c2hs ];
testHaskellDepends = [ base containers hspec inline-c QuickCheck ];
homepage = "https://github.com/cocreature/nanovg-hs";
description = "Haskell bindings for nanovg";
license = stdenv.lib.licenses.isc;
}) {};
glib = with haskellPackages; callPackage(
{ mkDerivation, base, bytestring, Cabal, containers, glib
, stdenv, text, utf8-string
}:
mkDerivation {
pname = "glib";
version = "0.13.6.0";
sha256 = "4e71062c6a458440294d820e21449aa4666deed2ea233ef5915da7c1d4aee8eb";
setupHaskellDepends = [ base Cabal gtk2hs-buildtools ];
libraryHaskellDepends = [
base bytestring containers text utf8-string
];
libraryPkgconfigDepends = [ pkgs.glib ];
homepage = "http://projects.haskell.org/gtk2hs/";
description = "Binding to the GLIB library for Gtk2Hs";
license = stdenv.lib.licenses.lgpl21;
}) {};
gtk2hs-buildtools = with haskellPackages; callPackage(
{ mkDerivation, alex, array, base, Cabal, containers, directory
, filepath, happy, hashtables, pretty, process, random, stdenv
}:
mkDerivation {
pname = "gtk2hs-buildtools";
version = "0.13.4.0";
src = ../gtk2hs/tools;
isLibrary = true;
isExecutable = true;
enableSeparateDataOutput = true;
libraryHaskellDepends = [
array base Cabal containers directory filepath hashtables pretty
process random
];
libraryToolDepends = [ alex happy ];
executableHaskellDepends = [ base ];
homepage = "http://projects.haskell.org/gtk2hs/";
description = "Tools to build the Gtk2Hs suite of User Interface libraries";
license = stdenv.lib.licenses.gpl2;
}) {};
sdl2Nek0 = with haskellPackages; callPackage(
{ mkDerivation, base, bytestring, deepseq, exceptions, linear, SDL2
, StateVar, stdenv, text, transformers, vector, weigh
}:
mkDerivation {
pname = "sdl2";
version = "2.4.1.0";
sha256 = "21a569c0c19f8ff2bbe1cf1d3eb32f65e8143806de353cedd240df5e9d088b5c";
isLibrary = true;
isExecutable = true;
enableSeparateDataOutput = true;
libraryHaskellDepends = [
base bytestring exceptions linear StateVar text transformers vector
];
librarySystemDepends = [ SDL2 ];
libraryPkgconfigDepends = [ SDL2 ];
testHaskellDepends = [ base deepseq linear vector weigh ];
doCheck = false;
description = "Both high- and low-level bindings to the SDL library (version 2.0.4+).";
license = stdenv.lib.licenses.bsd3;
}) {};
f = { mkDerivation, base, bytestring, clock, containers, deepseq
, linear, matrix, monad-loops, monad-parallel, mtl
, OpenGL, random, stdenv, stm, text, uuid, vector
}:
mkDerivation {
pname = "affection";
version = "0.0.0.9";
src = ./.;
configureFlags = [ "-fexamples" ];
isLibrary = true;
isExecutable = true;
libraryHaskellDepends = [
base bytestring clock containers glib linear monad-loops
monad-parallel mtl OpenGL sdl2Nek0 stm text uuid vector
];
executableHaskellDepends = [
base containers deepseq linear matrix nanovgNeko OpenGL random sdl2Nek0 stm
];
homepage = "https://github.com/nek0/affection#readme";
description = "A simple Game Engine using SDL";
license = stdenv.lib.licenses.lgpl3;
};
haskellPackages = if compiler == "default"
then pkgs.haskellPackages
else pkgs.haskell.packages.${compiler};
variant = if doBenchmark then pkgs.haskell.lib.doBenchmark else pkgs.lib.id;
drv = variant (haskellPackages.callPackage f {});
affection = pkgs.haskellPackages.callCabal2nix "affection" (gitignore ./.) {};
gitignore = dir: pkgs.nix-gitignore.gitignoreSource [] dir;
in
if pkgs.lib.inNixShell then drv.env else drv
affection.env

View file

@ -1,5 +1,8 @@
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE Strict #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
module Affection
( withAffection
, get
@ -13,15 +16,25 @@ import qualified SDL
import qualified SDL.Raw.Video as SDL (glSetAttribute)
import qualified SDL.Raw.Enum as SDL
import qualified Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..))
import qualified Graphics.GL as GLRaw
import Foreign.Marshal.Array
import qualified Data.ByteString as B
import Data.String (fromString)
import System.Clock
import Control.Monad.Loops
import Control.Monad.State.Strict
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Resource
import Foreign.C.Types (CInt(..))
-- internal imports
import Affection.Types as A
import Affection.Class as A
import Affection.StateMachine as A
import Affection.Util as A
import Affection.MessageBus as A
@ -29,102 +42,163 @@ import Affection.Subsystems as A
import Affection.Logging as A
import qualified Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..))
-- | Main function which bootstraps everything else.
withAffection
:: AffectionConfig us -- ^ Configuration of the Game and its engine.
:: forall us. (Affectionate us)
=> AffectionConfig us -- ^ Configuration of the Game and its engine.
-> IO ()
withAffection AffectionConfig{..} = do
withAffection AffectionConfig{..} = runResourceT $ do
liftIO $ logIO Debug "Affection starting"
liftIO $ logIO Debug "Initializing SDL"
-- intialiaze SDL
case initComponents of
All ->
SDL.initializeAll
Only is ->
SDL.initialize is
-- give SDL render quality
SDL.HintRenderScaleQuality SDL.$= SDL.ScaleLinear
-- just checking…
do
renderQuality <- SDL.get SDL.HintRenderScaleQuality
when (renderQuality /= SDL.ScaleLinear) $
logIO Warn "Linear texture filtering not enabled!"
renderQuality <- SDL.get SDL.HintRenderScaleQuality
when (renderQuality /= SDL.ScaleLinear) $
liftIO $ logIO Warn "Linear texture filtering not enabled!"
void $ liftIO (logIO Debug . fromString . show <$> (SDL.version :: IO (Integer, Integer, Integer)))
-- construct window
liftIO $ logIO Debug "Creating Window(s)"
windows <- zip (map fst windowConfigs) <$>
windows <-
mapM
(\wc -> SDL.createWindow windowTitle (snd wc))
windowConfigs
mapM_ (SDL.showWindow . snd) windows
_ <- SDL.glSetAttribute SDL.SDL_GL_SHARE_WITH_CURRENT_CONTEXT 1
contexts <- zip (map fst windows) <$> mapM (SDL.glCreateContext . snd) windows
-- let SDL.V2 (CInt rw) (CInt rh) = SDL.windowInitialSize windowConfigs
-- (w, h) = case canvasSize of
-- Just (cw, ch) -> (cw, ch)
-- Nothing -> (fromIntegral rw, fromIntegral rh)
mapM_ (flip SDL.setWindowMode initScreenMode . snd) windows
(\(_, sdlWindowConfig, mode) -> do
(windowKey, window) <-
allocate
(SDL.createWindow windowTitle sdlWindowConfig)
(\window -> do
logIO Debug "Destroying Window"
SDL.destroyWindow window
)
return $ AffectionWindow window windowKey mode
)
windowConfigs
-- Show windows
mapM_ (SDL.showWindow . awWindow) windows
-- set modes of windows
mapM_ (\(AffectionWindow window _ mode) -> SDL.setWindowMode window mode) windows
-- Make GL context shareable
void $ SDL.glSetAttribute SDL.SDL_GL_SHARE_WITH_CURRENT_CONTEXT 1
-- Create OpenGL contexts
contexts <-
mapM
(\(AffectionWindow window _ _) -> do
(contextKey, context) <-
allocate
(SDL.glCreateContext window)
(\context -> do
logIO Debug "Destroying context"
SDL.glDeleteContext context
)
return $ AffectionContext context contextKey
)
windows
-- sync updates with monitor
-- SDL.swapInterval $= SDL.SynchronizedUpdates -- <- causes Problems with windows
liftIO $ logIO Debug "Getting Time"
-- print current used GL Version
version <- liftIO $ peekArray0 (0 :: Word8) =<< GLRaw.glGetString GLRaw.GL_VERSION
liftIO $ print (B.pack version)
-- get current time
execTime <- getTime Monotonic
liftIO $ logIO Debug "Getting Time"
execTime <- liftIO $ getTime Monotonic
liftIO $ logIO Debug "Loading initial data container"
initContainer <- (\x -> AffectionData
{ quitEvent = False
, userState = x
, drawWindows = windows
, glContext = contexts
, screenMode = initScreenMode
, elapsedTime = 0
, deltaTime = 0
, sysTime = execTime
, pausedTime = False
}) <$> loadState
(_, nState) <- runStateT ( A.runState $ do
liftIO $ logIO Debug "Starting Loop"
preLoop
whileM_ (not . A.quitEvent <$> get)
-- construct game data object from provided Affectionate instance
(gameDataKey, gameData) <-
allocate
(liftIO $ loadState @us)
(liftIO . cleanUp)
-- build state container
let initContainer = AffectionData
{ drawWindows = windows
, glContext = contexts
, elapsedTime = 0
, deltaTime = 0
, sysTime = execTime
, pausedTime = False
}
-- initialize and run state
void $ liftIO $ runAffection initContainer
(do
-- get state
ad <- get
-- Measure time difference form last run
now <- liftIO $ getTime Monotonic
let lastTime = sysTime ad
-- compute dt and update elapsedTime
let !dt = fromIntegral
(toNanoSecs $ diffTimeSpec lastTime now) / (10 ^ (9 :: Int))
!ne = elapsedTime ad + dt
put $ ad
{ elapsedTime = ne
, deltaTime = dt
}
-- poll events
evs <- preHandleEvents =<< liftIO SDL.pollEvents
-- handle events
eventLoop evs
-- execute user defined update loop
unless (pausedTime ad) (updateLoop dt)
-- execute user defined draw loop
liftIO $ GL.clear [GL.ColorBuffer, GL.DepthBuffer, GL.StencilBuffer]
drawLoop
liftIO GL.flush
-- actual displaying of newly drawn frame
mapM_ (SDL.glSwapWindow . snd) windows
-- save new time
ad3 <- get
when (sysTime ad == sysTime ad3) (
put ad3
{ sysTime = now
}
)
)
) initContainer
liftIO $ logIO Debug "Running Pre-Loop stage"
-- run preLoop function from Affectionate
preLoop gameData
liftIO $ logIO Debug "Starting Loop"
whileM_ (hasNextStep gameData)
(do
-- get state
ad <- get
-- Measure time difference form last run
now <- liftIO $ getTime Monotonic
let lastTime = sysTime ad
-- compute dt and update elapsedTime
let dt = fromIntegral
(toNanoSecs $ diffTimeSpec lastTime now) / (10 ^ (9 :: Int))
ne = elapsedTime ad + dt
-- update state data object with new time values
put $ ad
{ elapsedTime = ne
, deltaTime = dt
}
-- poll events
liftIO SDL.pumpEvents
evs <- preHandleEvents =<< liftIO SDL.pollEvents
-- handle events
handleEvents gameData evs
-- execute user defined update loop
unless (pausedTime ad) (update gameData dt)
-- clear GL buffer >> execute user defined draw loop >> flush GL buffer
liftIO $ GL.clear [GL.ColorBuffer, GL.DepthBuffer, GL.StencilBuffer]
draw gameData
liftIO GL.flush
-- actual displaying of newly drawn frame
mapM_ (SDL.glSwapWindow . awWindow) windows
-- save new time
ad3 <- get
when (sysTime ad == sysTime ad3) (
put ad3
{ sysTime = now
}
)
)
)
-- Cleanup works
liftIO $ logIO Debug "Loop ended. Cleaning"
cleanUp $ userState nState
liftIO $ logIO Debug "Destroying Window"
mapM_ (SDL.glDeleteContext . snd) contexts
mapM_ (SDL.destroyWindow . snd) windows
release gameDataKey
-- mapM_ (SDL.glDeleteContext . snd) contexts
-- mapM_ (SDL.destroyWindow . (\(_,y,_) -> y)) windows
-- SDL.quit -- <- This causes segfaults depending on hardware
liftIO $ logIO Debug "This is the end"
runAffection
:: AffectionData
-> AffectionState AffectionData ResIO a
-> IO (a, AffectionData)
runAffection initialState a = runResourceT $ runStateT (A.runState a) initialState

21
src/Affection/Class.hs Normal file
View file

@ -0,0 +1,21 @@
module Affection.Class where
import qualified SDL
import Affection.Types as A
class Affectionate a where
loadState :: IO a
preLoop :: a -> Affection ()
handleEvents :: a -> [SDL.EventPayload] -> Affection ()
update :: a -> Double -> Affection ()
draw :: a -> Affection ()
cleanUp :: a -> IO ()
hasNextStep :: a -> Affection Bool

View file

@ -3,6 +3,8 @@
-- from "Debug.Trace".
module Affection.Logging where
import qualified Data.Text as T
import Debug.Trace
-- | The log level definition
@ -15,38 +17,38 @@ data LogLevel
-- | Pure logging function
log
:: LogLevel -- ^ Log level to log to
-> String -- ^ The message string
-> T.Text -- ^ The message string
-> a -- ^ Arbitrary datatype to return
-> a -- ^ Returned data
#if defined(VERBOSE)
log Verbose s = trace ("VERBOSE: " ++ s)
log Verbose s = trace ("VERBOSE: " ++ T.unpack s)
#endif
#if defined(DEBUG) || defined(VERBOSE)
log Debug s = trace ("DEBUG: " ++ s)
log Debug s = trace ("DEBUG: " ++ T.unpack s)
#endif
#if defined(WARN) || defined(DEBUG) || defined(VERBOSE)
log Warn s = trace ("WARN: " ++ s)
log Warn s = trace ("WARN: " ++ T.unpack s)
#endif
#if defined(ERROR) || defined(WARN) || defined(DEBUG) || defined(VERBOSE)
log Error s = trace ("ERROR: " ++ s)
log Error s = trace ("ERROR: " ++ T.unpack s)
#endif
log _ _ = id
-- | Manadic logging function residing in the 'IO' Monad
logIO
:: LogLevel -- ^ Log level to log to
-> String -- ^ The message string
-> T.Text -- ^ The message string
-> IO ()
#if defined(VERBOSE)
logIO Verbose s = traceIO ("VERBOSE: " ++ s)
logIO Verbose s = traceIO ("VERBOSE: " ++ T.unpack s)
#endif
#if defined(DEBUG) || defined(VERBOSE)
logIO Debug s = traceIO ("DEBUG: " ++ s)
logIO Debug s = traceIO ("DEBUG: " ++ T.unpack s)
#endif
#if defined(WARN) || defined(DEBUG) || defined(VERBOSE)
logIO Warn s = traceIO ("WARN: " ++ s)
logIO Warn s = traceIO ("WARN: " ++ T.unpack s)
#endif
#if defined(ERROR) || defined(WARN) || defined(DEBUG) || defined(VERBOSE)
logIO Error s = traceIO ("ERROR: " ++ s)
logIO Error s = traceIO ("ERROR: " ++ T.unpack s)
#endif
logIO _ _ = return ()

View file

@ -1,9 +1,6 @@
module Affection.MessageBus
( module M
, module Msg
) where
import Affection.MessageBus.Class as M
import Affection.MessageBus.Message as M
import Affection.MessageBus.Message as Msg

View file

@ -17,28 +17,30 @@ import Control.Monad.IO.Class (liftIO)
import Data.UUID
import Data.UUID.V4
import Data.String as S (fromString)
import Affection.Logging
-- | 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
class (Message (Mesg prt), Show (Mesg prt)) => Participant prt where
-- | Message datatype
type Mesg prt us :: *
type Mesg prt :: *
-- | Function to get the list of subscribers from the participant
partSubscribers
:: prt
-- ^ the 'Participant''s subscriber storage
-> Affection us [Mesg prt us -> Affection us ()]
-> Affection [Mesg prt -> Affection ()]
-- ^ List of Subscriber functions
-- | Subscribe to the 'Participant''s events
partSubscribe
:: prt
-- ^ The 'Participant''s subscriber storage
-> (Mesg prt us -> Affection us ())
-> (Mesg prt -> Affection ())
-- ^ What to do in case of a 'Message'
-- (Subscriber function)
-> Affection us UUID
-> Affection UUID
-- ^ 'UUID' of the registered subscriber Function
-- | Unsubscribe a Subscriber function from Participant
@ -47,20 +49,20 @@ class (Message (Mesg prt us), Show (Mesg prt us)) => Participant prt us where
-- ^ The 'Participant''s subscriber storage to unsubscribe from
-> UUID
-- ^ The subscriber function's 'UUID'
-> Affection us ()
-> Affection ()
-- | Get the 'Participant' to emit a 'Message' on all of its subscribers
partEmit
:: prt
-- ^ The 'Participant''s subscriber storage
-> Mesg prt us
-> Mesg prt
-- ^ The 'Message' to emit
-> Affection us ()
-> Affection ()
partEmit p m = do
liftIO $ logIO Verbose $ "Emitting message: " ++ show m
liftIO $ logIO Verbose $ "Emitting message: " <> S.fromString (show m)
l <- partSubscribers p
mapM_ ($ m) l
-- | Helper function to generate new 'UUID's
genUUID :: Affection us UUID
genUUID :: Affection UUID
genUUID = liftIO nextRandom

View file

@ -7,14 +7,14 @@ import Affection.Types
import qualified SDL
-- | Typeclass for simple scaffolding of a state machine
class StateMachine a us where
class StateMachine us a where
-- | State load routine
smLoad :: a -> Affection us ()
smLoad :: a -> us -> Affection ()
-- | state update routine
smUpdate :: a -> Double -> Affection us ()
smUpdate :: a -> us -> Double -> Affection ()
-- | State event handler routine
smEvent :: a -> [SDL.EventPayload] -> Affection us ()
smEvent :: a -> us -> [SDL.EventPayload] -> Affection ()
-- | State draw routine
smDraw :: a -> Affection us ()
smDraw :: a -> us -> Affection ()
-- | State clean routine
smClean :: a -> Affection us ()
smClean :: a -> us -> Affection ()

View file

@ -13,6 +13,7 @@ import Control.Monad (filterM)
import Control.Monad.IO.Class (liftIO)
import qualified Data.Vector as V
import Data.String as S (fromString)
import Foreign.C.Types (CInt(..))
@ -21,15 +22,15 @@ 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)
:: forall am. (Participant am, Mesg am ~ JoystickMessage)
=> am -- ^ The message system participant
-> [SDL.EventPayload] -- ^ Incoming events
-> Affection us [SDL.EventPayload] -- ^ Leftover SDL events
-> Affection [SDL.EventPayload] -- ^ Leftover SDL events
consumeSDLJoystickEvents am = doConsume
where
doConsume
:: [SDL.EventPayload]
-> Affection us [SDL.EventPayload]
-> Affection [SDL.EventPayload]
doConsume [] = return []
doConsume (e:es) = do
ts <- getElapsedTime
@ -80,13 +81,14 @@ consumeSDLJoystickEvents am = doConsume
joystickAutoConnect
:: JoystickMessage -- ^ Any 'JoystickMessage' will do,
-- but listens only on 'MsgJoystickDevice' messages
-> Affection us (Maybe SDL.Joystick)
-> Affection (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
logIO Verbose $ "Connecting Joystick " <> fromString (show which) <> " " <>
fromString (show descr)
Just <$> SDL.openJoystick descr
joystickAutoConnect _ = return Nothing
@ -96,19 +98,20 @@ 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
-> Affection [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
logIO Verbose $ "These are the Joysticks connected: " <>
fromString (show joyIds)
d <- filterM (\j -> fmap (== which) (SDL.getJoystickID j)) js
if not (null d)
then do
logIO Verbose $ "disconnected joysticks: " ++ show (head d)
logIO Verbose $ "Disconnecting Joystick " ++ show which
logIO Verbose $ "disconnected joysticks: " <> fromString (show $ head d)
logIO Verbose $ "Disconnecting Joystick " <> fromString (show which)
SDL.closeJoystick (head d)
njoys <- filterM (\j -> return $ head d /= j) js
logIO Verbose $ "returning joysticks: " ++ show njoys
logIO Verbose $ "returning joysticks: " <> fromString (show njoys)
return njoys
else do
logIO Error $ "Error while disconnecting Joystick"

View file

@ -13,10 +13,10 @@ import qualified SDL
-- | Helper function that consumes all Keyboard-related 'SDL.EventPayload's
-- and emits appropriate 'KeyboardMessage's
consumeSDLKeyboardEvents
:: forall ak us. (Participant ak us, Mesg ak us ~ KeyboardMessage)
:: forall ak. (Participant ak, Mesg ak ~ KeyboardMessage)
=> ak -- ^ The message system participant
-> [SDL.EventPayload] -- ^ Incoming events
-> Affection us [SDL.EventPayload] -- ^ Leftover SDL Events
-> Affection [SDL.EventPayload] -- ^ Leftover SDL Events
consumeSDLKeyboardEvents ak = doConsume
where
doConsume [] = return []

View file

@ -15,15 +15,15 @@ import qualified SDL
-- | Helper funtion that consumes all Mouse-related 'SDL.Eventpayload's
-- and emits appropriate 'MouseMessage's
consumeSDLMouseEvents
:: forall am us. (Participant am us, Mesg am us ~ MouseMessage)
:: forall am. (Participant am, Mesg am ~ MouseMessage)
=> am -- ^ The message system participant
-> [SDL.EventPayload] -- ^ Incoming events
-> Affection us [SDL.EventPayload] -- ^ Leftover SDL events
-> Affection [SDL.EventPayload] -- ^ Leftover SDL events
consumeSDLMouseEvents am = doConsume
where
doConsume
:: [SDL.EventPayload]
-> Affection us [SDL.EventPayload]
-> Affection [SDL.EventPayload]
doConsume [] = return []
doConsume (e:es) = do
ts <- getElapsedTime

View file

@ -15,16 +15,16 @@ import qualified SDL
-- | Helper function that consumes all Window-related 'SDL.EventPayload's
-- and emits appropriate 'WindowMessage's.
consumeSDLWindowEvents
:: forall aw us. (Participant aw us, Mesg aw us ~ WindowMessage)
:: forall aw. (Participant aw, Mesg aw ~ WindowMessage)
=> aw -- ^ The message system participant
-> [SDL.EventPayload] -- ^ Incoming events
-> Affection us [SDL.EventPayload] -- ^ Leftover SDL events
-> Affection [SDL.EventPayload] -- ^ Leftover SDL events
consumeSDLWindowEvents aw = doConsume
where
doConsume
:: (Mesg aw us ~ WindowMessage)
:: (Mesg aw ~ WindowMessage)
=> [SDL.EventPayload]
-> Affection us [SDL.EventPayload]
-> Affection [SDL.EventPayload]
doConsume [] = return []
doConsume (e:es) = do
ts <- getElapsedTime

View file

@ -7,7 +7,7 @@ import Affection.Types
import qualified SDL
-- | This class denotes a Subsystem to be part of SDL
class SDLSubsystem s us where
class SDLSubsystem s where
-- | Consume the given 'SDL.EventPayload's and return only those not
-- recognised
consumeSDLEvents :: s -> [SDL.EventPayload] -> Affection us [SDL.EventPayload]
consumeSDLEvents :: s -> [SDL.EventPayload] -> Affection [SDL.EventPayload]

View file

@ -5,6 +5,8 @@ module Affection.Types
, AffectionData(..)
, AffectionStateInner
, AffectionState(..)
, AffectionWindow(..)
, AffectionContext(..)
, InitComponents(..)
, Angle
-- | SDL reexports
@ -23,6 +25,7 @@ import qualified Data.Text as T
import Control.Monad.IO.Class
import Control.Monad.State.Strict
import Control.Monad.Trans.Resource
import qualified Control.Monad.Parallel as MP
import System.Clock (TimeSpec)
@ -35,27 +38,12 @@ data AffectionConfig us = AffectionConfig
-- ^ Window title
, windowConfigs ::
[
( Word -- ^ Window identifier
, SDL.WindowConfig -- ^ Window config for given window
( Word -- --^ Window identifier
, SDL.WindowConfig -- --^ Window config for given window
, SDL.WindowMode -- -- ^ Window mode to start in
)
]
-- ^ Window configurations
, canvasSize :: Maybe (Int, Int)
-- ^ size of the texture canvas
, initScreenMode :: SDL.WindowMode
-- ^ Window mode to start in
, loadState :: IO us
-- ^ Provide your own load function to create this data.
, preLoop :: Affection us ()
-- ^ Actions to be performed, before loop starts
, eventLoop :: [SDL.EventPayload] -> Affection us ()
-- ^ Main update function. Takes fractions of a second as input.
, updateLoop :: Double -> Affection us ()
-- ^ Main update function. Takes fractions of a second as input.
, drawLoop :: Affection us ()
-- ^ Function for updating graphics.
, cleanUp :: us -> IO ()
-- ^ Provide your own finisher function to clean your data.
}
-- | Components to initialize in SDL.
@ -64,38 +52,36 @@ data InitComponents
| Only [SDL.InitFlag]
-- | Main type for defining the look, feel and action of the whole application.
data AffectionData us = AffectionData
{ quitEvent :: Bool -- ^ Loop breaker.
, userState :: us -- ^ State data provided by user
, drawWindows ::
[
( Word -- ^ Window identifier
, SDL.Window -- ^ Window linked with identifier
)
] -- ^ SDL windows
, glContext ::
[
( Word -- ^ Window identifier
, SDL.GLContext -- ^ Associated OpenGL context
)
] -- ^ OpenGL rendering contexts
, screenMode :: SDL.WindowMode -- ^ current screen mode
, 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?
data AffectionData = AffectionData
{ drawWindows :: [ AffectionWindow ] -- ^ SDL windows
, glContext :: [ AffectionContext ] -- ^ OpenGL rendering contexts
, 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?
}
-- | Inner 'StateT' monad for the update state
type AffectionStateInner us a = StateT us a
type AffectionStateInner sd m = StateT sd m
-- | Affection's state monad
newtype AffectionState us m a = AffectionState
{ runState :: AffectionStateInner us m a }
deriving (Functor, Applicative, Monad, MonadIO, MonadState us)
newtype AffectionState sd m a = AffectionState
{ runState :: AffectionStateInner sd m a }
deriving (Functor, Applicative, Monad, MonadIO, MonadState sd, MonadResource)
instance MP.MonadParallel m => MP.MonadParallel (AffectionState us m)
instance MP.MonadParallel m => MP.MonadParallel (AffectionState sd m)
type Affection us a = AffectionState (AffectionData us) IO a
type Affection a = AffectionState AffectionData ResIO a
type Angle = Double
data AffectionWindow = AffectionWindow
{ awWindow :: SDL.Window
, awReleaseKey :: ReleaseKey
, awMode :: SDL.WindowMode
}
data AffectionContext = AffectionContext
{ acContext :: SDL.GLContext
, acReleaseKey :: ReleaseKey
}

View file

@ -10,26 +10,16 @@ import qualified Graphics.Rendering.OpenGL as GL
import System.Clock
import Data.String (fromString)
import Data.List (find)
import Control.Monad.State
-- | Prehandle SDL events
preHandleEvents :: [SDL.Event] -> Affection us [SDL.EventPayload]
preHandleEvents :: [SDL.Event] -> Affection [SDL.EventPayload]
preHandleEvents evs =
return $ map SDL.eventPayload evs
-- | Return the userstate to the user
getAffection :: Affection us us
getAffection = gets userState
-- | Put altered user state back
putAffection
:: us -- User state
-> Affection us ()
putAffection us = do
ad <- get
put $ ad
{ userState = us }
-- | block a thread for a specified amount of time
delaySec
:: Int -- ^ Number of seconds
@ -37,46 +27,54 @@ delaySec
delaySec dur = SDL.delay (fromIntegral $ dur * 1000)
-- | Get time since start but always the same in the current tick.
getElapsedTime :: Affection us Double
getElapsedTime :: Affection Double
getElapsedTime = gets elapsedTime
-- | Get delta time (time elapsed from last frame)
getDelta :: Affection us Double
getDelta :: Affection Double
getDelta = gets deltaTime
-- | Quit the engine loop
quit :: Affection us ()
quit = do
ad <- get
put $ ad { quitEvent = True }
-- | Toggle the Screen mode between 'SDL.Windowed' and 'SDL.FullscreenDesktop'.
-- Pauses the Engine in the process.
toggleScreen :: Affection us ()
toggleScreen = do
toggleScreen :: Word -> Affection ()
toggleScreen windowIdent = do
ad <- get
newMode <- case screenMode ad of
SDL.Windowed -> do
mapM_ (flip SDL.setWindowMode SDL.FullscreenDesktop . snd) (drawWindows ad)
return SDL.FullscreenDesktop
SDL.FullscreenDesktop -> do
mapM_ (flip SDL.setWindowMode SDL.Windowed . snd) (drawWindows ad)
return SDL.Windowed
x -> do
liftIO $ logIO Warn ("Unexpected Screen mode: " ++ show x)
return x
now <- liftIO $ getTime Monotonic
put ad
{ sysTime = now
, screenMode = newMode
}
(stop, alteredWindowList) <- foldM
(\(stop, resWindows) (num, aw@(AffectionWindow window _ mode)) -> do
if stop || num == windowIdent
then do
newMode <- case mode of
SDL.FullscreenDesktop -> do
liftIO $ SDL.setWindowMode window SDL.Windowed
return SDL.Windowed
SDL.Windowed -> do
liftIO $ SDL.setWindowMode window SDL.FullscreenDesktop
return SDL.FullscreenDesktop
x -> do
liftIO $ logIO Warn ("Unexpected window mode: " <> fromString (show x))
return x
return (True, resWindows ++ [aw { awMode = newMode }])
else
return (stop, resWindows ++ [aw])
)
(False, [])
(zip [0..] (drawWindows ad))
if stop
then do
now <- liftIO $ getTime Monotonic
put ad
{ sysTime = now
, drawWindows = alteredWindowList
}
else
liftIO $ logIO Warn ("No window found with ident " <> fromString (show windowIdent))
-- | 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 ()
-> Affection ()
fitViewport ratio (MsgWindowResize _ _ (SDL.V2 w h)) = do
liftIO $ logIO Verbose "Fitting Viewport to size"
if (fromIntegral w / fromIntegral h) > ratio