From 1b245dc753ed62987204360ddcba529f8f74527b Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 25 Mar 2016 09:41:22 +0100 Subject: [PATCH 001/261] initial scaffold --- .gitignore | 5 ++ LICENSE | 165 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 7 ++ Setup.hs | 2 + affection.cabal | 44 +++++++++++++ src/Affection.hs | 6 ++ stack.yaml | 35 ++++++++++ 7 files changed, 264 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Setup.hs create mode 100644 affection.cabal create mode 100644 src/Affection.hs create mode 100644 stack.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..993c34f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.hi +*.o +.hsenv* +.stack-work +.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..31afd6d --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4dd97c0 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Affection + +A simple Game Engine in Haskell using SDL + +## Disclaimer + +This Engine is not stable yet. Things may change. horribly. diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/affection.cabal b/affection.cabal new file mode 100644 index 0000000..4f38fcd --- /dev/null +++ b/affection.cabal @@ -0,0 +1,44 @@ +name: affection + +-- The package version. See the Haskell package versioning policy (PVP) +-- for standards guiding when and how versions should be incremented. +-- http://www.haskell.org/haskellwiki/Package_versioning_policy +-- PVP summary: +-+------- breaking API changes +-- | | +----- non-breaking API additions +-- | | | +--- code changes with no API change +version: 0.0.0.0 +synopsis: A simple Game Engine using SDL +-- description: +homepage: https://github.com/nek0/affection#readme +license: LGPL-3 +license-file: LICENSE +author: nek0 +maintainer: nek0@chelnok.de +category: Game +build-type: Simple + +-- Extra files to be distributed with the package, such as examples or a +-- README. +-- extra-source-files: + +cabal-version: >=1.10 + + +library + exposed-modules: Affection + + -- Modules included in this library but not exported. + -- other-modules: + + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + hs-source-dirs: src + default-language: Haskell2010 + -- Other library packages from which modules are imported. + build-depends: base >=4.8 && <4.9 + , sdl2 + , sdl2-image + +source-repository head + type: git + location: https://github.com/nek0/affection diff --git a/src/Affection.hs b/src/Affection.hs new file mode 100644 index 0000000..4dcd024 --- /dev/null +++ b/src/Affection.hs @@ -0,0 +1,6 @@ +module Affection + ( someFunc + ) where + +someFunc :: IO () +someFunc = putStrLn "someFunc" diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..b1f3b2c --- /dev/null +++ b/stack.yaml @@ -0,0 +1,35 @@ +# For more information, see: http://docs.haskellstack.org/en/stable/yaml_configuration.html + +# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) +resolver: lts-5.1 + +# Local packages, usually specified by relative directory name +packages: +- '.' + +# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) +extra-deps: [] + +# Override default flag values for local packages and extra-deps +flags: {} + +# Extra package databases containing global packages +extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true + +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: >= 1.0.0 + +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 + +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] + +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor From 0ab5a7d94757f7e0a1f8f18451f11c8faf0310a4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 25 Mar 2016 11:43:09 +0100 Subject: [PATCH 002/261] readme update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4dd97c0..2958e76 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,4 @@ A simple Game Engine in Haskell using SDL ## Disclaimer -This Engine is not stable yet. Things may change. horribly. +This Engine is not stable yet. Things may change. horribly. I do not intend bumping versions for now. From 8cb3dc21c457af82629200aee1f905ad3979e7c6 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 25 Mar 2016 11:43:31 +0100 Subject: [PATCH 003/261] first itty bitty steps --- affection.cabal | 5 +++-- src/Affection.hs | 28 +++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/affection.cabal b/affection.cabal index 4f38fcd..c99d070 100644 --- a/affection.cabal +++ b/affection.cabal @@ -26,7 +26,7 @@ cabal-version: >=1.10 library exposed-modules: Affection - + extensions: OverloadedStrings -- Modules included in this library but not exported. -- other-modules: @@ -37,7 +37,8 @@ library -- Other library packages from which modules are imported. build-depends: base >=4.8 && <4.9 , sdl2 - , sdl2-image + , text + -- , sdl2-image source-repository head type: git diff --git a/src/Affection.hs b/src/Affection.hs index 4dcd024..9fd2465 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -1,6 +1,28 @@ module Affection - ( someFunc + ( withAllAffection + , withWindow + , withDefaultWindow + , delaySec ) where -someFunc :: IO () -someFunc = putStrLn "someFunc" +import SDL +import Data.Text +import qualified Control.Concurrent as CC + +withAllAffection :: IO () -> IO () +withAllAffection ops = do + initializeAll + ops + quit + +withWindow :: Text -> WindowConfig -> (Window -> IO ()) -> IO () +withWindow title conf ops = do + window <- createWindow title conf + ops window + destroyWindow window + +withDefaultWindow :: Text -> (Window -> IO ()) -> IO () +withDefaultWindow title ops = withWindow title defaultWindow ops + +delaySec :: Int -> IO () +delaySec dur = delay (fromIntegral $ dur * 1000) From a457313adeccebd228f735ca886a8bfdc37a3f30 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 25 Mar 2016 16:58:46 +0100 Subject: [PATCH 004/261] more playthings --- affection.cabal | 1 + src/Affection.hs | 18 ++++++++++++------ src/Affection/Render.hs | 35 +++++++++++++++++++++++++++++++++++ src/Affection/Types.hs | 17 +++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 src/Affection/Render.hs create mode 100644 src/Affection/Types.hs diff --git a/affection.cabal b/affection.cabal index c99d070..0e453c5 100644 --- a/affection.cabal +++ b/affection.cabal @@ -27,6 +27,7 @@ cabal-version: >=1.10 library exposed-modules: Affection extensions: OverloadedStrings + , GADTs -- Modules included in this library but not exported. -- other-modules: diff --git a/src/Affection.hs b/src/Affection.hs index 9fd2465..311d222 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -3,26 +3,32 @@ module Affection , withWindow , withDefaultWindow , delaySec + , Affection.Render + , Affection.Types ) where import SDL import Data.Text import qualified Control.Concurrent as CC +import Affection.Render +import Affection.Types + withAllAffection :: IO () -> IO () withAllAffection ops = do initializeAll ops quit -withWindow :: Text -> WindowConfig -> (Window -> IO ()) -> IO () -withWindow title conf ops = do - window <- createWindow title conf - ops window +withWindow :: Text -> WindowConfig -> RendererConfig -> (Window -> Renderer -> IO ()) -> IO () +withWindow title wconf rconf ops = do + window <- createWindow title wconf + renderer <- createRenderer window (-1) rconf + inRender renderer $ ops destroyWindow window -withDefaultWindow :: Text -> (Window -> IO ()) -> IO () -withDefaultWindow title ops = withWindow title defaultWindow ops +withDefaultWindow :: Text -> (Window -> Renderer -> IO ()) -> IO () +withDefaultWindow title ops = withWindow title defaultWindow defaultRenderer ops delaySec :: Int -> IO () delaySec dur = delay (fromIntegral $ dur * 1000) diff --git a/src/Affection/Render.hs b/src/Affection/Render.hs new file mode 100644 index 0000000..fc05dab --- /dev/null +++ b/src/Affection/Render.hs @@ -0,0 +1,35 @@ +{-# LANGUAGE GADTs #-} + +module Affection.Render + ( RenderM + , inRender + ) where + +import Affection.Types (RGBA) +import SDL +import Linear (V4(..)) + +data RenderM :: * -> * where + Pure :: a -> RenderM a + Bind :: RenderM a -> (a -> RenderM b) -> RenderM b + Render :: (Renderer -> IO a) -> RenderM a + +instance Functor RenderM where + fmap f m = Bind m (Pure . f) + +instance Applicative RenderM where + pure x = Pure x + mf <*> mx = Bind mf (\f -> Bind mx (\x -> Pure (f x))) + +instance Monad RenderM where + m >>= f = Bind m f + +inRender :: Renderer -> RenderM a -> IO a +inRender _ (Pure a) = return a +inRender r (Bind m f) = do + result <- inRender m r + inRender (f result) r +inRender r (Render f) = f r + +changeColor :: RGBA -> RenderM () +changeColor (RGBA r g b a) = Render $ \renderer -> rendererDrawColor renderer $= V4 r g b a diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs new file mode 100644 index 0000000..5d34a89 --- /dev/null +++ b/src/Affection/Types.hs @@ -0,0 +1,17 @@ +module Affection.Types + ( newRGBA + ) where + +data RGBA = RGBA + { r :: Int + , g :: Int + , b :: Int + , a :: Int + } + +newRGBA :: Int -> Int -> Int -> Int -> RGBA +newRGBA r g b a = RGBA (overflow r) (overflow g) (overflow b) (overflow a) + where + overflow x + | x < 0 = 255 + (x `mod` 255) + | otherwise = x `mod` 255 From 89fc700dfed2dd97a2c1ebe0809fb7fcfffe0375 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 26 Mar 2016 03:50:39 +0100 Subject: [PATCH 005/261] wrapped into monad transformer --- affection.cabal | 14 ++++++-- src/Affection.hs | 8 ++--- src/Affection/Render.hs | 77 ++++++++++++++++++++++++++++++++++------- src/Affection/Types.hs | 26 ++++++++++---- 4 files changed, 98 insertions(+), 27 deletions(-) diff --git a/affection.cabal b/affection.cabal index 0e453c5..0c76ffc 100644 --- a/affection.cabal +++ b/affection.cabal @@ -26,19 +26,27 @@ cabal-version: >=1.10 library exposed-modules: Affection - extensions: OverloadedStrings - , GADTs + , Affection.Render + , Affection.Types + default-extensions: OverloadedStrings + -- Modules included in this library but not exported. -- other-modules: -- LANGUAGE extensions used by modules in this package. - -- other-extensions: + other-extensions: GADTs + , KindSignatures + , FlexibleInstances + , MultiParamTypeClasses + , UndecidableInstances hs-source-dirs: src default-language: Haskell2010 -- Other library packages from which modules are imported. build-depends: base >=4.8 && <4.9 , sdl2 , text + , linear + , mtl -- , sdl2-image source-repository head diff --git a/src/Affection.hs b/src/Affection.hs index 311d222..660e8f2 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -3,8 +3,8 @@ module Affection , withWindow , withDefaultWindow , delaySec - , Affection.Render - , Affection.Types + , module Affection.Render + , module Affection.Types ) where import SDL @@ -20,14 +20,14 @@ withAllAffection ops = do ops quit -withWindow :: Text -> WindowConfig -> RendererConfig -> (Window -> Renderer -> IO ()) -> IO () +withWindow :: Monad m => Text -> WindowConfig -> RendererConfig -> RenderT m a -> IO () withWindow title wconf rconf ops = do window <- createWindow title wconf renderer <- createRenderer window (-1) rconf inRender renderer $ ops destroyWindow window -withDefaultWindow :: Text -> (Window -> Renderer -> IO ()) -> IO () +withDefaultWindow :: Monad m => Text -> (RenderT m a) -> IO () withDefaultWindow title ops = withWindow title defaultWindow defaultRenderer ops delaySec :: Int -> IO () diff --git a/src/Affection/Render.hs b/src/Affection/Render.hs index fc05dab..3e4b12b 100644 --- a/src/Affection/Render.hs +++ b/src/Affection/Render.hs @@ -1,35 +1,86 @@ -{-# LANGUAGE GADTs #-} +{-# LANGUAGE GADTs, KindSignatures, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} module Affection.Render ( RenderM , inRender + , RenderT + , liftIO + , changeColor + , clear + , present ) where -import Affection.Types (RGBA) -import SDL +import Affection.Types +import qualified SDL import Linear (V4(..)) +import Control.Monad.Trans data RenderM :: * -> * where Pure :: a -> RenderM a Bind :: RenderM a -> (a -> RenderM b) -> RenderM b - Render :: (Renderer -> IO a) -> RenderM a + Render :: (SDL.Renderer -> IO a) -> RenderM a instance Functor RenderM where fmap f m = Bind m (Pure . f) instance Applicative RenderM where - pure x = Pure x + pure = Pure mf <*> mx = Bind mf (\f -> Bind mx (\x -> Pure (f x))) instance Monad RenderM where m >>= f = Bind m f -inRender :: Renderer -> RenderM a -> IO a -inRender _ (Pure a) = return a -inRender r (Bind m f) = do - result <- inRender m r - inRender (f result) r -inRender r (Render f) = f r +inRenderM :: SDL.Renderer -> RenderM a -> IO a +inRenderM _ (Pure a) = return a +inRenderM r (Bind m f) = do + result <- inRenderM r m + inRenderM r (f result) +inRenderM r (Render f) = f r -changeColor :: RGBA -> RenderM () -changeColor (RGBA r g b a) = Render $ \renderer -> rendererDrawColor renderer $= V4 r g b a +data RenderT :: (* -> *) -> * -> * where + TPure :: a -> RenderT m a + TBind :: RenderT m a -> (a -> RenderT m b) -> RenderT m b + -- RunRenderT :: RenderT IO a -> IO a + TLift :: (Monad m) => m a -> RenderT m a + TLiftIO :: IO a -> RenderT m a + TRender :: (SDL.Renderer -> IO a) -> RenderT RenderM a + +instance Functor (RenderT m) where + fmap f m = TBind m (TPure . f) + +instance Applicative (RenderT m) where + pure = TPure + mf <*> mx = TBind mf (\f -> TBind mx (\x -> TPure (f x))) + +instance Monad m => Monad (RenderT m) where + return = TPure + x >>= f = TBind x f + +instance MonadTrans RenderT where + lift = TLift + +instance Monad m => MonadIO (RenderT m) where + liftIO = TLiftIO + +inRender :: Monad m => SDL.Renderer -> RenderT m a -> IO a +inRender _ (TPure a) = return a +inRender r (TBind m f) = do + result <- inRender r m + inRender r (f result) +-- inRender r (TLift x) = return x +inRender _ (TLiftIO x) = return =<< x +inRender r (TRender f) = f r + +changeColor :: RGBA -> RenderT RenderM () +changeColor colours = TRender $ \r -> + SDL.rendererDrawColor r SDL.$= V4 + (fromIntegral $ getR colours) + (fromIntegral $ getG colours) + (fromIntegral $ getB colours) + (fromIntegral $ getA colours) + +clear :: RenderT RenderM () +clear = TRender $ \r -> SDL.clear r + +present :: RenderT RenderM () +present = TRender $ \r -> SDL.present r diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 5d34a89..0c350ef 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -1,13 +1,13 @@ module Affection.Types - ( newRGBA + ( RGBA + , newRGBA + , getR + , getG + , getB + , getA ) where -data RGBA = RGBA - { r :: Int - , g :: Int - , b :: Int - , a :: Int - } +data RGBA = RGBA Int Int Int Int newRGBA :: Int -> Int -> Int -> Int -> RGBA newRGBA r g b a = RGBA (overflow r) (overflow g) (overflow b) (overflow a) @@ -15,3 +15,15 @@ newRGBA r g b a = RGBA (overflow r) (overflow g) (overflow b) (overflow a) overflow x | x < 0 = 255 + (x `mod` 255) | otherwise = x `mod` 255 + +getR :: RGBA -> Int +getR (RGBA r _ _ _) = r + +getG :: RGBA -> Int +getG (RGBA _ g _ _) = g + +getB :: RGBA -> Int +getB (RGBA _ _ b _) = b + +getA :: RGBA -> Int +getA (RGBA _ _ _ a) = a From 693215792b21933a8943621a560c0400a19bbdf4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 26 Mar 2016 08:49:24 +0100 Subject: [PATCH 006/261] better overflow for RGBA creation --- src/Affection/Types.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 0c350ef..93bc849 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -13,8 +13,8 @@ newRGBA :: Int -> Int -> Int -> Int -> RGBA newRGBA r g b a = RGBA (overflow r) (overflow g) (overflow b) (overflow a) where overflow x - | x < 0 = 255 + (x `mod` 255) - | otherwise = x `mod` 255 + | x < 0 = x - (x `div` 256) * 255 + 1 + | otherwise = x - (x `div` 256) * 255 getR :: RGBA -> Int getR (RGBA r _ _ _) = r From 2e8a31c731869b68c91dd9adc298eb5517206bda Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 26 Mar 2016 09:33:38 +0100 Subject: [PATCH 007/261] overflow handling not needed. SDL does it. --- src/Affection/Render.hs | 12 ++++++------ src/Affection/Types.hs | 33 +++++++-------------------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/Affection/Render.hs b/src/Affection/Render.hs index 3e4b12b..8b5f9bc 100644 --- a/src/Affection/Render.hs +++ b/src/Affection/Render.hs @@ -72,12 +72,12 @@ inRender _ (TLiftIO x) = return =<< x inRender r (TRender f) = f r changeColor :: RGBA -> RenderT RenderM () -changeColor colours = TRender $ \r -> - SDL.rendererDrawColor r SDL.$= V4 - (fromIntegral $ getR colours) - (fromIntegral $ getG colours) - (fromIntegral $ getB colours) - (fromIntegral $ getA colours) +changeColor (RGBA r g b a) = TRender $ \rend -> + SDL.rendererDrawColor rend SDL.$= V4 + (fromIntegral r) + (fromIntegral g) + (fromIntegral b) + (fromIntegral a) clear :: RenderT RenderM () clear = TRender $ \r -> SDL.clear r diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 93bc849..4807ab4 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -1,29 +1,10 @@ module Affection.Types - ( RGBA - , newRGBA - , getR - , getG - , getB - , getA + ( RGBA(..) ) where -data RGBA = RGBA Int Int Int Int - -newRGBA :: Int -> Int -> Int -> Int -> RGBA -newRGBA r g b a = RGBA (overflow r) (overflow g) (overflow b) (overflow a) - where - overflow x - | x < 0 = x - (x `div` 256) * 255 + 1 - | otherwise = x - (x `div` 256) * 255 - -getR :: RGBA -> Int -getR (RGBA r _ _ _) = r - -getG :: RGBA -> Int -getG (RGBA _ g _ _) = g - -getB :: RGBA -> Int -getB (RGBA _ _ b _) = b - -getA :: RGBA -> Int -getA (RGBA _ _ _ a) = a +data RGBA = RGBA + { r :: Int + , g :: Int + , b :: Int + , a :: Int + } From 0faa4c6c291e971ca1c2519fca4885a96833f2db Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 26 Mar 2016 14:03:09 +0100 Subject: [PATCH 008/261] why so complicated? --- src/Affection/Render.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Affection/Render.hs b/src/Affection/Render.hs index 8b5f9bc..744a17b 100644 --- a/src/Affection/Render.hs +++ b/src/Affection/Render.hs @@ -68,7 +68,7 @@ inRender r (TBind m f) = do result <- inRender r m inRender r (f result) -- inRender r (TLift x) = return x -inRender _ (TLiftIO x) = return =<< x +inRender _ (TLiftIO x) = x inRender r (TRender f) = f r changeColor :: RGBA -> RenderT RenderM () From 1262be0666ea862ca5317aa1dd8e6432043c0f17 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 26 Apr 2016 22:33:35 +0200 Subject: [PATCH 009/261] make gegl dependency --- affection.cabal | 1 + src/Affection.hs | 4 +++- stack.yaml | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index 0c76ffc..c44e398 100644 --- a/affection.cabal +++ b/affection.cabal @@ -47,6 +47,7 @@ library , text , linear , mtl + , gegl -- , sdl2-image source-repository head diff --git a/src/Affection.hs b/src/Affection.hs index 660e8f2..3263691 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -9,7 +9,7 @@ module Affection import SDL import Data.Text -import qualified Control.Concurrent as CC +import GEGL import Affection.Render import Affection.Types @@ -24,7 +24,9 @@ withWindow :: Monad m => Text -> WindowConfig -> RendererConfig -> RenderT m a - withWindow title wconf rconf ops = do window <- createWindow title wconf renderer <- createRenderer window (-1) rconf + gegl_init inRender renderer $ ops + gegl_exit destroyWindow window withDefaultWindow :: Monad m => Text -> (RenderT m a) -> IO () diff --git a/stack.yaml b/stack.yaml index b1f3b2c..97425ce 100644 --- a/stack.yaml +++ b/stack.yaml @@ -6,6 +6,7 @@ resolver: lts-5.1 # Local packages, usually specified by relative directory name packages: - '.' +- "../gegl" # Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) extra-deps: [] From 54d9f891b487d9fc0752c000799267b821e62d40 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 26 Apr 2016 22:35:31 +0200 Subject: [PATCH 010/261] add babl --- stack.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stack.yaml b/stack.yaml index 97425ce..638943f 100644 --- a/stack.yaml +++ b/stack.yaml @@ -6,7 +6,8 @@ resolver: lts-5.1 # Local packages, usually specified by relative directory name packages: - '.' -- "../gegl" +- '../gegl' +- '../babl' # Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) extra-deps: [] From 2b69376fb74eabd2d53c8786a87137f5ea643ad4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 29 May 2016 18:01:23 +0200 Subject: [PATCH 011/261] merging with affection-examples --- affection.cabal | 33 ++++++++++++++++++++++++++++++--- examples/example00.hs | 10 ++++++++++ examples/example01.hs | 9 +++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 examples/example00.hs create mode 100644 examples/example01.hs diff --git a/affection.cabal b/affection.cabal index c44e398..0dee209 100644 --- a/affection.cabal +++ b/affection.cabal @@ -23,6 +23,13 @@ build-type: Simple cabal-version: >=1.10 +source-repository head + type: git + location: https://github.com/nek0/affection + +flag examples + description: Build example programs + default: False library exposed-modules: Affection @@ -50,6 +57,26 @@ library , gegl -- , sdl2-image -source-repository head - type: git - location: https://github.com/nek0/affection +executable example00 + hs-source-dirs: examples + main-is: example00.hs + ghc-options: -threaded -Wall + default-language: Haskell2010 + default-extensions: OverloadedStrings + if flag(examples) + build-depends: base + , affection + 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 + else + buildable: False diff --git a/examples/example00.hs b/examples/example00.hs new file mode 100644 index 0000000..3d67ac1 --- /dev/null +++ b/examples/example00.hs @@ -0,0 +1,10 @@ +import Affection +import Affection.Render + +main :: IO () +main = withAllAffection $ + withDefaultWindow "test" $ do + changeColor $ RGBA 255 255 255 255 + clear + present + liftIO $ delaySec 2 diff --git a/examples/example01.hs b/examples/example01.hs new file mode 100644 index 0000000..b0050b5 --- /dev/null +++ b/examples/example01.hs @@ -0,0 +1,9 @@ +import Affection + +main :: IO () +main = withAllAffection $ + withDefaultWindow "test" $ do + changeColor $ RGBA 255 255 255 255 + clear + present + liftIO $ delaySec 2 From 42cdb7e246c9146c22ee2201a35cc6c2ac96d72f Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 16 Oct 2016 17:01:31 +0200 Subject: [PATCH 012/261] updating to reality Preparing to incorporate more of gegl, since it has matured a little bit. --- affection.cabal | 2 +- src/Affection.hs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index 0dee209..e177d91 100644 --- a/affection.cabal +++ b/affection.cabal @@ -49,7 +49,7 @@ library hs-source-dirs: src default-language: Haskell2010 -- Other library packages from which modules are imported. - build-depends: base >=4.8 && <4.9 + build-depends: base >=4.9 && <4.10 , sdl2 , text , linear diff --git a/src/Affection.hs b/src/Affection.hs index 3263691..0ea48b9 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -23,8 +23,12 @@ withAllAffection ops = do withWindow :: Monad m => Text -> WindowConfig -> RendererConfig -> RenderT m a -> IO () withWindow title wconf rconf ops = do window <- createWindow title wconf + -- I don't need a renderer here, i need a surface renderer <- createRenderer window (-1) rconf + surface <- getWindowSurface window gegl_init + -- I think I need some AffectionT or someting similar here and not a RenderT + -- from SDL. inRender renderer $ ops gegl_exit destroyWindow window From c49f41673cb30b8be68788c2ff76937637e6e308 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 16 Oct 2016 17:02:28 +0200 Subject: [PATCH 013/261] destackify --- stack.yaml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 stack.yaml diff --git a/stack.yaml b/stack.yaml deleted file mode 100644 index 638943f..0000000 --- a/stack.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# For more information, see: http://docs.haskellstack.org/en/stable/yaml_configuration.html - -# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) -resolver: lts-5.1 - -# Local packages, usually specified by relative directory name -packages: -- '.' -- '../gegl' -- '../babl' - -# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) -extra-deps: [] - -# Override default flag values for local packages and extra-deps -flags: {} - -# Extra package databases containing global packages -extra-package-dbs: [] - -# Control whether we use the GHC we find on the path -# system-ghc: true - -# Require a specific version of stack, using version ranges -# require-stack-version: -any # Default -# require-stack-version: >= 1.0.0 - -# Override the architecture used by stack, especially useful on Windows -# arch: i386 -# arch: x86_64 - -# Extra directories used by stack for building -# extra-include-dirs: [/path/to/dir] -# extra-lib-dirs: [/path/to/dir] - -# Allow a newer minor version of GHC than the snapshot specifies -# compiler-check: newer-minor From 4d7f8fb3546db995ed7eb097a0d68be61217f8ab Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 31 Oct 2016 23:47:16 +0100 Subject: [PATCH 014/261] working types, but no working implementation --- affection.cabal | 2 ++ src/Affection.hs | 69 ++++++++++++++++++++++++----------- src/Affection/Types.hs | 81 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 21 deletions(-) diff --git a/affection.cabal b/affection.cabal index e177d91..1155bd4 100644 --- a/affection.cabal +++ b/affection.cabal @@ -55,6 +55,8 @@ library , linear , mtl , gegl + , monad-loops + , timeit -- , sdl2-image executable example00 diff --git a/src/Affection.hs b/src/Affection.hs index 0ea48b9..b09b42c 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -1,5 +1,6 @@ +{-# LANGUAGE RecordWildCards #-} module Affection - ( withAllAffection + ( withAffection , withWindow , withDefaultWindow , delaySec @@ -7,34 +8,62 @@ module Affection , module Affection.Types ) where -import SDL -import Data.Text -import GEGL +import qualified SDL +import qualified GEGL as G + +import qualified Data.Text as T +import Data.Maybe + +import System.TimeIt (timeItT) + +import Control.Monad.Loops +import Control.Monad.State +import Control.Monad.Reader +import Control.Concurrent.MVar import Affection.Render -import Affection.Types +import Affection.Types as Types -withAllAffection :: IO () -> IO () -withAllAffection ops = do - initializeAll - ops - quit +withAffection :: AffectionConfig us -> IO () +withAffection conf@AffectionConfig{..} = do + case initComponents of + All -> + SDL.initializeAll + Only is -> + SDL.initialize is + execTime <- newMVar 0 + window <- SDL.createWindow windowTitle windowConfig + surface <- SDL.getWindowSurface window + let initContainer = AffectionData conf False userData window surface + state <- newMVar initContainer + (res, nState) <- runStateT ( + whileM_ (do + current <- get + return $ Types.quitEvent current + ) + $ do + lastTime <- liftIO $ fromMaybe 0 <$> tryReadMVar execTime + (dTime, _) <- liftIO $ timeItT <$> drawLoop + (uTime, _) <- liftIO $ timeItT <$> updateLoop lastTime + liftIO $ putMVar execTime $ lastTime + dTime + uTime + ) initContainer + SDL.quit -withWindow :: Monad m => Text -> WindowConfig -> RendererConfig -> RenderT m a -> IO () +withWindow :: Monad m => T.Text -> WindowConfig -> SDL.RendererConfig -> RenderT m a -> IO () withWindow title wconf rconf ops = do - window <- createWindow title wconf + window <- SDL.createWindow title wconf -- I don't need a renderer here, i need a surface - renderer <- createRenderer window (-1) rconf - surface <- getWindowSurface window - gegl_init + renderer <- SDL.createRenderer window (-1) rconf + surface <- SDL.getWindowSurface window + G.gegl_init -- I think I need some AffectionT or someting similar here and not a RenderT -- from SDL. inRender renderer $ ops - gegl_exit - destroyWindow window + G.gegl_exit + SDL.destroyWindow window -withDefaultWindow :: Monad m => Text -> (RenderT m a) -> IO () -withDefaultWindow title ops = withWindow title defaultWindow defaultRenderer ops +withDefaultWindow :: Monad m => T.Text -> (RenderT m a) -> IO () +withDefaultWindow title ops = withWindow title defaultWindow SDL.defaultRenderer ops delaySec :: Int -> IO () -delaySec dur = delay (fromIntegral $ dur * 1000) +delaySec dur = SDL.delay (fromIntegral $ dur * 1000) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 4807ab4..44eebc2 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -1,7 +1,86 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-} module Affection.Types - ( RGBA(..) + ( AffectionData(..) + , AffectionConfig(..) + -- , AffectionState(..) + -- , AffectionDraw(..) + -- , Draw(..) + , AffectionStateInner(..) + -- , AffectionDrawInner(..) + , InitComponents(..) + -- , Loop(..) + , RGBA(..) + , SDL.WindowConfig(..) + , SDL.defaultWindow ) where +import qualified SDL.Init as SDL +import qualified SDL.Video as SDL +import qualified Data.Text as T + +import Control.Monad.IO.Class +import Control.Monad.State +import Control.Monad.Reader + +import Control.Concurrent.MVar + +-- | Configuration for the aplication. needed at startup. +data AffectionConfig us = AffectionConfig + { initComponents :: InitComponents -- ^ SDL components to initialize at startup + , windowTitle :: T.Text -- ^ Window title + , windowConfig :: SDL.WindowConfig -- ^ Window configuration + , drawLoop :: AffectionState (AffectionData us) IO (IO ()) -- ^ Some function. Type to be determined. + , updateLoop :: Double -> AffectionState us IO (IO ()) -- ^ Another function. Type to be determined + , userData :: us + } + +-- | 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 + , drawSurface :: SDL.Surface -- ^ SDL surface + } + +-- -- | Data and surfaces for drawing. +-- data AffectionDraw dd = AffectionDraw +-- } + +-- | Components to initialize in SDL. +data InitComponents + = All + | Only [SDL.InitFlag] + +-- | Inner 'StateT' monad for the update state +type AffectionStateInner us m a = StateT (AffectionData us) m a + +-- | Affection's state monad +newtype AffectionState us m a = AffectionState + { runState :: AffectionStateInner us m a } + deriving (Functor, Applicative, Monad, MonadIO) + +-- -- | 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 From 6ff8df056b9a07f124907c230c6e13fda86cf248 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 2 Nov 2016 01:14:53 +0100 Subject: [PATCH 015/261] it compiles! --- affection.cabal | 2 +- src/Affection.hs | 26 +++++++++++++++++--------- src/Affection/Types.hs | 9 +++++---- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/affection.cabal b/affection.cabal index 1155bd4..4fbe4c3 100644 --- a/affection.cabal +++ b/affection.cabal @@ -56,7 +56,7 @@ library , mtl , gegl , monad-loops - , timeit + , clock -- , sdl2-image executable example00 diff --git a/src/Affection.hs b/src/Affection.hs index b09b42c..ce4aad1 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -5,7 +5,7 @@ module Affection , withDefaultWindow , delaySec , module Affection.Render - , module Affection.Types + , module Types ) where import qualified SDL @@ -14,7 +14,7 @@ import qualified GEGL as G import qualified Data.Text as T import Data.Maybe -import System.TimeIt (timeItT) +import System.Clock import Control.Monad.Loops import Control.Monad.State @@ -31,21 +31,29 @@ withAffection conf@AffectionConfig{..} = do SDL.initializeAll Only is -> SDL.initialize is - execTime <- newMVar 0 + execTime <- newMVar =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig surface <- SDL.getWindowSurface window - let initContainer = AffectionData conf False userData window surface + initContainer <- return $ AffectionData + { affectionConfig = conf + , quitEvent = False + , userState = userData + , drawWindow = window + , drawSurface = surface + } state <- newMVar initContainer - (res, nState) <- runStateT ( + (res, nState) <- runStateT ( Types.runState $ whileM_ (do current <- get return $ Types.quitEvent current ) $ do - lastTime <- liftIO $ fromMaybe 0 <$> tryReadMVar execTime - (dTime, _) <- liftIO $ timeItT <$> drawLoop - (uTime, _) <- liftIO $ timeItT <$> updateLoop lastTime - liftIO $ putMVar execTime $ lastTime + dTime + uTime + now <- liftIO $ getTime Monotonic + lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime + drawLoop + updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / + (fromIntegral 10 ^ 9) + liftIO $ putMVar execTime $ now ) initContainer SDL.quit diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 44eebc2..3cf0e65 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -2,7 +2,7 @@ module Affection.Types ( AffectionData(..) , AffectionConfig(..) - -- , AffectionState(..) + , AffectionState(..) -- , AffectionDraw(..) -- , Draw(..) , AffectionStateInner(..) @@ -30,7 +30,7 @@ data AffectionConfig us = AffectionConfig , windowTitle :: T.Text -- ^ Window title , windowConfig :: SDL.WindowConfig -- ^ Window configuration , drawLoop :: AffectionState (AffectionData us) IO (IO ()) -- ^ Some function. Type to be determined. - , updateLoop :: Double -> AffectionState us IO (IO ()) -- ^ Another function. Type to be determined + , updateLoop :: Double -> AffectionState (AffectionData us) IO (IO ()) -- ^ main update function. Takes nanoseconds as input. , userData :: us } @@ -53,12 +53,13 @@ data InitComponents | Only [SDL.InitFlag] -- | Inner 'StateT' monad for the update state -type AffectionStateInner us m a = StateT (AffectionData us) m a +-- type AffectionStateInner us m a = StateT (AffectionData us) m a +type AffectionStateInner us m a = StateT us m a -- | Affection's state monad newtype AffectionState us m a = AffectionState { runState :: AffectionStateInner us m a } - deriving (Functor, Applicative, Monad, MonadIO) + deriving (Functor, Applicative, Monad, MonadIO, MonadState us) -- -- | Inner 'StateT' monad of Affection -- type AffectionInner us od a = StateT (AffectionState us od) IO a From f50bd1afe8fafcf9a40d804e007e57ec9580bb03 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 4 Nov 2016 16:06:16 +0100 Subject: [PATCH 016/261] get first example somewhat working --- affection.cabal | 5 +++ examples/example00.hs | 89 ++++++++++++++++++++++++++++++++++++++---- src/Affection.hs | 23 ++++++----- src/Affection/Types.hs | 22 +++++++---- 4 files changed, 115 insertions(+), 24 deletions(-) diff --git a/affection.cabal b/affection.cabal index 4fbe4c3..c65c64c 100644 --- a/affection.cabal +++ b/affection.cabal @@ -68,6 +68,11 @@ executable example00 if flag(examples) build-depends: base , affection + , sdl2 + , gegl + , babl + , containers + , mtl else buildable: False diff --git a/examples/example00.hs b/examples/example00.hs index 3d67ac1..f4a78ce 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -1,10 +1,85 @@ +{-# LANGUAGE RecordWildCards #-} + import Affection -import Affection.Render +import qualified SDL +import qualified SDL.Raw as Raw +import qualified GEGL as G +import qualified BABL as B +import qualified Data.Map.Strict as M + +import Foreign.Storable (peek) +import Foreign.C.Types (CInt(..)) + +import Debug.Trace + +-- main :: IO () +-- main = withAllAffection $ +-- withDefaultWindow "test" $ do +-- changeColor $ RGBA 255 255 255 255 +-- clear +-- present +-- liftIO $ delaySec 2 main :: IO () -main = withAllAffection $ - withDefaultWindow "test" $ do - changeColor $ RGBA 255 255 255 255 - clear - present - liftIO $ delaySec 2 +main = do + conf <- return $ AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + , drawLoop = draw + , updateLoop = update + , loadState = load + } + withAffection conf + +type UserData = M.Map String G.GeglNode + +load :: SDL.Surface -> IO UserData +load _ = do + traceM "loading" + root <- G.gegl_node_new + traceM "new root node" + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation + [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 + , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 + ] + traceM "checkerboard" + myMap <- return $ M.fromList + [ ("root" , root) + , ("checkerboard", checkerboard) + ] + traceM "loading complete" + return myMap + +draw :: AffectionState (AffectionData UserData) IO () +draw = do + traceM "drawing" + AffectionData{..} <- get + liftIO $ SDL.lockSurface drawSurface + pixels <- liftIO $ SDL.surfacePixels drawSurface + let SDL.Surface rawSurfacePtr _ = drawSurface + rawSurface <- liftIO $ peek rawSurfacePtr + pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface + format <- liftIO $ (B.babl_format $ B.PixelFormat B.R'G'B'A B.CFu8) + SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface + let (w, h) = (fromIntegral rw, fromIntegral rh) + liftIO $ G.gegl_node_blit + (userState M.! "checkerboard" :: G.GeglNode) + 1 + (G.GeglRectangle 0 0 w h) + format + pixels + (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) + [G.GeglBlitDefault] + liftIO $ SDL.unlockSurface drawSurface + liftIO $ SDL.updateWindowSurface drawWindow + +update :: Double -> AffectionState (AffectionData UserData) IO () +update sec = do + traceM "updating" + -- traceM $ show sec + -- liftIO $ delaySec 5 + -- ud@AffectionData{..} <- get + -- put $ ud + -- { quitEvent = True + -- } diff --git a/src/Affection.hs b/src/Affection.hs index ce4aad1..756ef9c 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -4,6 +4,8 @@ module Affection , withWindow , withDefaultWindow , delaySec + , get + , put , module Affection.Render , module Types ) where @@ -31,30 +33,33 @@ withAffection conf@AffectionConfig{..} = do SDL.initializeAll Only is -> SDL.initialize is + G.gegl_init execTime <- newMVar =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig surface <- SDL.getWindowSurface window - initContainer <- return $ AffectionData - { affectionConfig = conf - , quitEvent = False - , userState = userData + initContainer <- return . (\x -> AffectionData + -- { affectionConfig = conf + { quitEvent = False + , userState = x , drawWindow = window , drawSurface = surface - } - state <- newMVar initContainer + }) =<< loadState surface (res, nState) <- runStateT ( Types.runState $ whileM_ (do current <- get - return $ Types.quitEvent current + return $ not $ Types.quitEvent current ) - $ do + (do now <- liftIO $ getTime Monotonic lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime drawLoop updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) - liftIO $ putMVar execTime $ now + _ <- liftIO $ swapMVar execTime $ now + return () + ) ) initContainer + G.gegl_exit SDL.quit withWindow :: Monad m => T.Text -> WindowConfig -> SDL.RendererConfig -> RenderT m a -> IO () diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 3cf0e65..eca0baf 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -26,18 +26,24 @@ import Control.Concurrent.MVar -- | Configuration for the aplication. needed at startup. data AffectionConfig us = AffectionConfig - { initComponents :: InitComponents -- ^ SDL components to initialize at startup - , windowTitle :: T.Text -- ^ Window title - , windowConfig :: SDL.WindowConfig -- ^ Window configuration - , drawLoop :: AffectionState (AffectionData us) IO (IO ()) -- ^ Some function. Type to be determined. - , updateLoop :: Double -> AffectionState (AffectionData us) IO (IO ()) -- ^ main update function. Takes nanoseconds as input. - , userData :: us + { initComponents :: InitComponents + -- ^ SDL components to initialize at startup + , windowTitle :: T.Text + -- ^ Window title + , windowConfig :: SDL.WindowConfig + -- ^ Window configuration + , drawLoop :: AffectionState (AffectionData us) IO () + -- ^ Function for updating graphics. + , updateLoop :: Double -> AffectionState (AffectionData us) IO () + -- ^ Main update function. Takes fractions of a second as input. + , loadState :: SDL.Surface -> IO us + -- ^ Provide your own load function to create this data. } -- | 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. + -- { affectionConfig :: AffectionConfig us -- ^ Application configuration. + { quitEvent :: Bool -- ^ Loop breaker. , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window , drawSurface :: SDL.Surface -- ^ SDL surface From d847ef18d287653c1c1f0234d2ac337305701f2b Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 4 Nov 2016 16:06:44 +0100 Subject: [PATCH 017/261] no second example for now --- affection.cabal | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/affection.cabal b/affection.cabal index c65c64c..f5aa1e1 100644 --- a/affection.cabal +++ b/affection.cabal @@ -76,14 +76,14 @@ executable example00 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 - 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 +-- else +-- buildable: False From e11312fb5df69260918488092e934d853b3b4a8a Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 4 Nov 2016 16:38:13 +0100 Subject: [PATCH 018/261] terminate after 5 seconds --- examples/example00.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index f4a78ce..5e0eb1f 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -78,8 +78,8 @@ update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do traceM "updating" -- traceM $ show sec - -- liftIO $ delaySec 5 - -- ud@AffectionData{..} <- get - -- put $ ud - -- { quitEvent = True - -- } + liftIO $ delaySec 5 + ud@AffectionData{..} <- get + put $ ud + { quitEvent = True + } From 211cbd85ad8a1d5ab507104ee2a34c1c275cfdac Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 4 Nov 2016 19:05:43 +0100 Subject: [PATCH 019/261] now using built-in timing facilities --- examples/example00.hs | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index 5e0eb1f..5601cb9 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -32,7 +32,10 @@ main = do } withAffection conf -type UserData = M.Map String G.GeglNode +data UserData = UserData + { nodeGraph :: M.Map String G.GeglNode + , elapsedTime :: Double + } load :: SDL.Surface -> IO UserData load _ = do @@ -49,12 +52,16 @@ load _ = do , ("checkerboard", checkerboard) ] traceM "loading complete" - return myMap + return $ UserData + { nodeGraph = myMap + , elapsedTime = 0 + } draw :: AffectionState (AffectionData UserData) IO () draw = do traceM "drawing" AffectionData{..} <- get + let UserData{..} = userState liftIO $ SDL.lockSurface drawSurface pixels <- liftIO $ SDL.surfacePixels drawSurface let SDL.Surface rawSurfacePtr _ = drawSurface @@ -64,7 +71,7 @@ draw = do SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) liftIO $ G.gegl_node_blit - (userState M.! "checkerboard" :: G.GeglNode) + (nodeGraph M.! "checkerboard" :: G.GeglNode) 1 (G.GeglRectangle 0 0 w h) format @@ -77,9 +84,18 @@ draw = do update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do traceM "updating" - -- traceM $ show sec - liftIO $ delaySec 5 - ud@AffectionData{..} <- get - put $ ud - { quitEvent = True - } + -- liftIO $ delaySec 5 + ad@AffectionData{..} <- get + let ud@UserData{..} = userState + traceM $ show elapsedTime + if elapsedTime < 5 + then + put $ ad + { userState = ud + { elapsedTime = elapsedTime + sec + } + } + else + put $ ad + { quitEvent = True + } From 1ccdf4ad75d9297357b669a98c484a285cf21bce Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 5 Nov 2016 00:41:49 +0100 Subject: [PATCH 020/261] showing text --- examples/example00.hs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index 5601cb9..f3092b4 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -47,9 +47,22 @@ load _ = do , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 ] traceM "checkerboard" + over <- G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + text <- G.gegl_node_new_child root $ G.textOperation + [ G.Property "string" $ G.PropertyString "Hello world!" + , G.Property "color" $ G.PropertyColor $ G.RGB 0 0 1 + , G.Property "size" $ G.PropertyDouble 40 + ] + traceM "text" + G.gegl_node_link checkerboard over + G.gegl_node_connect_to text "output" over "aux" + traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) + , ("over" , over) , ("checkerboard", checkerboard) + , ("text" , text) ] traceM "loading complete" return $ UserData @@ -67,11 +80,11 @@ draw = do let SDL.Surface rawSurfacePtr _ = drawSurface rawSurface <- liftIO $ peek rawSurfacePtr pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface - format <- liftIO $ (B.babl_format $ B.PixelFormat B.R'G'B'A B.CFu8) + format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) liftIO $ G.gegl_node_blit - (nodeGraph M.! "checkerboard" :: G.GeglNode) + (nodeGraph M.! "over" :: G.GeglNode) 1 (G.GeglRectangle 0 0 w h) format From c7ab44e2f97067d18b69e6f8908c8f1c9c149d78 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 6 Nov 2016 05:02:06 +0100 Subject: [PATCH 021/261] cleanup and documentation --- examples/example00.hs | 2 +- src/Affection.hs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index f3092b4..f9438f6 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -51,7 +51,7 @@ load _ = do traceM "over" text <- G.gegl_node_new_child root $ G.textOperation [ G.Property "string" $ G.PropertyString "Hello world!" - , G.Property "color" $ G.PropertyColor $ G.RGB 0 0 1 + -- , G.Property "color" $ G.PropertyColor $ G.RGBA 0 0 1 0.1 , G.Property "size" $ G.PropertyDouble 40 ] traceM "text" diff --git a/src/Affection.hs b/src/Affection.hs index 756ef9c..3fe9a85 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -26,7 +26,10 @@ import Control.Concurrent.MVar import Affection.Render import Affection.Types as Types -withAffection :: AffectionConfig us -> IO () +-- | Main function which bootstraps everything else. +withAffection + :: AffectionConfig us -- ^ Configuration of the Game and its engine. + -> IO () withAffection conf@AffectionConfig{..} = do case initComponents of All -> @@ -38,7 +41,6 @@ withAffection conf@AffectionConfig{..} = do window <- SDL.createWindow windowTitle windowConfig surface <- SDL.getWindowSurface window initContainer <- return . (\x -> AffectionData - -- { affectionConfig = conf { quitEvent = False , userState = x , drawWindow = window @@ -62,6 +64,8 @@ withAffection conf@AffectionConfig{..} = do G.gegl_exit SDL.quit +-- | DEPRECATED! +-- Function for bootstraping a window. withWindow :: Monad m => T.Text -> WindowConfig -> SDL.RendererConfig -> RenderT m a -> IO () withWindow title wconf rconf ops = do window <- SDL.createWindow title wconf @@ -75,8 +79,13 @@ withWindow title wconf rconf ops = do G.gegl_exit SDL.destroyWindow window +-- | DEPRECATED! +-- Bootstrap a default window. withDefaultWindow :: Monad m => T.Text -> (RenderT m a) -> IO () withDefaultWindow title ops = withWindow title defaultWindow SDL.defaultRenderer ops -delaySec :: Int -> IO () +-- | block a thread for a specified amount of time +delaySec + :: Int -- ^ Number of seconds + -> IO () delaySec dur = SDL.delay (fromIntegral $ dur * 1000) From 678c830e33948f71565f83ae45d5d441ac39dc47 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 8 Nov 2016 04:31:51 +0100 Subject: [PATCH 022/261] example now working with the right colours This was an issue with the default surface created by sdl, which had the wrong color format. For the future it is advised to use the RGBA CFu8 format from babl, or things may break again. --- examples/example00.hs | 2 +- src/Affection.hs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index f9438f6..993b805 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -51,7 +51,7 @@ load _ = do traceM "over" text <- G.gegl_node_new_child root $ G.textOperation [ G.Property "string" $ G.PropertyString "Hello world!" - -- , G.Property "color" $ G.PropertyColor $ G.RGBA 0 0 1 0.1 + , G.Property "color" $ G.PropertyColor $ G.RGBA 0 0 1 0.1 , G.Property "size" $ G.PropertyDouble 40 ] traceM "text" diff --git a/src/Affection.hs b/src/Affection.hs index 3fe9a85..f40438f 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -11,6 +11,7 @@ module Affection ) where import qualified SDL +import qualified SDL.Raw as Raw import qualified GEGL as G import qualified Data.Text as T @@ -39,7 +40,8 @@ withAffection conf@AffectionConfig{..} = do G.gegl_init execTime <- newMVar =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig - surface <- SDL.getWindowSurface window + oldSurf@(SDL.Surface ptr vect) <- SDL.getWindowSurface window + surface <- (\x -> return $ SDL.Surface x Nothing) =<< Raw.convertSurfaceFormat ptr Raw.SDL_PIXELFORMAT_ABGR8888 0 initContainer <- return . (\x -> AffectionData { quitEvent = False , userState = x @@ -52,6 +54,7 @@ withAffection conf@AffectionConfig{..} = do return $ not $ Types.quitEvent current ) (do + liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing now <- liftIO $ getTime Monotonic lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime drawLoop From 298ad4076be414911d18a36d9ead1035c105165f Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 8 Nov 2016 04:34:26 +0100 Subject: [PATCH 023/261] prettyfiing --- src/Affection.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Affection.hs b/src/Affection.hs index f40438f..362427f 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -41,7 +41,8 @@ withAffection conf@AffectionConfig{..} = do execTime <- newMVar =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig oldSurf@(SDL.Surface ptr vect) <- SDL.getWindowSurface window - surface <- (\x -> return $ SDL.Surface x Nothing) =<< Raw.convertSurfaceFormat ptr Raw.SDL_PIXELFORMAT_ABGR8888 0 + surface <- (\x -> return $ SDL.Surface x Nothing) =<< + Raw.convertSurfaceFormat ptr Raw.SDL_PIXELFORMAT_ABGR8888 0 initContainer <- return . (\x -> AffectionData { quitEvent = False , userState = x From 79cdc4934a5fcc28113fb638e80420d0e53c2622 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 8 Nov 2016 05:15:44 +0100 Subject: [PATCH 024/261] show and reduce warnings --- affection.cabal | 1 + src/Affection.hs | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/affection.cabal b/affection.cabal index f5aa1e1..ce8a6fd 100644 --- a/affection.cabal +++ b/affection.cabal @@ -48,6 +48,7 @@ library , UndecidableInstances hs-source-dirs: src default-language: Haskell2010 + ghc-options: -Wall -- Other library packages from which modules are imported. build-depends: base >=4.9 && <4.10 , sdl2 diff --git a/src/Affection.hs b/src/Affection.hs index 362427f..205bcb9 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -21,7 +21,6 @@ import System.Clock import Control.Monad.Loops import Control.Monad.State -import Control.Monad.Reader import Control.Concurrent.MVar import Affection.Render @@ -31,7 +30,7 @@ import Affection.Types as Types withAffection :: AffectionConfig us -- ^ Configuration of the Game and its engine. -> IO () -withAffection conf@AffectionConfig{..} = do +withAffection AffectionConfig{..} = do case initComponents of All -> SDL.initializeAll @@ -40,16 +39,16 @@ withAffection conf@AffectionConfig{..} = do G.gegl_init execTime <- newMVar =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig - oldSurf@(SDL.Surface ptr vect) <- SDL.getWindowSurface window + oldSurf@(SDL.Surface ptr _) <- SDL.getWindowSurface window surface <- (\x -> return $ SDL.Surface x Nothing) =<< - Raw.convertSurfaceFormat ptr Raw.SDL_PIXELFORMAT_ABGR8888 0 + Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 initContainer <- return . (\x -> AffectionData { quitEvent = False , userState = x , drawWindow = window , drawSurface = surface }) =<< loadState surface - (res, nState) <- runStateT ( Types.runState $ + (_, _) <- runStateT ( Types.runState $ whileM_ (do current <- get return $ not $ Types.quitEvent current From 6ed98fae2b374cbbc107865becfa76f4984e8f2f Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 13 Nov 2016 13:39:25 +0100 Subject: [PATCH 025/261] removed old module and added cleanup function --- affection.cabal | 2 +- src/Affection.hs | 49 ++++++++++++----------- src/Affection/Render.hs | 86 ----------------------------------------- src/Affection/Types.hs | 10 +++-- 4 files changed, 32 insertions(+), 115 deletions(-) delete mode 100644 src/Affection/Render.hs diff --git a/affection.cabal b/affection.cabal index ce8a6fd..db5ea25 100644 --- a/affection.cabal +++ b/affection.cabal @@ -33,7 +33,7 @@ flag examples library exposed-modules: Affection - , Affection.Render + -- , Affection.Render , Affection.Types default-extensions: OverloadedStrings diff --git a/src/Affection.hs b/src/Affection.hs index 205bcb9..ad82c5e 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -1,12 +1,11 @@ {-# LANGUAGE RecordWildCards #-} module Affection ( withAffection - , withWindow - , withDefaultWindow + -- , withWindow + -- , withDefaultWindow , delaySec , get , put - , module Affection.Render , module Types ) where @@ -23,7 +22,6 @@ import Control.Monad.Loops import Control.Monad.State import Control.Concurrent.MVar -import Affection.Render import Affection.Types as Types -- | Main function which bootstraps everything else. @@ -48,16 +46,16 @@ withAffection AffectionConfig{..} = do , drawWindow = window , drawSurface = surface }) =<< loadState surface - (_, _) <- runStateT ( Types.runState $ + (_, nState) <- runStateT ( Types.runState $ whileM_ (do current <- get return $ not $ Types.quitEvent current ) (do - liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing now <- liftIO $ getTime Monotonic lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime drawLoop + liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) _ <- liftIO $ swapMVar execTime $ now @@ -65,27 +63,28 @@ withAffection AffectionConfig{..} = do ) ) initContainer G.gegl_exit + cleanUp $ userState nState SDL.quit --- | DEPRECATED! --- Function for bootstraping a window. -withWindow :: Monad m => T.Text -> WindowConfig -> SDL.RendererConfig -> RenderT m a -> IO () -withWindow title wconf rconf ops = do - window <- SDL.createWindow title wconf - -- I don't need a renderer here, i need a surface - renderer <- SDL.createRenderer window (-1) rconf - surface <- SDL.getWindowSurface window - G.gegl_init - -- I think I need some AffectionT or someting similar here and not a RenderT - -- from SDL. - inRender renderer $ ops - G.gegl_exit - SDL.destroyWindow window - --- | DEPRECATED! --- Bootstrap a default window. -withDefaultWindow :: Monad m => T.Text -> (RenderT m a) -> IO () -withDefaultWindow title ops = withWindow title defaultWindow SDL.defaultRenderer ops +-- -- | DEPRECATED! +-- -- Function for bootstraping a window. +-- withWindow :: Monad m => T.Text -> WindowConfig -> SDL.RendererConfig -> RenderT m a -> IO () +-- withWindow title wconf rconf ops = do +-- window <- SDL.createWindow title wconf +-- -- I don't need a renderer here, i need a surface +-- renderer <- SDL.createRenderer window (-1) rconf +-- surface <- SDL.getWindowSurface window +-- G.gegl_init +-- -- I think I need some AffectionT or someting similar here and not a RenderT +-- -- from SDL. +-- inRender renderer $ ops +-- G.gegl_exit +-- SDL.destroyWindow window +-- +-- -- | DEPRECATED! +-- -- Bootstrap a default window. +-- withDefaultWindow :: Monad m => T.Text -> (RenderT m a) -> IO () +-- withDefaultWindow title ops = withWindow title defaultWindow SDL.defaultRenderer ops -- | block a thread for a specified amount of time delaySec diff --git a/src/Affection/Render.hs b/src/Affection/Render.hs deleted file mode 100644 index 744a17b..0000000 --- a/src/Affection/Render.hs +++ /dev/null @@ -1,86 +0,0 @@ -{-# LANGUAGE GADTs, KindSignatures, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} - -module Affection.Render - ( RenderM - , inRender - , RenderT - , liftIO - , changeColor - , clear - , present - ) where - -import Affection.Types -import qualified SDL -import Linear (V4(..)) -import Control.Monad.Trans - -data RenderM :: * -> * where - Pure :: a -> RenderM a - Bind :: RenderM a -> (a -> RenderM b) -> RenderM b - Render :: (SDL.Renderer -> IO a) -> RenderM a - -instance Functor RenderM where - fmap f m = Bind m (Pure . f) - -instance Applicative RenderM where - pure = Pure - mf <*> mx = Bind mf (\f -> Bind mx (\x -> Pure (f x))) - -instance Monad RenderM where - m >>= f = Bind m f - -inRenderM :: SDL.Renderer -> RenderM a -> IO a -inRenderM _ (Pure a) = return a -inRenderM r (Bind m f) = do - result <- inRenderM r m - inRenderM r (f result) -inRenderM r (Render f) = f r - -data RenderT :: (* -> *) -> * -> * where - TPure :: a -> RenderT m a - TBind :: RenderT m a -> (a -> RenderT m b) -> RenderT m b - -- RunRenderT :: RenderT IO a -> IO a - TLift :: (Monad m) => m a -> RenderT m a - TLiftIO :: IO a -> RenderT m a - TRender :: (SDL.Renderer -> IO a) -> RenderT RenderM a - -instance Functor (RenderT m) where - fmap f m = TBind m (TPure . f) - -instance Applicative (RenderT m) where - pure = TPure - mf <*> mx = TBind mf (\f -> TBind mx (\x -> TPure (f x))) - -instance Monad m => Monad (RenderT m) where - return = TPure - x >>= f = TBind x f - -instance MonadTrans RenderT where - lift = TLift - -instance Monad m => MonadIO (RenderT m) where - liftIO = TLiftIO - -inRender :: Monad m => SDL.Renderer -> RenderT m a -> IO a -inRender _ (TPure a) = return a -inRender r (TBind m f) = do - result <- inRender r m - inRender r (f result) --- inRender r (TLift x) = return x -inRender _ (TLiftIO x) = x -inRender r (TRender f) = f r - -changeColor :: RGBA -> RenderT RenderM () -changeColor (RGBA r g b a) = TRender $ \rend -> - SDL.rendererDrawColor rend SDL.$= V4 - (fromIntegral r) - (fromIntegral g) - (fromIntegral b) - (fromIntegral a) - -clear :: RenderT RenderM () -clear = TRender $ \r -> SDL.clear r - -present :: RenderT RenderM () -present = TRender $ \r -> SDL.present r diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index eca0baf..33618d8 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -5,13 +5,15 @@ module Affection.Types , AffectionState(..) -- , AffectionDraw(..) -- , Draw(..) - , AffectionStateInner(..) + , AffectionStateInner -- , AffectionDrawInner(..) , InitComponents(..) -- , Loop(..) , RGBA(..) , SDL.WindowConfig(..) , SDL.defaultWindow + -- | Convenience exports + , liftIO ) where import qualified SDL.Init as SDL @@ -20,9 +22,9 @@ import qualified Data.Text as T import Control.Monad.IO.Class import Control.Monad.State -import Control.Monad.Reader +-- import Control.Monad.Reader -import Control.Concurrent.MVar +-- import Control.Concurrent.MVar -- | Configuration for the aplication. needed at startup. data AffectionConfig us = AffectionConfig @@ -38,6 +40,8 @@ data AffectionConfig us = AffectionConfig -- ^ Main update function. Takes fractions of a second as input. , loadState :: SDL.Surface -> IO us -- ^ Provide your own load function to create this data. + , cleanUp :: us -> IO () + -- ^ Provide your own finisher function to clean your data. } -- | Main type for defining the look, feel and action of the whole application. From 7ae7c4e0807f42172655315a42e5e64168b05bae Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 13 Nov 2016 13:46:23 +0100 Subject: [PATCH 026/261] updated example --- examples/example00.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/example00.hs b/examples/example00.hs index 993b805..e8df2d8 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -29,6 +29,7 @@ main = do , drawLoop = draw , updateLoop = update , loadState = load + , cleanUp = clean } withAffection conf @@ -112,3 +113,6 @@ update sec = do put $ ad { quitEvent = True } + +clean :: UserData -> IO () +clean _ = return () From 04f6488a1ad15ddbfeae50765687aae19c2ad0e8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 18 Nov 2016 16:27:47 +0100 Subject: [PATCH 027/261] starting work on new example --- affection.cabal | 27 ++++--- examples/example01.hs | 167 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 177 insertions(+), 17 deletions(-) diff --git a/affection.cabal b/affection.cabal index db5ea25..5aea7e6 100644 --- a/affection.cabal +++ b/affection.cabal @@ -77,14 +77,19 @@ executable example00 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 --- 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 diff --git a/examples/example01.hs b/examples/example01.hs index b0050b5..35606e3 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -1,9 +1,164 @@ +{-# LANGUAGE RecordWildCards #-} + import Affection +import qualified SDL +import qualified SDL.Raw as Raw +import qualified GEGL as G +import qualified BABL as B +import qualified Data.Map.Strict as M + +import Foreign.Storable (peek) +import Foreign.C.Types + +import Debug.Trace + +-- main :: IO () +-- main = withAllAffection $ +-- withDefaultWindow "test" $ do +-- changeColor $ RGBA 255 255 255 255 +-- clear +-- present +-- liftIO $ delaySec 2 main :: IO () -main = withAllAffection $ - withDefaultWindow "test" $ do - changeColor $ RGBA 255 255 255 255 - clear - present - liftIO $ delaySec 2 +main = do + conf <- return $ AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + , drawLoop = draw + , updateLoop = update + , loadState = load + , cleanUp = clean + } + withAffection conf + +data UserData = UserData + { nodeGraph :: M.Map String G.GeglNode + , foreground :: G.GeglBuffer + , elapsedTime :: Double + } + +load :: SDL.Surface -> IO UserData +load _ = do + traceM "loading" + root <- G.gegl_node_new + traceM "new root node" + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation + [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 + , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 + ] + traceM "checkerboard" + over <- G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 20 20) =<< + B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation + [ G.Property "buffer" $ G.PropertyBuffer buffer + ] + traceM "buffer-source" + G.gegl_node_link checkerboard over + G.gegl_node_connect_to bufsrc "output" over "aux" + traceM "connections made" + myMap <- return $ M.fromList + [ ("root" , root) + , ("over" , over) + , ("background" , checkerboard) + , ("foreground" , bufsrc) + ] + let roi = G.GeglRectangle 0 0 20 20 + G.iterateOver + buffer + roi + (B.PixelFormat B.RGBA B.CFfloat) + G.GeglAccessReadWrite + G.GeglAbyssNone + (\(G.Pixel px py pc) -> + let dsqr = (((10 - px) ^ 2) + ((10 - py) ^ 2)) + (G.CVfloat (CFloat pr), G.CVfloat (CFloat pg), G.CVfloat (CFloat pb), G.CVfloat (CFloat pa)) = pc + dist = (sqrt (fromIntegral dsqr :: Float)) + in if dsqr < 100 + then + if dist < fromIntegral 9 + then + G.Pixel px py + ( G.CVfloat $ CFloat 0 + , G.CVfloat $ CFloat 0 + , G.CVfloat $ CFloat 0 + , G.CVfloat $ CFloat $ if pa < 1 then 1 else pa + ) + else + let alpha = fromIntegral 10 - dist + dst_a = pa + a = alpha + dst_a * (1 - alpha) + a_term = dst_a * (1 - alpha) + red = 0 * alpha + pr * a_term + gre = 0 * alpha + pg * a_term + blu = 0 * alpha + pb * a_term + in + G.Pixel px py + ( G.CVfloat $ CFloat $ red / a + , G.CVfloat $ CFloat $ gre / a + , G.CVfloat $ CFloat $ blu / a + , G.CVfloat $ CFloat $ if pa < alpha then alpha else pa + ) + else + G.Pixel px py pc + ) + traceM "loading complete" + return $ UserData + { nodeGraph = myMap + , foreground = buffer + , elapsedTime = 0 + } + +draw :: AffectionState (AffectionData UserData) IO () +draw = do + traceM "drawing" + AffectionData{..} <- get + let UserData{..} = userState + liftIO $ SDL.lockSurface drawSurface + pixels <- liftIO $ SDL.surfacePixels drawSurface + let SDL.Surface rawSurfacePtr _ = drawSurface + rawSurface <- liftIO $ peek rawSurfacePtr + pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface + format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) + SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface + let (w, h) = (fromIntegral rw, fromIntegral rh) + liftIO $ G.gegl_node_blit + (nodeGraph M.! "over" :: G.GeglNode) + 1 + (G.GeglRectangle 0 0 w h) + format + pixels + (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) + [G.GeglBlitDefault] + liftIO $ SDL.unlockSurface drawSurface + liftIO $ SDL.updateWindowSurface drawWindow + +update :: Double -> AffectionState (AffectionData UserData) IO () +update sec = do + traceM "updating" + -- liftIO $ delaySec 5 + ad@AffectionData{..} <- get + let ud@UserData{..} = userState + traceM $ show elapsedTime + if elapsedTime < 5 + then do + fg <- liftIO $ G.gegl_buffer_get_extent foreground + _ <- liftIO $ G.gegl_buffer_set_extent foreground $ fg + { G.rectangleX = G.rectangleX fg + 1 + , G.rectangleY = G.rectangleY fg + 1 + } + put $ ad + { userState = ud + { elapsedTime = elapsedTime + sec + } + } + else + put $ ad + { quitEvent = True + } + +clean :: UserData -> IO () +clean _ = return () From 334dba9e4860f0df86b3ce680aea9d1f0a020b87 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 18 Nov 2016 16:30:54 +0100 Subject: [PATCH 028/261] extended gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 993c34f..3d66116 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ .hsenv* .stack-work .git +.cabal-sandbox/ +cabal.sandbox.config +dist/ From 4e20d26c97c87aab6be741f6014ee361e598256f Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 8 Dec 2016 18:22:29 +0100 Subject: [PATCH 029/261] working on drawing primitives --- affection.cabal | 3 +- examples/example00.hs | 4 +-- examples/example01.hs | 79 +++++++++++++++++++++--------------------- src/Affection.hs | 10 +++--- src/Affection/Draw.hs | 71 +++++++++++++++++++++++++++++++++++++ src/Affection/Types.hs | 17 ++++++++- 6 files changed, 137 insertions(+), 47 deletions(-) create mode 100644 src/Affection/Draw.hs diff --git a/affection.cabal b/affection.cabal index 5aea7e6..4c15e11 100644 --- a/affection.cabal +++ b/affection.cabal @@ -33,7 +33,7 @@ flag examples library exposed-modules: Affection - -- , Affection.Render + , Affection.Draw , Affection.Types default-extensions: OverloadedStrings @@ -56,6 +56,7 @@ library , linear , mtl , gegl + , babl , monad-loops , clock -- , sdl2-image diff --git a/examples/example00.hs b/examples/example00.hs index e8df2d8..a240d6d 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -71,7 +71,7 @@ load _ = do , elapsedTime = 0 } -draw :: AffectionState (AffectionData UserData) IO () +draw :: Affection UserData () draw = do traceM "drawing" AffectionData{..} <- get @@ -95,7 +95,7 @@ draw = do liftIO $ SDL.unlockSurface drawSurface liftIO $ SDL.updateWindowSurface drawWindow -update :: Double -> AffectionState (AffectionData UserData) IO () +update :: Double -> Affection UserData () update sec = do traceM "updating" -- liftIO $ delaySec 5 diff --git a/examples/example01.hs b/examples/example01.hs index 35606e3..80fb49e 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -67,44 +67,44 @@ load _ = do , ("foreground" , bufsrc) ] let roi = G.GeglRectangle 0 0 20 20 - G.iterateOver - buffer - roi - (B.PixelFormat B.RGBA B.CFfloat) - G.GeglAccessReadWrite - G.GeglAbyssNone - (\(G.Pixel px py pc) -> - let dsqr = (((10 - px) ^ 2) + ((10 - py) ^ 2)) - (G.CVfloat (CFloat pr), G.CVfloat (CFloat pg), G.CVfloat (CFloat pb), G.CVfloat (CFloat pa)) = pc - dist = (sqrt (fromIntegral dsqr :: Float)) - in if dsqr < 100 - then - if dist < fromIntegral 9 - then - G.Pixel px py - ( G.CVfloat $ CFloat 0 - , G.CVfloat $ CFloat 0 - , G.CVfloat $ CFloat 0 - , G.CVfloat $ CFloat $ if pa < 1 then 1 else pa - ) - else - let alpha = fromIntegral 10 - dist - dst_a = pa - a = alpha + dst_a * (1 - alpha) - a_term = dst_a * (1 - alpha) - red = 0 * alpha + pr * a_term - gre = 0 * alpha + pg * a_term - blu = 0 * alpha + pb * a_term - in - G.Pixel px py - ( G.CVfloat $ CFloat $ red / a - , G.CVfloat $ CFloat $ gre / a - , G.CVfloat $ CFloat $ blu / a - , G.CVfloat $ CFloat $ if pa < alpha then alpha else pa - ) - else - G.Pixel px py pc - ) + -- G.iterateOver + -- buffer + -- roi + -- (B.PixelFormat B.RGBA B.CFfloat) + -- G.GeglAccessReadWrite + -- G.GeglAbyssNone + -- (\(G.Pixel px py pc) -> + -- let dsqr = (((10 - px) ^ 2) + ((10 - py) ^ 2)) + -- (G.CVfloat (CFloat pr), G.CVfloat (CFloat pg), G.CVfloat (CFloat pb), G.CVfloat (CFloat pa)) = pc + -- dist = (sqrt (fromIntegral dsqr :: Float)) + -- in if dsqr < 100 + -- then + -- if dist < fromIntegral 9 + -- then + -- G.Pixel px py + -- ( G.CVfloat $ CFloat 0 + -- , G.CVfloat $ CFloat 0 + -- , G.CVfloat $ CFloat 0 + -- , G.CVfloat $ CFloat $ if pa < 1 then 1 else pa + -- ) + -- else + -- let alpha = fromIntegral 10 - dist + -- dst_a = pa + -- a = alpha + dst_a * (1 - alpha) + -- a_term = dst_a * (1 - alpha) + -- red = 0 * alpha + pr * a_term + -- gre = 0 * alpha + pg * a_term + -- blu = 0 * alpha + pb * a_term + -- in + -- G.Pixel px py + -- ( G.CVfloat $ CFloat $ red / a + -- , G.CVfloat $ CFloat $ gre / a + -- , G.CVfloat $ CFloat $ blu / a + -- , G.CVfloat $ CFloat $ if pa < alpha then alpha else pa + -- ) + -- else + -- G.Pixel px py pc + -- ) traceM "loading complete" return $ UserData { nodeGraph = myMap @@ -112,7 +112,7 @@ load _ = do , elapsedTime = 0 } -draw :: AffectionState (AffectionData UserData) IO () +draw :: Affection UserData () draw = do traceM "drawing" AffectionData{..} <- get @@ -125,6 +125,7 @@ draw = do format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) + drawRect foreground (G.RGB 1 0 0) (Line 2) (G.GeglRectangle 5 5 200 200) liftIO $ G.gegl_node_blit (nodeGraph M.! "over" :: G.GeglNode) 1 diff --git a/src/Affection.hs b/src/Affection.hs index ad82c5e..addd755 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -6,10 +6,11 @@ module Affection , delaySec , get , put - , module Types + , module A ) where import qualified SDL +import qualified SDL.Internal.Numbered as SDL (toNumber) import qualified SDL.Raw as Raw import qualified GEGL as G @@ -22,7 +23,8 @@ import Control.Monad.Loops import Control.Monad.State import Control.Concurrent.MVar -import Affection.Types as Types +import Affection.Types as A +import Affection.Draw as A -- | Main function which bootstraps everything else. withAffection @@ -46,10 +48,10 @@ withAffection AffectionConfig{..} = do , drawWindow = window , drawSurface = surface }) =<< loadState surface - (_, nState) <- runStateT ( Types.runState $ + (_, nState) <- runStateT ( A.runState $ whileM_ (do current <- get - return $ not $ Types.quitEvent current + return $ not $ A.quitEvent current ) (do now <- liftIO $ getTime Monotonic diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs new file mode 100644 index 0000000..91d95b1 --- /dev/null +++ b/src/Affection/Draw.hs @@ -0,0 +1,71 @@ +{-# LANGUAGE RecordWildCards #-} + +-- | Module for drawing primitives +module Affection.Draw + ( drawRect + ) where + +import Affection.Types (DrawType(..), Affection, GeglRectangle(..), liftIO) + +import Foreign.C.Types + +import qualified BABL as B + +import qualified GEGL as G + +-- | Draw a rectangle on target buffer +drawRect + :: G.GeglBuffer -- ^ Target buffer + -> G.Color -- ^ Color to draw in + -> DrawType -- ^ Draw type + -> GeglRectangle -- ^ Dimensions of Rectangle + -> Affection a () +drawRect buf color dt rect@G.GeglRectangle{..} = + liftIO $ G.iterateOver buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ + (\(G.Pixel px py pc) -> + case dt of + Fill -> + let col = colorize pc color + in + G.Pixel px py col + Line width -> + if (px >= rectangleX && px <= (rectangleX + width) || + px <= (rectangleX + rectangleWidth) && px >= (rectangleX + rectangleWidth - width)) && + (py >= rectangleY && py <= (rectangleY + width) || + py <= (rectangleY + rectangleHeight) && py >= (rectangleY + rectangleHeight - width)) + then + let col = colorize pc color + in G.Pixel px py col + else + G.Pixel px py pc + ) + +-- | compute color for a single pixel +colorize + :: (G.ComponentValue, G.ComponentValue, G.ComponentValue, G.ComponentValue) -- ^ Pixel information in buffer + -> G.Color -- ^ Color to draw over + -> (G.ComponentValue, G.ComponentValue, G.ComponentValue, G.ComponentValue) -- ^ Resulting colour +colorize (rr, rg, rb, ra) col = + let (G.CVdouble (CDouble br)) = rr + (G.CVdouble (CDouble bg)) = rg + (G.CVdouble (CDouble bb)) = rb + (G.CVdouble (CDouble ba)) = ra + (cr, cg, cb) = case col of + G.RGBA r g b _ -> (r, g, b) + G.RGB r g b -> (r, g, b) + ca = case col of + G.RGBA _ _ _ a -> a + G.RGB _ _ _ -> 1 + alpha = ca + dst_a = ba + da = alpha + dst_a * (1 - alpha) + a_term = dst_a * (1 - alpha) + red = cr * alpha + br * a_term + gre = cg * alpha + bg * a_term + blu = cb * alpha + bb * a_term + in + ( G.CVdouble $ CDouble $ red / da + , G.CVdouble $ CDouble $ gre / da + , G.CVdouble $ CDouble $ blu / da + , G.CVdouble $ CDouble $ ca + ) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 33618d8..a710371 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -1,6 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-} module Affection.Types - ( AffectionData(..) + ( Affection + , AffectionData(..) , AffectionConfig(..) , AffectionState(..) -- , AffectionDraw(..) @@ -10,15 +11,20 @@ module Affection.Types , InitComponents(..) -- , Loop(..) , RGBA(..) + , DrawType(..) , SDL.WindowConfig(..) , SDL.defaultWindow -- | Convenience exports , liftIO + -- | GEGL reexports + , G.GeglRectangle(..) + , G.GeglBuffer(..) ) where import qualified SDL.Init as SDL import qualified SDL.Video as SDL import qualified Data.Text as T +import qualified GEGL as G import Control.Monad.IO.Class import Control.Monad.State @@ -71,6 +77,8 @@ newtype AffectionState us m a = AffectionState { runState :: AffectionStateInner us m a } deriving (Functor, Applicative, Monad, MonadIO, MonadState us) +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 -- @@ -98,3 +106,10 @@ data RGBA = RGBA , b :: Int , a :: Int } + +-- | Type for defining the draw type of draw functions +data DrawType + = Fill -- ^ Fill the specified area completely with color + | Line -- ^ only draw the outline of the area + { lineWidth :: Int -- ^ Width of line in pixels + } From af5526c6508d8c66b356aa0ce8c6111e95e56883 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 10 Dec 2016 23:49:51 +0100 Subject: [PATCH 030/261] simplifying drawing for performance purposes. Does not yet work --- src/Affection/Draw.hs | 46 +++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 91d95b1..ba90c64 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -19,26 +19,19 @@ drawRect -> G.Color -- ^ Color to draw in -> DrawType -- ^ Draw type -> GeglRectangle -- ^ Dimensions of Rectangle - -> Affection a () -drawRect buf color dt rect@G.GeglRectangle{..} = - liftIO $ G.iterateOver buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ - (\(G.Pixel px py pc) -> - case dt of - Fill -> - let col = colorize pc color - in - G.Pixel px py col - Line width -> - if (px >= rectangleX && px <= (rectangleX + width) || - px <= (rectangleX + rectangleWidth) && px >= (rectangleX + rectangleWidth - width)) && - (py >= rectangleY && py <= (rectangleY + width) || - py <= (rectangleY + rectangleHeight) && py >= (rectangleY + rectangleHeight - width)) - then - let col = colorize pc color - in G.Pixel px py col - else - G.Pixel px py pc + -> IO () +drawRect buf color (Fill) rect@G.GeglRectangle{..} = + G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ + (\(x, y) -> + let col = unsafeColorize color + in + G.Pixel x y col ) +drawRect buf color (Line size) rect@G.GeglRectangle{..} = do + drawRect buf color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) + drawRect buf color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) + drawRect buf color Fill (G.GeglRectangle (rectangleWidth - size) rectangleY size rectangleHeight) + drawRect buf color Fill (G.GeglRectangle rectangleX (rectangleHeight - size) rectangleWidth size) -- | compute color for a single pixel colorize @@ -69,3 +62,18 @@ colorize (rr, rg, rb, ra) col = , G.CVdouble $ CDouble $ blu / da , G.CVdouble $ CDouble $ ca ) + +unsafeColorize col = + let + (r, g, b) = case col of + G.RGBA cr cg cb _ -> (cr, cg, cb) + G.RGB cr cg cb -> (cr, cg, cb) + a = case col of + G.RGBA _ _ _ ca -> ca + G.RGB _ _ _ -> 1 + in + ( G.CVdouble $ CDouble $ r + , G.CVdouble $ CDouble $ g + , G.CVdouble $ CDouble $ b + , G.CVdouble $ CDouble $ a + ) From 4cc55e32e7b339b27e99c697d96b7804bc8df6cb Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 00:04:23 +0100 Subject: [PATCH 031/261] unlift drawing --- examples/example01.hs | 2 +- src/Affection/Draw.hs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index 80fb49e..4aba6f3 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -125,7 +125,7 @@ draw = do format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) - drawRect foreground (G.RGB 1 0 0) (Line 2) (G.GeglRectangle 5 5 200 200) + liftIO $ drawRect foreground (G.RGB 1 0 0) (Line 2) (G.GeglRectangle 10 10 500 500) liftIO $ G.gegl_node_blit (nodeGraph M.! "over" :: G.GeglNode) 1 diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 91d95b1..6e1c4e4 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -19,9 +19,9 @@ drawRect -> G.Color -- ^ Color to draw in -> DrawType -- ^ Draw type -> GeglRectangle -- ^ Dimensions of Rectangle - -> Affection a () + -> IO () drawRect buf color dt rect@G.GeglRectangle{..} = - liftIO $ G.iterateOver buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ + G.iterateOver buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ (\(G.Pixel px py pc) -> case dt of Fill -> From 93dd045f275c263dad6a373414483aef1d55e5f4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 00:09:57 +0100 Subject: [PATCH 032/261] simplified line width --- src/Affection/Draw.hs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 6e1c4e4..a31c96d 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -28,11 +28,9 @@ drawRect buf color dt rect@G.GeglRectangle{..} = let col = colorize pc color in G.Pixel px py col - Line width -> - if (px >= rectangleX && px <= (rectangleX + width) || - px <= (rectangleX + rectangleWidth) && px >= (rectangleX + rectangleWidth - width)) && - (py >= rectangleY && py <= (rectangleY + width) || - py <= (rectangleY + rectangleHeight) && py >= (rectangleY + rectangleHeight - width)) + Line size -> + if not ((px >= (rectangleX + size) && px < (rectangleX + rectangleWidth - size)) && + (py >= (rectangleY + size) && py < (rectangleY + rectangleHeight - size))) then let col = colorize pc color in G.Pixel px py col From 3757622475e2f389dc26cb79e9d5528a5edba9a5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 01:42:06 +0100 Subject: [PATCH 033/261] works now and is fast --- examples/example01.hs | 2 +- src/Affection/Draw.hs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index 80fb49e..16d3f2e 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -51,7 +51,7 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 20 20) =<< + buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation [ G.Property "buffer" $ G.PropertyBuffer buffer diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index ba90c64..1b1c84a 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -30,8 +30,8 @@ drawRect buf color (Fill) rect@G.GeglRectangle{..} = drawRect buf color (Line size) rect@G.GeglRectangle{..} = do drawRect buf color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) drawRect buf color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) - drawRect buf color Fill (G.GeglRectangle (rectangleWidth - size) rectangleY size rectangleHeight) - drawRect buf color Fill (G.GeglRectangle rectangleX (rectangleHeight - size) rectangleWidth size) + drawRect buf color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) + drawRect buf color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) -- | compute color for a single pixel colorize From 40d88c4fd2c0ed01b36731c17ac48a57a3a44bb7 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 12:11:58 +0100 Subject: [PATCH 034/261] new example It's a rectangle following the mouse --- affection.cabal | 17 ++++++ examples/example02.hs | 136 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 examples/example02.hs diff --git a/affection.cabal b/affection.cabal index 4c15e11..c5fc553 100644 --- a/affection.cabal +++ b/affection.cabal @@ -94,3 +94,20 @@ executable example01 , 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 + else + buildable: False diff --git a/examples/example02.hs b/examples/example02.hs new file mode 100644 index 0000000..f32721c --- /dev/null +++ b/examples/example02.hs @@ -0,0 +1,136 @@ +{-# LANGUAGE RecordWildCards #-} + +import Affection +import qualified SDL +import qualified SDL.Raw as Raw +import qualified GEGL as G +import qualified BABL as B +import qualified Data.Map.Strict as M + +import Foreign.Ptr (castPtr) +import Foreign.Storable (peek) +import Foreign.C.Types + +import Debug.Trace + +main :: IO () +main = do + conf <- return $ AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + , drawLoop = draw + , updateLoop = update + , loadState = load + , cleanUp = clean + } + withAffection conf + +data UserData = UserData + { nodeGraph :: M.Map String G.GeglNode + , foreground :: G.GeglBuffer + , coordinates :: Maybe (Int, Int) + } + +data Particle = Particle + { particleCoords :: (Int, Int) + , particleTTL :: Double + , particleVel :: (Int, Int) + } + +load :: SDL.Surface -> IO UserData +load surface = do + traceM "loading" + root <- G.gegl_node_new + traceM "new root node" + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation + [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 + , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 + ] + traceM "checkerboard" + let SDL.Surface rawSurfacePtr _ = surface + -- display <- G.gegl_node_new_child root $ G.Operation "dodo:sdl-surface" $ + -- [ G.Property "surface" $ G.PropertyPointer $ castPtr rawSurfacePtr ] + over <- G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation + [ G.Property "buffer" $ G.PropertyBuffer buffer + ] + traceM "buffer-source" + G.gegl_node_link_many [checkerboard, over] + G.gegl_node_connect_to bufsrc "output" over "aux" + traceM "connections made" + myMap <- return $ M.fromList + [ ("root" , root) + , ("over" , over) + -- , ("display" , display) + , ("background" , checkerboard) + , ("foreground" , bufsrc) + ] + let roi = G.GeglRectangle 0 0 20 20 + traceM "loading complete" + return $ UserData + { nodeGraph = myMap + , foreground = buffer + , coordinates = Nothing + } + +draw :: Affection UserData () +draw = do + traceM "drawing" + AffectionData{..} <- get + let UserData{..} = userState + liftIO $ SDL.lockSurface drawSurface + pixels <- liftIO $ SDL.surfacePixels drawSurface + let SDL.Surface rawSurfacePtr _ = drawSurface + rawSurface <- liftIO $ peek rawSurfacePtr + pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface + format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) + SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface + let (w, h) = (fromIntegral rw, fromIntegral rh) + maybe (return ()) (\(x, y) -> do + liftIO $ drawRect foreground (G.RGB 1 0 0) (Line 2) (G.GeglRectangle (x - 10) (y - 10) 20 20) + ) coordinates + -- liftIO $ G.gegl_node_process $ nodeGraph M.! "display" + liftIO $ G.gegl_node_blit + (nodeGraph M.! "over" :: G.GeglNode) + 1 + (G.GeglRectangle 0 0 w h) + format + pixels + (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) + [G.GeglBlitDefault] + liftIO $ SDL.unlockSurface drawSurface + liftIO $ SDL.updateWindowSurface drawWindow + liftIO $ clearArea foreground (GeglRectangle 0 0 w h) + +update :: Double -> AffectionState (AffectionData UserData) IO () +update sec = do + traceM "updating" + -- liftIO $ delaySec 5 + ad@AffectionData{..} <- get + let ud@UserData{..} = userState + traceM $ (show $ 1 / sec) ++ " FPS" + ev <- liftIO $ SDL.pollEvent + maybe (return ()) (\e -> + case SDL.eventPayload e of + SDL.MouseMotionEvent dat -> do + let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat + put $ ad + { userState = ud + { coordinates = Just (fromIntegral x, fromIntegral y) + } + } + SDL.WindowClosedEvent _ -> do + traceM "seeya!" + put $ ad + { quitEvent = True + } + _ -> + return () + ) ev + +clean :: UserData -> IO () +clean _ = return () From f034f40c2da03cd050ba0be068868ceeaf5d0767 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 12:23:46 +0100 Subject: [PATCH 035/261] show fps --- examples/example01.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index 58f528b..5ec6295 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -143,8 +143,8 @@ update sec = do -- liftIO $ delaySec 5 ad@AffectionData{..} <- get let ud@UserData{..} = userState - traceM $ show elapsedTime - if elapsedTime < 5 + traceM $ (show $ 1 / sec) ++ " FPS" + if elapsedTime < 20 then do fg <- liftIO $ G.gegl_buffer_get_extent foreground _ <- liftIO $ G.gegl_buffer_set_extent foreground $ fg From 5a0b78827c0c26322cfc2d468d483c49864d2b59 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 12:24:02 +0100 Subject: [PATCH 036/261] clear area --- src/Affection/Draw.hs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 1b1c84a..b1d5091 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -3,6 +3,8 @@ -- | Module for drawing primitives module Affection.Draw ( drawRect + -- , clear + , clearArea ) where import Affection.Types (DrawType(..), Affection, GeglRectangle(..), liftIO) @@ -77,3 +79,16 @@ unsafeColorize col = , G.CVdouble $ CDouble $ b , G.CVdouble $ CDouble $ a ) + +-- -- | Clear all data from a buffer +-- clear +-- :: G.GeglBuffer -- ^ Target buffer +-- -> IO () +-- clear buf = clearArea buf =<< G.gegl_rectangle_infinite_plane + +-- | Clear a specified area of a buffer from all data +clearArea + :: G.GeglBuffer -- ^ Target buffer + -> G.GeglRectangle -- ^ Area to clear + -> IO () +clearArea = G.gegl_buffer_clear From 2b1e6c62f1253073de4e7b784cdbc7e1d4f8181b Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 17:38:03 +0100 Subject: [PATCH 037/261] squeezing even more perfomrance out of it --- examples/example01.hs | 18 ++++++------ examples/example02.hs | 37 +++++++++++++----------- src/Affection.hs | 24 ++++++++++++++-- src/Affection/Draw.hs | 64 ++++++++++++++++++++++++++++++++++++------ src/Affection/Types.hs | 10 +++++-- 5 files changed, 113 insertions(+), 40 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index 5ec6295..084a844 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -125,15 +125,15 @@ draw = do format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) - liftIO $ drawRect foreground (G.RGB 1 0 0) (Line 2) (G.GeglRectangle 10 10 500 500) - liftIO $ G.gegl_node_blit - (nodeGraph M.! "over" :: G.GeglNode) - 1 - (G.GeglRectangle 0 0 w h) - format - pixels - (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) - [G.GeglBlitDefault] + drawRect foreground (nodeGraph M.! "over") (G.RGB 1 0 0) (Line 2) (G.GeglRectangle 10 10 500 500) + -- liftIO $ G.gegl_node_blit + -- (nodeGraph M.! "over" :: G.GeglNode) + -- 1 + -- (G.GeglRectangle 0 0 w h) + -- format + -- pixels + -- (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) + -- [G.GeglBlitDefault] liftIO $ SDL.unlockSurface drawSurface liftIO $ SDL.updateWindowSurface drawWindow diff --git a/examples/example02.hs b/examples/example02.hs index f32721c..7d22021 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -59,7 +59,7 @@ load surface = do [ G.Property "buffer" $ G.PropertyBuffer buffer ] traceM "buffer-source" - G.gegl_node_link_many [checkerboard, over] + G.gegl_node_link checkerboard over G.gegl_node_connect_to bufsrc "output" over "aux" traceM "connections made" myMap <- return $ M.fromList @@ -82,29 +82,34 @@ draw = do traceM "drawing" AffectionData{..} <- get let UserData{..} = userState - liftIO $ SDL.lockSurface drawSurface - pixels <- liftIO $ SDL.surfacePixels drawSurface + -- liftIO $ SDL.lockSurface drawSurface + -- pixels <- liftIO $ SDL.surfacePixels drawSurface let SDL.Surface rawSurfacePtr _ = drawSurface rawSurface <- liftIO $ peek rawSurfacePtr pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) - maybe (return ()) (\(x, y) -> do - liftIO $ drawRect foreground (G.RGB 1 0 0) (Line 2) (G.GeglRectangle (x - 10) (y - 10) 20 20) + liftIO $ clearArea foreground (GeglRectangle 0 0 w h) + maybe (return ()) (\(x, y) -> + drawRect + foreground + (nodeGraph M.! "over") + (G.RGB 1 0 0) + (Line 2) + (G.GeglRectangle (x - 10) (y - 10) 20 20) ) coordinates -- liftIO $ G.gegl_node_process $ nodeGraph M.! "display" - liftIO $ G.gegl_node_blit - (nodeGraph M.! "over" :: G.GeglNode) - 1 - (G.GeglRectangle 0 0 w h) - format - pixels - (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) - [G.GeglBlitDefault] - liftIO $ SDL.unlockSurface drawSurface - liftIO $ SDL.updateWindowSurface drawWindow - liftIO $ clearArea foreground (GeglRectangle 0 0 w h) + -- liftIO $ G.gegl_node_blit + -- (nodeGraph M.! "over" :: G.GeglNode) + -- 1 + -- (G.GeglRectangle 0 0 w h) + -- format + -- pixels + -- (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) + -- [G.GeglBlitDefault] + -- liftIO $ SDL.unlockSurface drawSurface + -- liftIO $ SDL.updateWindowSurface drawWindow update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do diff --git a/src/Affection.hs b/src/Affection.hs index addd755..d08e4d9 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -23,9 +23,14 @@ import Control.Monad.Loops import Control.Monad.State import Control.Concurrent.MVar +import Foreign.C.Types (CInt(..)) +import Foreign.Storable (peek) + import Affection.Types as A import Affection.Draw as A +import qualified BABL as B + -- | Main function which bootstraps everything else. withAffection :: AffectionConfig us -- ^ Configuration of the Game and its engine. @@ -40,15 +45,24 @@ withAffection AffectionConfig{..} = do execTime <- newMVar =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig oldSurf@(SDL.Surface ptr _) <- SDL.getWindowSurface window - surface <- (\x -> return $ SDL.Surface x Nothing) =<< - Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 + rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 + let surface = (flip SDL.Surface Nothing) rawSurfacePtr + bablFormat = B.PixelFormat B.RGBA B.CFu8 + pixels <- SDL.surfacePixels surface + format <- B.babl_format bablFormat + SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions surface + pixelFormat <- peek . Raw.surfaceFormat =<< peek rawSurfacePtr + let (w, h) = (fromIntegral rw, fromIntegral rh) + stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w + cpp = B.babl_components_per_pixel bablFormat initContainer <- return . (\x -> AffectionData { quitEvent = False , userState = x , drawWindow = window , drawSurface = surface + , drawStack = [] }) =<< loadState surface - (_, nState) <- runStateT ( A.runState $ + (_, nState) <- runStateT ( A.runState $ do whileM_ (do current <- get return $ not $ A.quitEvent current @@ -57,6 +71,10 @@ withAffection AffectionConfig{..} = do now <- liftIO $ getTime Monotonic lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime drawLoop + ad <- get + mapM_ (handleDrawRequest pixels format stride cpp) $ drawStack ad + put $ ad + { drawStack = [] } liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index b1d5091..6be8e4a 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -4,13 +4,20 @@ module Affection.Draw ( drawRect -- , clear + , handleDrawRequest + , present , clearArea ) where -import Affection.Types (DrawType(..), Affection, GeglRectangle(..), liftIO) +import Affection.Types +import Foreign.Ptr (Ptr, plusPtr) import Foreign.C.Types +import Control.Monad.State (get, put) + +import qualified SDL + import qualified BABL as B import qualified GEGL as G @@ -18,22 +25,61 @@ import qualified GEGL as G -- | Draw a rectangle on target buffer drawRect :: G.GeglBuffer -- ^ Target buffer + -> G.GeglNode -- ^ Target node -> G.Color -- ^ Color to draw in -> DrawType -- ^ Draw type -> GeglRectangle -- ^ Dimensions of Rectangle - -> IO () -drawRect buf color (Fill) rect@G.GeglRectangle{..} = - G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ + -> Affection us () +drawRect buf node color (Fill) rect@G.GeglRectangle{..} = do + liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ (\(x, y) -> let col = unsafeColorize color in G.Pixel x y col ) -drawRect buf color (Line size) rect@G.GeglRectangle{..} = do - drawRect buf color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) - drawRect buf color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) - drawRect buf color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) - drawRect buf color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) + ad@AffectionData{..} <- get + put $ ad + { drawStack = (DrawRequest node rect) : drawStack + } +drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do + drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) + drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) + drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) + drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) + +-- | force a blit of a specified area +present + :: G.GeglNode -- ^ Node to blit + -> G.GeglRectangle -- ^ Area to blit + -> Affection us () +present node rect = do + ad@AffectionData{..} <- get + put $ ad + { drawStack = (DrawRequest node rect) : drawStack + } + +-- | function for handling 'DrawRequest's and updating the output +handleDrawRequest + :: Ptr a -- ^ Pixel buffer to blit to + -> B.BablFormatPtr -- ^ format to blit in + -> Int -- ^ Stride + -> Int -- ^ Components per Pixel + -> DrawRequest -- ^ 'DrawRequest' to handle + -> Affection us () +handleDrawRequest pixels format stride cpp DrawRequest{..} = do + ad <- get + liftIO $ SDL.lockSurface $ drawSurface ad + liftIO $ G.gegl_node_blit + requestNode + 1 + requestArea + format + (pixels `plusPtr` + (rectangleX requestArea * cpp + rectangleY requestArea * stride)) + stride + [G.GeglBlitDefault] + liftIO $ SDL.unlockSurface $ drawSurface ad + liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | compute color for a single pixel colorize diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index a710371..b58f75c 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -12,6 +12,7 @@ module Affection.Types -- , Loop(..) , RGBA(..) , DrawType(..) + , DrawRequest(..) , SDL.WindowConfig(..) , SDL.defaultWindow -- | Convenience exports @@ -57,11 +58,14 @@ data AffectionData us = AffectionData , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window , drawSurface :: SDL.Surface -- ^ SDL surface + , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequests's to be processed } --- -- | Data and surfaces for drawing. --- data AffectionDraw dd = AffectionDraw --- } +-- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated +data DrawRequest = DrawRequest + { requestNode :: G.GeglNode -- ^ The 'G.GeglNode' to blit + , requestArea :: G.GeglRectangle -- ^ The area to update + } -- | Components to initialize in SDL. data InitComponents From fd7f2ff8ba4a0d94aafca33675c366879257dbea Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 17:44:01 +0100 Subject: [PATCH 038/261] another DrawRequest --- src/Affection/Draw.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 6be8e4a..e2a8931 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -46,6 +46,10 @@ drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) + ad@AffectionData{..} <- get + put $ ad + { drawStack = (DrawRequest node rect) : drawStack + } -- | force a blit of a specified area present From cb158f1b2b887d775921799543bbf1d8e27903eb Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 20:24:16 +0100 Subject: [PATCH 039/261] now works and cleans and everything \o/ --- examples/example02.hs | 12 +++++++--- src/Affection.hs | 9 +++++-- src/Affection/Draw.hs | 53 +++++++++++++++++++++++++++++++++++------- src/Affection/Types.hs | 15 ++++++++---- 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 7d22021..4176d35 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -19,6 +19,7 @@ main = do { initComponents = All , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow + , preLoop = drawInit , drawLoop = draw , updateLoop = update , loadState = load @@ -77,6 +78,12 @@ load surface = do , coordinates = Nothing } +drawInit :: Affection UserData () +drawInit = do + AffectionData{..} <- get + let UserData{..} = userState + present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True + draw :: Affection UserData () draw = do traceM "drawing" @@ -95,8 +102,8 @@ draw = do drawRect foreground (nodeGraph M.! "over") - (G.RGB 1 0 0) - (Line 2) + (G.RGBA 1 0 0 0.5) + (Line 7) (G.GeglRectangle (x - 10) (y - 10) 20 20) ) coordinates -- liftIO $ G.gegl_node_process $ nodeGraph M.! "display" @@ -114,7 +121,6 @@ draw = do update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do traceM "updating" - -- liftIO $ delaySec 5 ad@AffectionData{..} <- get let ud@UserData{..} = userState traceM $ (show $ 1 / sec) ++ " FPS" diff --git a/src/Affection.hs b/src/Affection.hs index d08e4d9..f776384 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -63,6 +63,7 @@ withAffection AffectionConfig{..} = do , drawStack = [] }) =<< loadState surface (_, nState) <- runStateT ( A.runState $ do + preLoop whileM_ (do current <- get return $ not $ A.quitEvent current @@ -70,11 +71,15 @@ withAffection AffectionConfig{..} = do (do now <- liftIO $ getTime Monotonic lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime - drawLoop ad <- get - mapM_ (handleDrawRequest pixels format stride cpp) $ drawStack ad + mapM_ (invalidateDrawRequest pixels format stride cpp) $ drawStack ad put $ ad { drawStack = [] } + drawLoop + ad <- get + clear <- return . catMaybes =<< mapM (handleDrawRequest pixels format stride cpp) (drawStack ad) + put $ ad + { drawStack = clear } liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index e2a8931..63abfa6 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -5,6 +5,7 @@ module Affection.Draw ( drawRect -- , clear , handleDrawRequest + , invalidateDrawRequest , present , clearArea ) where @@ -15,6 +16,7 @@ import Foreign.Ptr (Ptr, plusPtr) import Foreign.C.Types import Control.Monad.State (get, put) +import Control.Monad (when) import qualified SDL @@ -22,6 +24,8 @@ import qualified BABL as B import qualified GEGL as G +import Debug.Trace + -- | Draw a rectangle on target buffer drawRect :: G.GeglBuffer -- ^ Target buffer @@ -39,27 +43,29 @@ drawRect buf node color (Fill) rect@G.GeglRectangle{..} = do ) ad@AffectionData{..} <- get put $ ad - { drawStack = (DrawRequest node rect) : drawStack + { drawStack = (DrawRequest node rect buf False) : drawStack } drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) - ad@AffectionData{..} <- get - put $ ad - { drawStack = (DrawRequest node rect) : drawStack - } + -- ad@AffectionData{..} <- get + -- put $ ad + -- { drawStack = (DrawRequest node rect) : drawStack + -- } -- | force a blit of a specified area present :: G.GeglNode -- ^ Node to blit + -> G.GeglBuffer -- ^ BUffer to draw -> G.GeglRectangle -- ^ Area to blit + -> Bool -- ^ Shall the drawing be cleared in the next run? -> Affection us () -present node rect = do +present node buf rect pers = do ad@AffectionData{..} <- get put $ ad - { drawStack = (DrawRequest node rect) : drawStack + { drawStack = (DrawRequest node rect buf pers) : drawStack } -- | function for handling 'DrawRequest's and updating the output @@ -69,8 +75,8 @@ handleDrawRequest -> Int -- ^ Stride -> Int -- ^ Components per Pixel -> DrawRequest -- ^ 'DrawRequest' to handle - -> Affection us () -handleDrawRequest pixels format stride cpp DrawRequest{..} = do + -> Affection us (Maybe DrawRequest) +handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do ad <- get liftIO $ SDL.lockSurface $ drawSurface ad liftIO $ G.gegl_node_blit @@ -84,6 +90,35 @@ handleDrawRequest pixels format stride cpp DrawRequest{..} = do [G.GeglBlitDefault] liftIO $ SDL.unlockSurface $ drawSurface ad liftIO $ SDL.updateWindowSurface $ drawWindow ad + if requestPersist + then + return Nothing + else + return $ Just dr + +-- | clear a previously drawn area +invalidateDrawRequest + :: Ptr a -- ^ Pixel buffer to blit to + -> B.BablFormatPtr -- ^ format to blit in + -> Int -- ^ Stride + -> Int -- ^ Components per Pixel + -> DrawRequest -- ^ Drawrequest to invalidate + -> Affection us () +invalidateDrawRequest pixels format stride cpp dr@DrawRequest{..} = do + ad <- get + liftIO $ clearArea requestBuffer requestArea + liftIO $ SDL.lockSurface $ drawSurface ad + liftIO $ G.gegl_node_blit + requestNode + 1 + requestArea + format + (pixels `plusPtr` + (rectangleX requestArea * cpp + rectangleY requestArea * stride)) + stride + [G.GeglBlitDefault] + liftIO $ SDL.unlockSurface $ drawSurface ad + liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | compute color for a single pixel colorize diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index b58f75c..28c34c9 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -41,9 +41,11 @@ data AffectionConfig us = AffectionConfig -- ^ Window title , windowConfig :: SDL.WindowConfig -- ^ Window configuration - , drawLoop :: AffectionState (AffectionData us) IO () + , preLoop :: Affection us () + -- ^ Actions to be performed, before loop starts + , drawLoop :: Affection us () -- ^ Function for updating graphics. - , updateLoop :: Double -> AffectionState (AffectionData us) IO () + , updateLoop :: Double -> Affection us () -- ^ Main update function. Takes fractions of a second as input. , loadState :: SDL.Surface -> IO us -- ^ Provide your own load function to create this data. @@ -58,13 +60,16 @@ data AffectionData us = AffectionData , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window , drawSurface :: SDL.Surface -- ^ SDL surface - , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequests's to be processed + , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed + , clearStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be invalidated } -- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated data DrawRequest = DrawRequest - { requestNode :: G.GeglNode -- ^ The 'G.GeglNode' to blit - , requestArea :: G.GeglRectangle -- ^ The area to update + { requestNode :: G.GeglNode -- ^ The 'G.GeglNode' to blit + , requestArea :: G.GeglRectangle -- ^ The area to update + , requestBuffer :: G.GeglBuffer -- ^ Buffer to draw + , requestPersist :: Bool -- ^ Shall the drawRequest persist } -- | Components to initialize in SDL. From 8820a6095fbfaba72e5b2930025ed54323d29c16 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 20:35:18 +0100 Subject: [PATCH 040/261] cleaning --- examples/example02.hs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 4176d35..b3f18ec 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -7,7 +7,6 @@ import qualified GEGL as G import qualified BABL as B import qualified Data.Map.Strict as M -import Foreign.Ptr (castPtr) import Foreign.Storable (peek) import Foreign.C.Types @@ -40,7 +39,7 @@ data Particle = Particle } load :: SDL.Surface -> IO UserData -load surface = do +load _ = do traceM "loading" root <- G.gegl_node_new traceM "new root node" @@ -49,9 +48,6 @@ load surface = do , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 ] traceM "checkerboard" - let SDL.Surface rawSurfacePtr _ = surface - -- display <- G.gegl_node_new_child root $ G.Operation "dodo:sdl-surface" $ - -- [ G.Property "surface" $ G.PropertyPointer $ castPtr rawSurfacePtr ] over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< @@ -66,11 +62,9 @@ load surface = do myMap <- return $ M.fromList [ ("root" , root) , ("over" , over) - -- , ("display" , display) , ("background" , checkerboard) , ("foreground" , bufsrc) ] - let roi = G.GeglRectangle 0 0 20 20 traceM "loading complete" return $ UserData { nodeGraph = myMap @@ -89,12 +83,7 @@ draw = do traceM "drawing" AffectionData{..} <- get let UserData{..} = userState - -- liftIO $ SDL.lockSurface drawSurface - -- pixels <- liftIO $ SDL.surfacePixels drawSurface let SDL.Surface rawSurfacePtr _ = drawSurface - rawSurface <- liftIO $ peek rawSurfacePtr - pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface - format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) liftIO $ clearArea foreground (GeglRectangle 0 0 w h) @@ -106,17 +95,6 @@ draw = do (Line 7) (G.GeglRectangle (x - 10) (y - 10) 20 20) ) coordinates - -- liftIO $ G.gegl_node_process $ nodeGraph M.! "display" - -- liftIO $ G.gegl_node_blit - -- (nodeGraph M.! "over" :: G.GeglNode) - -- 1 - -- (G.GeglRectangle 0 0 w h) - -- format - -- pixels - -- (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) - -- [G.GeglBlitDefault] - -- liftIO $ SDL.unlockSurface drawSurface - -- liftIO $ SDL.updateWindowSurface drawWindow update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do From 663ffca664169ccb5bc3e01cef9b7d751459f6e3 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 11 Dec 2016 22:08:33 +0100 Subject: [PATCH 041/261] some little cleaning --- examples/example02.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/example02.hs b/examples/example02.hs index b3f18ec..47c0b1a 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -83,7 +83,6 @@ draw = do traceM "drawing" AffectionData{..} <- get let UserData{..} = userState - let SDL.Surface rawSurfacePtr _ = drawSurface SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface let (w, h) = (fromIntegral rw, fromIntegral rh) liftIO $ clearArea foreground (GeglRectangle 0 0 w h) From b0d44124dcbade3c2ffa6c7facdd271b4f94a3a5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 12 Dec 2016 02:10:30 +0100 Subject: [PATCH 042/261] updating examples --- examples/example00.hs | 1 + examples/example01.hs | 1 + src/Affection.hs | 35 ++++++++++++++++------------------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index a240d6d..8046990 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -26,6 +26,7 @@ main = do { initComponents = All , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow + , preLoop = return () , drawLoop = draw , updateLoop = update , loadState = load diff --git a/examples/example01.hs b/examples/example01.hs index 084a844..2331e6f 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -26,6 +26,7 @@ main = do { initComponents = All , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow + , preLoop = return () , drawLoop = draw , updateLoop = update , loadState = load diff --git a/src/Affection.hs b/src/Affection.hs index f776384..8352afa 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -1,6 +1,8 @@ {-# LANGUAGE RecordWildCards #-} module Affection ( withAffection + , getAffection + , putAffection -- , withWindow -- , withDefaultWindow , delaySec @@ -91,25 +93,20 @@ withAffection AffectionConfig{..} = do cleanUp $ userState nState SDL.quit --- -- | DEPRECATED! --- -- Function for bootstraping a window. --- withWindow :: Monad m => T.Text -> WindowConfig -> SDL.RendererConfig -> RenderT m a -> IO () --- withWindow title wconf rconf ops = do --- window <- SDL.createWindow title wconf --- -- I don't need a renderer here, i need a surface --- renderer <- SDL.createRenderer window (-1) rconf --- surface <- SDL.getWindowSurface window --- G.gegl_init --- -- I think I need some AffectionT or someting similar here and not a RenderT --- -- from SDL. --- inRender renderer $ ops --- G.gegl_exit --- SDL.destroyWindow window --- --- -- | DEPRECATED! --- -- Bootstrap a default window. --- withDefaultWindow :: Monad m => T.Text -> (RenderT m a) -> IO () --- withDefaultWindow title ops = withWindow title defaultWindow SDL.defaultRenderer ops +-- | Return the userstate to the user +getAffection :: Affection us us +getAffection = do + ad <- get + return $ userState ad + +-- | 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 From 05b126ef9443e248439732e9c9d3447268109645 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 12 Dec 2016 02:10:42 +0100 Subject: [PATCH 043/261] cleaning Particles will be implemented in affection itself --- examples/example02.hs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 47c0b1a..5d66681 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -32,12 +32,6 @@ data UserData = UserData , coordinates :: Maybe (Int, Int) } -data Particle = Particle - { particleCoords :: (Int, Int) - , particleTTL :: Double - , particleVel :: (Int, Int) - } - load :: SDL.Surface -> IO UserData load _ = do traceM "loading" From 4eaa644cbf5d0eb68fa17cb4597cfdbc4e681b6e Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 12 Dec 2016 02:11:31 +0100 Subject: [PATCH 044/261] new accessors --- examples/example02.hs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 5d66681..71cf8b4 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -68,16 +68,15 @@ load _ = do drawInit :: Affection UserData () drawInit = do - AffectionData{..} <- get - let UserData{..} = userState + UserData{..} <- getAffection present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True draw :: Affection UserData () draw = do traceM "drawing" - AffectionData{..} <- get - let UserData{..} = userState - SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface + ad <- get + UserData{..} <- getAffection + SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions $ drawSurface ad let (w, h) = (fromIntegral rw, fromIntegral rh) liftIO $ clearArea foreground (GeglRectangle 0 0 w h) maybe (return ()) (\(x, y) -> @@ -92,18 +91,16 @@ draw = do update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do traceM "updating" - ad@AffectionData{..} <- get - let ud@UserData{..} = userState + ad <- get + ud <- getAffection traceM $ (show $ 1 / sec) ++ " FPS" ev <- liftIO $ SDL.pollEvent maybe (return ()) (\e -> case SDL.eventPayload e of SDL.MouseMotionEvent dat -> do let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat - put $ ad - { userState = ud - { coordinates = Just (fromIntegral x, fromIntegral y) - } + putAffection $ ud + { coordinates = Just (fromIntegral x, fromIntegral y) } SDL.WindowClosedEvent _ -> do traceM "seeya!" From 8a8a5ed066c0128e406289646b883ae294cd7272 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 12 Dec 2016 02:12:45 +0100 Subject: [PATCH 045/261] removing ubsolete imports --- examples/example02.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 71cf8b4..72cc6eb 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -2,12 +2,10 @@ import Affection import qualified SDL -import qualified SDL.Raw as Raw import qualified GEGL as G import qualified BABL as B import qualified Data.Map.Strict as M -import Foreign.Storable (peek) import Foreign.C.Types import Debug.Trace From 1f946624efd422ad4f00a2574b324820a60009f4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 12 Dec 2016 03:34:57 +0100 Subject: [PATCH 046/261] introducing particles first little drafts --- affection.cabal | 1 + src/Affection.hs | 1 + src/Affection/Particle.hs | 32 ++++++++++++++++++++++++++++++++ src/Affection/Types.hs | 14 ++++++++++++-- 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/Affection/Particle.hs diff --git a/affection.cabal b/affection.cabal index c5fc553..a57e18b 100644 --- a/affection.cabal +++ b/affection.cabal @@ -34,6 +34,7 @@ flag examples library exposed-modules: Affection , Affection.Draw + , Affection.Particle , Affection.Types default-extensions: OverloadedStrings diff --git a/src/Affection.hs b/src/Affection.hs index 8352afa..cddccae 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -30,6 +30,7 @@ import Foreign.Storable (peek) import Affection.Types as A import Affection.Draw as A +import Afection.Particle as A import qualified BABL as B diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs new file mode 100644 index 0000000..1acea04 --- /dev/null +++ b/src/Affection/Particle.hs @@ -0,0 +1,32 @@ +-- | This module introduces a simple particle system to Affection +module Affection.Particle + ( updateParticles + , drawParticles + ) where + +import Affection.Types + +-- This function updates particles through a specified function. Particle ageing +-- and death is being handled by 'updateParticles' itself and does not need to +-- bother you. +updateParticles + :: Double -- ^ Elapsed time in seconds + -> (Double -> Particle -> Particle) -- ^ Update function for a single 'Particle' + -- This Function should take the elapsed time + -- in seconds and the initial particle as arguments. + -> [Particle] -- ^ List of 'Particle's to be processed + -> [Particle] -- ^ resulting list of particles +updateParticles _ _ [] = [] +updateParticles time funct (p:ps) = + if particleLife p - time < 0 + then + updateParticles time funct ps + else + (funct time $ p { particleLife = particleLife p - time }) : + updateparticles time funct ps + +drawParticles + :: (Particle -> Affection us ()) + -> [Particle] + -> Affection us () +drawParticles = mapM_ diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 28c34c9..20bf577 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -13,10 +13,12 @@ module Affection.Types , RGBA(..) , DrawType(..) , DrawRequest(..) - , SDL.WindowConfig(..) - , SDL.defaultWindow + -- | Particle system + , Particle(..) -- | Convenience exports , liftIO + , SDL.WindowConfig(..) + , SDL.defaultWindow -- | GEGL reexports , G.GeglRectangle(..) , G.GeglBuffer(..) @@ -122,3 +124,11 @@ data DrawType | Line -- ^ only draw the outline of the area { lineWidth :: Int -- ^ Width of line in pixels } + +-- | A single particle +data Particle = Particle + { particleLife :: Double -- ^ Time to live in seconds + , particlePosition :: (Int, Int) -- ^ Position of particle on canvas + , particleRotation :: Double -- ^ Particle rotation + , particleVelocity :: (Int, Int) -- ^ particle velocity as vector of pixels per second + } deriving (Show, Eq) From 9d1b26d633ffc22b58360d08f5933475b8eb1471 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 13 Dec 2016 11:08:49 +0100 Subject: [PATCH 047/261] new: particle system and an example for that --- affection.cabal | 18 +++++ examples/example03.hs | 155 ++++++++++++++++++++++++++++++++++++++ src/Affection.hs | 6 +- src/Affection/Draw.hs | 16 ++-- src/Affection/Particle.hs | 37 ++++++--- src/Affection/Types.hs | 15 +++- 6 files changed, 223 insertions(+), 24 deletions(-) create mode 100644 examples/example03.hs diff --git a/affection.cabal b/affection.cabal index a57e18b..da1f56e 100644 --- a/affection.cabal +++ b/affection.cabal @@ -112,3 +112,21 @@ executable example02 , mtl else buildable: False + +executable example03 + hs-source-dirs: examples + main-is: example03.hs + ghc-options: -threaded -Wall + default-language: Haskell2010 + default-extensions: OverloadedStrings + if flag(examples) + build-depends: base + , affection + , sdl2 + , gegl + , babl + , containers + , mtl + , random + else + buildable: False diff --git a/examples/example03.hs b/examples/example03.hs new file mode 100644 index 0000000..7236074 --- /dev/null +++ b/examples/example03.hs @@ -0,0 +1,155 @@ +{-# LANGUAGE RecordWildCards #-} + +import Affection +import qualified SDL +import qualified GEGL as G +import qualified BABL as B +import qualified Data.Map.Strict as M + +import Foreign.C.Types + +import System.Random (randomRIO) + +import Debug.Trace + +main :: IO () +main = do + conf <- return $ AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + , preLoop = drawInit + , drawLoop = draw + , updateLoop = update + , loadState = load + , cleanUp = clean + } + withAffection conf + +data UserData = UserData + { nodeGraph :: M.Map String G.GeglNode + , foreground :: G.GeglBuffer + , coordinates :: Maybe (Int, Int) + , partsys :: ParticleSystem + } + +load :: SDL.Surface -> IO UserData +load _ = do + traceM "loading" + root <- G.gegl_node_new + traceM "new root node" + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation + [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 + , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 + ] + traceM "checkerboard" + over <- G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation + [ G.Property "buffer" $ G.PropertyBuffer buffer + ] + traceM "buffer-source" + G.gegl_node_link checkerboard over + G.gegl_node_connect_to bufsrc "output" over "aux" + traceM "connections made" + myMap <- return $ M.fromList + [ ("root" , root) + , ("over" , over) + , ("background" , checkerboard) + , ("foreground" , bufsrc) + ] + traceM "loading complete" + return $ UserData + { nodeGraph = myMap + , foreground = buffer + , coordinates = Nothing + , partsys = ParticleSystem [] over buffer + } + +drawInit :: Affection UserData () +drawInit = do + UserData{..} <- getAffection + present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True + +draw :: Affection UserData () +draw = do + traceM "drawing" + -- ad <- get + -- ud <- getAffection + -- drawParticles partDraw $ particles ud + -- SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions $ drawSurface ad + -- let (w, h) = (fromIntegral rw, fromIntegral rh) + -- liftIO $ clearArea (foreground ud) (GeglRectangle 0 0 w h) + -- maybe (return ()) (\(x, y) -> + -- drawRect + -- (foreground ud) + -- (nodeGraph ud M.! "over") + -- (G.RGBA 1 0 0 0.5) + -- (Line 7) + -- (G.GeglRectangle (x - 10) (y - 10) 20 20) + -- ) $ coordinates ud + +update :: Double -> AffectionState (AffectionData UserData) IO () +update sec = do + traceM "updating" + ad <- get + ud <- getAffection + -- let newPart = updateParticles sec partUpd $ particles ud + -- putAffection $ ud { particles = newPart } + nps <- updateParticleSystem (partsys ud) sec partUpd partDraw + putAffection $ ud { partsys = nps } + traceM $ (show $ 1 / sec) ++ " FPS" + ev <- liftIO $ SDL.pollEvent + maybe (return ()) (\e -> + case SDL.eventPayload e of + SDL.MouseButtonEvent dat -> if sec < 0.03 + then do + let (SDL.P (SDL.V2 x y)) = SDL.mouseButtonEventPos dat + vx <- liftIO $ randomRIO (-20, 20) + vy <- liftIO $ randomRIO (-20, 20) + traceM $ "velocity is: " ++ show vx ++ " " ++ show vy + putAffection $ ud + { coordinates = Just (fromIntegral x, fromIntegral y) + , partsys = (partsys ud) + { psParts = (Particle + { particleLife = 5 + , particlePosition = (fromIntegral x, fromIntegral y) + , particleRotation = 0 + , particleVelocity = (vx, vy) + }) : (psParts $ partsys ud) + } + } + else return () + SDL.WindowClosedEvent _ -> do + traceM "seeya!" + put $ ad + { quitEvent = True + } + _ -> + return () + ) ev + +clean :: UserData -> IO () +clean _ = return () + +partUpd :: Double -> Particle -> Particle +partUpd sec p@Particle{..} = + p + { particlePosition = (newX, newY) + , particleLife = particleLife - sec + } + where + newX = (fst particlePosition) + sec * (fromIntegral $ fst particleVelocity) + newY = (snd particlePosition) + sec * (fromIntegral $ snd particleVelocity) + +partDraw :: G.GeglBuffer -> G.GeglNode -> Particle -> Affection UserData () +partDraw buf node Particle{..} = do + ud <- getAffection + drawRect + buf + node + (G.RGBA 1 0 0 0.5) + (Line 5) + (G.GeglRectangle ((floor $ fst particlePosition) - 10) ((floor $ snd particlePosition) -10) 20 20) diff --git a/src/Affection.hs b/src/Affection.hs index cddccae..4cfc9cb 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -30,7 +30,7 @@ import Foreign.Storable (peek) import Affection.Types as A import Affection.Draw as A -import Afection.Particle as A +import Affection.Particle as A import qualified BABL as B @@ -79,13 +79,13 @@ withAffection AffectionConfig{..} = do put $ ad { drawStack = [] } drawLoop + updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / + (fromIntegral 10 ^ 9) ad <- get clear <- return . catMaybes =<< mapM (handleDrawRequest pixels format stride cpp) (drawStack ad) put $ ad { drawStack = clear } liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing - updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / - (fromIntegral 10 ^ 9) _ <- liftIO $ swapMVar execTime $ now return () ) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 63abfa6..975ee30 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE RecordWildCards, BangPatterns #-} -- | Module for drawing primitives module Affection.Draw @@ -78,7 +78,8 @@ handleDrawRequest -> Affection us (Maybe DrawRequest) handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do ad <- get - liftIO $ SDL.lockSurface $ drawSurface ad + let !surf = drawSurface ad + liftIO $ SDL.lockSurface surf liftIO $ G.gegl_node_blit requestNode 1 @@ -86,9 +87,9 @@ handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do format (pixels `plusPtr` (rectangleX requestArea * cpp + rectangleY requestArea * stride)) - stride + 0 [G.GeglBlitDefault] - liftIO $ SDL.unlockSurface $ drawSurface ad + liftIO $ SDL.unlockSurface surf liftIO $ SDL.updateWindowSurface $ drawWindow ad if requestPersist then @@ -106,8 +107,9 @@ invalidateDrawRequest -> Affection us () invalidateDrawRequest pixels format stride cpp dr@DrawRequest{..} = do ad <- get + let !surf = drawSurface ad liftIO $ clearArea requestBuffer requestArea - liftIO $ SDL.lockSurface $ drawSurface ad + liftIO $ SDL.lockSurface surf liftIO $ G.gegl_node_blit requestNode 1 @@ -115,9 +117,9 @@ invalidateDrawRequest pixels format stride cpp dr@DrawRequest{..} = do format (pixels `plusPtr` (rectangleX requestArea * cpp + rectangleY requestArea * stride)) - stride + 0 [G.GeglBlitDefault] - liftIO $ SDL.unlockSurface $ drawSurface ad + liftIO $ SDL.unlockSurface surf liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | compute color for a single pixel diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 1acea04..1d4e900 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -1,32 +1,49 @@ +{-# LANGUAGE RecordWildCards #-} + -- | This module introduces a simple particle system to Affection module Affection.Particle - ( updateParticles + ( updateParticle , drawParticles + , updateParticleSystem ) where import Affection.Types +import Data.Maybe (catMaybes) + +import qualified GEGL as G + -- This function updates particles through a specified function. Particle ageing -- and death is being handled by 'updateParticles' itself and does not need to -- bother you. -updateParticles +updateParticle :: Double -- ^ Elapsed time in seconds -> (Double -> Particle -> Particle) -- ^ Update function for a single 'Particle' -- This Function should take the elapsed time -- in seconds and the initial particle as arguments. - -> [Particle] -- ^ List of 'Particle's to be processed - -> [Particle] -- ^ resulting list of particles -updateParticles _ _ [] = [] -updateParticles time funct (p:ps) = - if particleLife p - time < 0 + -> Particle -- ^ 'Particle' to be processed + -> Maybe Particle -- ^ resulting 'Particle' +updateParticle time funct p@Particle{..} = + if particleLife - time < 0 then - updateParticles time funct ps + Nothing else - (funct time $ p { particleLife = particleLife p - time }) : - updateparticles time funct ps + Just $ funct time $ p { particleLife = particleLife - time } drawParticles :: (Particle -> Affection us ()) -> [Particle] -> Affection us () drawParticles = mapM_ + +updateParticleSystem + :: ParticleSystem + -> Double + -> (Double -> Particle -> Particle) + -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) + -> Affection us ParticleSystem +updateParticleSystem sys sec upd draw = do + let x = catMaybes $ map (updateParticle sec upd) (psParts sys) + mapM_ (draw (psBuffer sys) (psNode sys)) x + return $ sys + { psParts = x } diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 20bf577..93cd960 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -15,6 +15,7 @@ module Affection.Types , DrawRequest(..) -- | Particle system , Particle(..) + , ParticleSystem(..) -- | Convenience exports , liftIO , SDL.WindowConfig(..) @@ -127,8 +128,14 @@ data DrawType -- | A single particle data Particle = Particle - { particleLife :: Double -- ^ Time to live in seconds - , particlePosition :: (Int, Int) -- ^ Position of particle on canvas - , particleRotation :: Double -- ^ Particle rotation - , particleVelocity :: (Int, Int) -- ^ particle velocity as vector of pixels per second + { particleLife :: Double -- ^ Time to live in seconds + , particlePosition :: (Double, Double) -- ^ Position of particle on canvas + , particleRotation :: Double -- ^ Particle rotation + , particleVelocity :: (Int, Int) -- ^ particle velocity as vector of pixels per second } deriving (Show, Eq) + +data ParticleSystem = ParticleSystem + { psParts :: [Particle] + , psNode :: G.GeglNode + , psBuffer :: G.GeglBuffer + } From 0bbf86e7f8c11f0f0a727729dc0d6f7c0cbd69f4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 13 Dec 2016 22:18:09 +0100 Subject: [PATCH 048/261] more toying --- src/Affection/Draw.hs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 975ee30..1d3de88 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -12,7 +12,7 @@ module Affection.Draw import Affection.Types -import Foreign.Ptr (Ptr, plusPtr) +import Foreign.Ptr (Ptr, plusPtr, nullPtr) import Foreign.C.Types import Control.Monad.State (get, put) @@ -55,7 +55,7 @@ drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do -- { drawStack = (DrawRequest node rect) : drawStack -- } --- | force a blit of a specified area +-- | force a blit of a specified area. Do not use often as it slows down the program present :: G.GeglNode -- ^ Node to blit -> G.GeglBuffer -- ^ BUffer to draw @@ -67,6 +67,19 @@ present node buf rect pers = do put $ ad { drawStack = (DrawRequest node rect buf pers) : drawStack } + ad <- get + format <- liftIO $ B.babl_format $ B.PixelFormat B.RGBA B.CFu8 + liftIO $ SDL.lockSurface $ drawSurface ad + liftIO $ G.gegl_node_blit + node + 1 + rect + format + nullPtr + 0 + [G.GeglBlitDefault] + liftIO $ SDL.unlockSurface $ drawSurface ad + liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | function for handling 'DrawRequest's and updating the output handleDrawRequest @@ -87,7 +100,7 @@ handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do format (pixels `plusPtr` (rectangleX requestArea * cpp + rectangleY requestArea * stride)) - 0 + stride [G.GeglBlitDefault] liftIO $ SDL.unlockSurface surf liftIO $ SDL.updateWindowSurface $ drawWindow ad @@ -117,7 +130,7 @@ invalidateDrawRequest pixels format stride cpp dr@DrawRequest{..} = do format (pixels `plusPtr` (rectangleX requestArea * cpp + rectangleY requestArea * stride)) - 0 + stride [G.GeglBlitDefault] liftIO $ SDL.unlockSurface surf liftIO $ SDL.updateWindowSurface $ drawWindow ad From 421935509071c37e4364751285029a0d343b03c0 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 16 Dec 2016 16:43:25 +0100 Subject: [PATCH 049/261] more profiling --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index da1f56e..cf25000 100644 --- a/affection.cabal +++ b/affection.cabal @@ -116,7 +116,7 @@ executable example02 executable example03 hs-source-dirs: examples main-is: example03.hs - ghc-options: -threaded -Wall + ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts default-language: Haskell2010 default-extensions: OverloadedStrings if flag(examples) From 9d50f8a7e0e331c995b3b36ff34750abe8941353 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 18 Dec 2016 19:01:18 +0100 Subject: [PATCH 050/261] reducing blits per rectangle --- src/Affection/Draw.hs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 1d3de88..59cf3fe 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -46,14 +46,24 @@ drawRect buf node color (Fill) rect@G.GeglRectangle{..} = do { drawStack = (DrawRequest node rect buf False) : drawStack } drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do - drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) - drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) - drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) - drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) - -- ad@AffectionData{..} <- get - -- put $ ad - -- { drawStack = (DrawRequest node rect) : drawStack - -- } + liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ + (\(x, y) -> + let col = unsafeColorize color + in if not ((x >= rectangleX + size && x < rectangleX + rectangleWidth - size) && + (y >= rectangleY + size && y < rectangleY + rectangleHeight - size)) + then + G.Pixel x y col + else + G.Pixel x y $ unsafeColorize $ G.RGBA 0 0 0 0 + ) + ad@AffectionData{..} <- get + put $ ad + { drawStack = (DrawRequest node rect buf False) : drawStack + } + -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) + -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) + -- drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) + -- drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) -- | force a blit of a specified area. Do not use often as it slows down the program present @@ -63,9 +73,9 @@ present -> Bool -- ^ Shall the drawing be cleared in the next run? -> Affection us () present node buf rect pers = do - ad@AffectionData{..} <- get + ad <- get put $ ad - { drawStack = (DrawRequest node rect buf pers) : drawStack + { drawStack = (DrawRequest node rect buf pers) : drawStack ad } ad <- get format <- liftIO $ B.babl_format $ B.PixelFormat B.RGBA B.CFu8 From 5a44486477b81a2c1dbe1aeed8236b1c6a480b63 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 18 Dec 2016 19:01:53 +0100 Subject: [PATCH 051/261] adjusted example --- examples/example03.hs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index 7236074..0142593 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -101,27 +101,26 @@ update sec = do nps <- updateParticleSystem (partsys ud) sec partUpd partDraw putAffection $ ud { partsys = nps } traceM $ (show $ 1 / sec) ++ " FPS" - ev <- liftIO $ SDL.pollEvent - maybe (return ()) (\e -> + ev <- liftIO $ SDL.pollEvents + mapM_ (\e -> case SDL.eventPayload e of - SDL.MouseButtonEvent dat -> if sec < 0.03 - then do + SDL.MouseButtonEvent dat -> do let (SDL.P (SDL.V2 x y)) = SDL.mouseButtonEventPos dat vx <- liftIO $ randomRIO (-20, 20) vy <- liftIO $ randomRIO (-20, 20) + life <- liftIO $ randomRIO (1, 5) traceM $ "velocity is: " ++ show vx ++ " " ++ show vy putAffection $ ud { coordinates = Just (fromIntegral x, fromIntegral y) , partsys = (partsys ud) { psParts = (Particle - { particleLife = 5 + { particleLife = life , particlePosition = (fromIntegral x, fromIntegral y) , particleRotation = 0 , particleVelocity = (vx, vy) }) : (psParts $ partsys ud) } } - else return () SDL.WindowClosedEvent _ -> do traceM "seeya!" put $ ad From 5f4d049a2b07d7cf009f023c64d253b0b0ee09f9 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 04:14:57 +0100 Subject: [PATCH 052/261] some explenations --- src/Affection.hs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 4cfc9cb..9ff09b9 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -67,25 +67,35 @@ withAffection AffectionConfig{..} = do }) =<< loadState surface (_, nState) <- runStateT ( A.runState $ do preLoop + liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing whileM_ (do current <- get return $ not $ A.quitEvent current ) (do + -- Measure time difference form last run now <- liftIO $ getTime Monotonic lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime + -- get state ad <- get + -- clean draw requests from last run mapM_ (invalidateDrawRequest pixels format stride cpp) $ drawStack ad put $ ad { drawStack = [] } + -- execute user defined draw loop drawLoop + -- execute user defined update loop updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) - ad <- get - clear <- return . catMaybes =<< mapM (handleDrawRequest pixels format stride cpp) (drawStack ad) + -- handle all new draw requests + clear <- catMaybes <$> mapM (handleDrawRequest pixels format stride cpp) (drawStack ad) + -- save all draw requests to clear in next run put $ ad { drawStack = clear } + -- blit surface and update window liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing + liftIO $ SDL.updateWindowSurface $ drawWindow ad + -- save new time _ <- liftIO $ swapMVar execTime $ now return () ) From ab976b4af69e8bbac992bcba76a5368e0863051f Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 04:15:30 +0100 Subject: [PATCH 053/261] this was the bad performance bottleneck --- src/Affection/Draw.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 59cf3fe..6237c83 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -101,7 +101,7 @@ handleDrawRequest -> Affection us (Maybe DrawRequest) handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do ad <- get - let !surf = drawSurface ad + let surf = drawSurface ad liftIO $ SDL.lockSurface surf liftIO $ G.gegl_node_blit requestNode @@ -113,7 +113,7 @@ handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do stride [G.GeglBlitDefault] liftIO $ SDL.unlockSurface surf - liftIO $ SDL.updateWindowSurface $ drawWindow ad + -- liftIO $ SDL.updateWindowSurface $ drawWindow ad if requestPersist then return Nothing @@ -143,7 +143,7 @@ invalidateDrawRequest pixels format stride cpp dr@DrawRequest{..} = do stride [G.GeglBlitDefault] liftIO $ SDL.unlockSurface surf - liftIO $ SDL.updateWindowSurface $ drawWindow ad + -- liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | compute color for a single pixel colorize From 954c70b31b3dd21361ac0f4b47a6eb2395cbca27 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 04:15:48 +0100 Subject: [PATCH 054/261] added pitch rate for particles --- src/Affection/Types.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 93cd960..c03c124 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -128,10 +128,11 @@ data DrawType -- | A single particle data Particle = Particle - { particleLife :: Double -- ^ Time to live in seconds - , particlePosition :: (Double, Double) -- ^ Position of particle on canvas - , particleRotation :: Double -- ^ Particle rotation - , particleVelocity :: (Int, Int) -- ^ particle velocity as vector of pixels per second + { particleLife :: Double -- ^ Time to live in seconds + , particlePosition :: (Double, Double) -- ^ Position of particle on canvas + , particleRotation :: Double -- ^ Particle rotation + , particleVelocity :: (Int, Int) -- ^ particle velocity as vector of pixels per second + , particlePitchRate :: Double -- ^ Rotational velocity of particle } deriving (Show, Eq) data ParticleSystem = ParticleSystem From fc67e752ede4d7c600e2a7bac5f6235933e9c214 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 04:16:31 +0100 Subject: [PATCH 055/261] updated example. works fine --- examples/example03.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/example03.hs b/examples/example03.hs index 0142593..46f37b0 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -109,7 +109,7 @@ update sec = do vx <- liftIO $ randomRIO (-20, 20) vy <- liftIO $ randomRIO (-20, 20) life <- liftIO $ randomRIO (1, 5) - traceM $ "velocity is: " ++ show vx ++ " " ++ show vy + -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy putAffection $ ud { coordinates = Just (fromIntegral x, fromIntegral y) , partsys = (partsys ud) @@ -118,6 +118,7 @@ update sec = do , particlePosition = (fromIntegral x, fromIntegral y) , particleRotation = 0 , particleVelocity = (vx, vy) + , particlePitchRate = 0 }) : (psParts $ partsys ud) } } From d1c697c204cd607f9f96008dbcb8d4383134c23e Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 05:27:35 +0100 Subject: [PATCH 056/261] fixing my stupidity --- src/Affection.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 9ff09b9..f0c0d93 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -88,13 +88,14 @@ withAffection AffectionConfig{..} = do updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) -- handle all new draw requests - clear <- catMaybes <$> mapM (handleDrawRequest pixels format stride cpp) (drawStack ad) + ad2 <- get + clear <- catMaybes <$> mapM (handleDrawRequest pixels format stride cpp) (drawStack ad2) -- save all draw requests to clear in next run - put $ ad + put $ ad2 { drawStack = clear } -- blit surface and update window liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing - liftIO $ SDL.updateWindowSurface $ drawWindow ad + liftIO $ SDL.updateWindowSurface $ drawWindow ad2 -- save new time _ <- liftIO $ swapMVar execTime $ now return () From 55ae0bd32acae9b0f03df8f8a2cba128ae33082c Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 05:27:45 +0100 Subject: [PATCH 057/261] changing particle draw condition Now particles are drawn when moving the mouse whilst pressing the left mouse button --- examples/example03.hs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index 46f37b0..2a4f49a 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -104,12 +104,15 @@ update sec = do ev <- liftIO $ SDL.pollEvents mapM_ (\e -> case SDL.eventPayload e of - SDL.MouseButtonEvent dat -> do - let (SDL.P (SDL.V2 x y)) = SDL.mouseButtonEventPos dat + SDL.MouseMotionEvent dat -> + if SDL.ButtonLeft `elem` SDL.mouseMotionEventState dat + then do + let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat vx <- liftIO $ randomRIO (-20, 20) vy <- liftIO $ randomRIO (-20, 20) life <- liftIO $ randomRIO (1, 5) - -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy + traceM $ "position is: " ++ show x ++ " " ++ show y + traceM $ "velocity is: " ++ show vx ++ " " ++ show vy putAffection $ ud { coordinates = Just (fromIntegral x, fromIntegral y) , partsys = (partsys ud) @@ -122,6 +125,8 @@ update sec = do }) : (psParts $ partsys ud) } } + else + return () SDL.WindowClosedEvent _ -> do traceM "seeya!" put $ ad From a1b0b50b571da07bebc55e1da66d5ee64a3d5636 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 06:28:18 +0100 Subject: [PATCH 058/261] update particles after new ones come --- examples/example03.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index 2a4f49a..3832d41 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -98,8 +98,6 @@ update sec = do ud <- getAffection -- let newPart = updateParticles sec partUpd $ particles ud -- putAffection $ ud { particles = newPart } - nps <- updateParticleSystem (partsys ud) sec partUpd partDraw - putAffection $ ud { partsys = nps } traceM $ (show $ 1 / sec) ++ " FPS" ev <- liftIO $ SDL.pollEvents mapM_ (\e -> @@ -111,8 +109,8 @@ update sec = do vx <- liftIO $ randomRIO (-20, 20) vy <- liftIO $ randomRIO (-20, 20) life <- liftIO $ randomRIO (1, 5) - traceM $ "position is: " ++ show x ++ " " ++ show y - traceM $ "velocity is: " ++ show vx ++ " " ++ show vy + -- traceM $ "position is: " ++ show x ++ " " ++ show y + -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy putAffection $ ud { coordinates = Just (fromIntegral x, fromIntegral y) , partsys = (partsys ud) @@ -135,6 +133,9 @@ update sec = do _ -> return () ) ev + ud2 <- getAffection + nps <- updateParticleSystem (partsys ud2) sec partUpd partDraw + putAffection $ ud2 { partsys = nps } clean :: UserData -> IO () clean _ = return () From 179e2f95169d729b9bd4e49728cded6f6843e6a8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 20 Dec 2016 11:29:03 +0100 Subject: [PATCH 059/261] trying hard to make it better --- examples/example01.hs | 25 +++++++++++++------------ examples/example03.hs | 4 ++-- src/Affection/Types.hs | 41 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index 2331e6f..a3358ae 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -116,16 +116,17 @@ load _ = do draw :: Affection UserData () draw = do traceM "drawing" - AffectionData{..} <- get - let UserData{..} = userState - liftIO $ SDL.lockSurface drawSurface - pixels <- liftIO $ SDL.surfacePixels drawSurface - let SDL.Surface rawSurfacePtr _ = drawSurface - rawSurface <- liftIO $ peek rawSurfacePtr - pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface - format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) - SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface - let (w, h) = (fromIntegral rw, fromIntegral rh) + UserData{..} <- getAffection + -- AffectionData{..} <- get + -- let UserData{..} = userState + -- -- liftIO $ SDL.lockSurface drawSurface + -- pixels <- liftIO $ SDL.surfacePixels drawSurface + -- let SDL.Surface rawSurfacePtr _ = drawSurface + -- rawSurface <- liftIO $ peek rawSurfacePtr + -- pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface + -- format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) + -- SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface + -- let (w, h) = (fromIntegral rw, fromIntegral rh) drawRect foreground (nodeGraph M.! "over") (G.RGB 1 0 0) (Line 2) (G.GeglRectangle 10 10 500 500) -- liftIO $ G.gegl_node_blit -- (nodeGraph M.! "over" :: G.GeglNode) @@ -135,8 +136,8 @@ draw = do -- pixels -- (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) -- [G.GeglBlitDefault] - liftIO $ SDL.unlockSurface drawSurface - liftIO $ SDL.updateWindowSurface drawWindow + -- liftIO $ SDL.unlockSurface drawSurface + -- liftIO $ SDL.updateWindowSurface drawWindow update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do diff --git a/examples/example03.hs b/examples/example03.hs index 3832d41..7b579d4 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -117,9 +117,9 @@ update sec = do { psParts = (Particle { particleLife = life , particlePosition = (fromIntegral x, fromIntegral y) - , particleRotation = 0 + , particleRotation = Rad 0 , particleVelocity = (vx, vy) - , particlePitchRate = 0 + , particlePitchRate = Rad 0 }) : (psParts $ partsys ud) } } diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index c03c124..bca87d0 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -13,6 +13,8 @@ module Affection.Types , RGBA(..) , DrawType(..) , DrawRequest(..) + , Angle(..) + , ConvertAngle(..) -- | Particle system , Particle(..) , ParticleSystem(..) @@ -126,13 +128,42 @@ data DrawType { lineWidth :: Int -- ^ Width of line in pixels } +-- | 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 - { particleLife :: Double -- ^ Time to live in seconds - , particlePosition :: (Double, Double) -- ^ Position of particle on canvas - , particleRotation :: Double -- ^ Particle rotation - , particleVelocity :: (Int, Int) -- ^ particle velocity as vector of pixels per second - , particlePitchRate :: Double -- ^ Rotational velocity of particle + { particleLife :: Double + -- ^ Time to live in seconds + , 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 } deriving (Show, Eq) data ParticleSystem = ParticleSystem From 52da5e54bd7d5283dc5c7b802c2f7e202322ccc7 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 21 Dec 2016 00:16:21 +0100 Subject: [PATCH 060/261] some refinement --- src/Affection.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index f0c0d93..460cc63 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -58,13 +58,13 @@ withAffection AffectionConfig{..} = do let (w, h) = (fromIntegral rw, fromIntegral rh) stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w cpp = B.babl_components_per_pixel bablFormat - initContainer <- return . (\x -> AffectionData + initContainer <- (\x -> AffectionData { quitEvent = False , userState = x , drawWindow = window , drawSurface = surface , drawStack = [] - }) =<< loadState surface + }) <$> loadState surface (_, nState) <- runStateT ( A.runState $ do preLoop liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing From f468fdaaf53fbda2e6649470858363cb3c226550 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 21 Dec 2016 04:28:57 +0100 Subject: [PATCH 061/261] *vomiting sounds* --- affection.cabal | 18 ++++++++ examples/example01.hs | 2 +- examples/example02.hs | 39 ++++++++++------- examples/example03.hs | 2 +- src/Affection.hs | 5 ++- src/Affection/Draw.hs | 99 +++++++++++++++++++++++++----------------- src/Affection/Types.hs | 17 +++++--- 7 files changed, 117 insertions(+), 65 deletions(-) diff --git a/affection.cabal b/affection.cabal index cf25000..92511b4 100644 --- a/affection.cabal +++ b/affection.cabal @@ -130,3 +130,21 @@ executable example03 , 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 + else + buildable: False diff --git a/examples/example01.hs b/examples/example01.hs index a3358ae..bb61e2b 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -52,7 +52,7 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation [ G.Property "buffer" $ G.PropertyBuffer buffer diff --git a/examples/example02.hs b/examples/example02.hs index 72cc6eb..7b62eb0 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -3,10 +3,13 @@ import Affection import qualified SDL import qualified GEGL as G +import qualified GEGL.FFI.Buffer as G import qualified BABL as B import qualified Data.Map.Strict as M import Foreign.C.Types +import Foreign.Marshal.Utils (new) +import Foreign.Ptr (castPtr) import Debug.Trace @@ -16,7 +19,7 @@ main = do { initComponents = All , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow - , preLoop = drawInit + , preLoop = return () , drawLoop = draw , updateLoop = update , loadState = load @@ -42,20 +45,24 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + buffer@(G.GeglBuffer b) <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation - [ G.Property "buffer" $ G.PropertyBuffer buffer + bufPtr <- new b + sink <- G.gegl_node_new_child root $ G.Operation "gegl:buffer-sink" + [ G.Property "buffer" $ G.PropertyPointer $ castPtr bufPtr ] - traceM "buffer-source" - G.gegl_node_link checkerboard over - G.gegl_node_connect_to bufsrc "output" over "aux" + traceM "buffer-sink" + nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] + traceM "nop" + G.gegl_node_link_many [checkerboard, over, sink] + G.gegl_node_connect_to nop "output" over "aux" traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) , ("over" , over) , ("background" , checkerboard) - , ("foreground" , bufsrc) + , ("sink" , sink) + , ("nop" , nop) ] traceM "loading complete" return $ UserData @@ -64,10 +71,10 @@ load _ = do , coordinates = Nothing } -drawInit :: Affection UserData () -drawInit = do - UserData{..} <- getAffection - present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True +-- drawInit :: Affection UserData () +-- drawInit = do +-- UserData{..} <- getAffection +-- present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True draw :: Affection UserData () draw = do @@ -78,12 +85,12 @@ draw = do let (w, h) = (fromIntegral rw, fromIntegral rh) liftIO $ clearArea foreground (GeglRectangle 0 0 w h) maybe (return ()) (\(x, y) -> - drawRect - foreground - (nodeGraph M.! "over") + drawRect' + (nodeGraph M.! "nop") (G.RGBA 1 0 0 0.5) - (Line 7) + (Fill) (G.GeglRectangle (x - 10) (y - 10) 20 20) + foreground ) coordinates update :: Double -> AffectionState (AffectionData UserData) IO () diff --git a/examples/example03.hs b/examples/example03.hs index 7b579d4..7fd540e 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -45,7 +45,7 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation [ G.Property "buffer" $ G.PropertyBuffer buffer diff --git a/src/Affection.hs b/src/Affection.hs index 460cc63..e2670e6 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -63,6 +63,7 @@ withAffection AffectionConfig{..} = do , userState = x , drawWindow = window , drawSurface = surface + , drawFormat = format , drawStack = [] }) <$> loadState surface (_, nState) <- runStateT ( A.runState $ do @@ -79,7 +80,7 @@ withAffection AffectionConfig{..} = do -- get state ad <- get -- clean draw requests from last run - mapM_ (invalidateDrawRequest pixels format stride cpp) $ drawStack ad + mapM_ (invalidateDrawRequest pixels stride cpp) $ drawStack ad put $ ad { drawStack = [] } -- execute user defined draw loop @@ -89,7 +90,7 @@ withAffection AffectionConfig{..} = do (fromIntegral 10 ^ 9) -- handle all new draw requests ad2 <- get - clear <- catMaybes <$> mapM (handleDrawRequest pixels format stride cpp) (drawStack ad2) + clear <- catMaybes <$> mapM (handleDrawRequest pixels stride cpp) (drawStack ad2) -- save all draw requests to clear in next run put $ ad2 { drawStack = clear } diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 6237c83..9df3acf 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -3,10 +3,11 @@ -- | Module for drawing primitives module Affection.Draw ( drawRect + , drawRect' -- , clear , handleDrawRequest , invalidateDrawRequest - , present + -- , present , clearArea ) where @@ -16,7 +17,7 @@ import Foreign.Ptr (Ptr, plusPtr, nullPtr) import Foreign.C.Types import Control.Monad.State (get, put) -import Control.Monad (when) +import Control.Monad (when, unless) import qualified SDL @@ -43,7 +44,7 @@ drawRect buf node color (Fill) rect@G.GeglRectangle{..} = do ) ad@AffectionData{..} <- get put $ ad - { drawStack = (DrawRequest node rect buf False) : drawStack + { drawStack = (DrawRequest rect buf False) : drawStack } drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ @@ -58,60 +59,80 @@ drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do ) ad@AffectionData{..} <- get put $ ad - { drawStack = (DrawRequest node rect buf False) : drawStack + { drawStack = (DrawRequest rect buf False) : drawStack } -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) -- drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) -- drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) --- | force a blit of a specified area. Do not use often as it slows down the program -present - :: G.GeglNode -- ^ Node to blit - -> G.GeglBuffer -- ^ BUffer to draw - -> G.GeglRectangle -- ^ Area to blit - -> Bool -- ^ Shall the drawing be cleared in the next run? +drawRect' + :: G.GeglNode -- ^ Target Node + -> G.Color -- ^ Color to draw in + -> DrawType -- ^ Draw type + -> G.GeglRectangle -- ^ Dimensions of Rectangle + -> G.GeglBuffer -- ^ Final Buffer -> Affection us () -present node buf rect pers = do +drawRect' node color Fill rect@GeglRectangle{..} buf = do ad <- get + opNode <- liftIO $ G.gegl_node_new_child node $ G.Operation "gegl:rectangle" + [ G.Property "x" $ G.PropertyDouble $ fromIntegral rectangleX + , G.Property "y" $ G.PropertyDouble $ fromIntegral rectangleY + , G.Property "width" $ G.PropertyDouble $ fromIntegral rectangleWidth + , G.Property "height" $ G.PropertyDouble $ fromIntegral rectangleHeight + , G.Property "color" $ G.PropertyColor color + ] + diw <- liftIO $ G.gegl_node_connect_to opNode "output" node "input" + unless diw $ error "Affection.Draw.drawRect': connect failed" put $ ad - { drawStack = (DrawRequest node rect buf pers) : drawStack ad + { drawStack = (DrawRequest rect buf False) : drawStack ad } - ad <- get - format <- liftIO $ B.babl_format $ B.PixelFormat B.RGBA B.CFu8 - liftIO $ SDL.lockSurface $ drawSurface ad - liftIO $ G.gegl_node_blit - node - 1 - rect - format - nullPtr - 0 - [G.GeglBlitDefault] - liftIO $ SDL.unlockSurface $ drawSurface ad - liftIO $ SDL.updateWindowSurface $ drawWindow ad + +-- -- | force a blit of a specified area. Do not use often as it slows down the program +-- present +-- :: G.GeglNode -- ^ Node to blit +-- -> G.GeglBuffer -- ^ BUffer to draw +-- -> G.GeglRectangle -- ^ Area to blit +-- -> Bool -- ^ Shall the drawing be cleared in the next run? +-- -> Affection us () +-- present node buf rect pers = do +-- ad <- get +-- put $ ad +-- { drawStack = (DrawRequest rect buf pers) : drawStack ad +-- } +-- ad <- get +-- format <- liftIO $ B.babl_format $ B.PixelFormat B.RGBA B.CFu8 +-- liftIO $ SDL.lockSurface $ drawSurface ad +-- liftIO $ G.gegl_node_blit +-- rect +-- format +-- nullPtr +-- 0 +-- [G.GeglBlitDefault] +-- liftIO $ SDL.unlockSurface $ drawSurface ad +-- liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | function for handling 'DrawRequest's and updating the output handleDrawRequest :: Ptr a -- ^ Pixel buffer to blit to - -> B.BablFormatPtr -- ^ format to blit in + -- -> B.BablFormatPtr -- ^ format to blit in -> Int -- ^ Stride -> Int -- ^ Components per Pixel -> DrawRequest -- ^ 'DrawRequest' to handle -> Affection us (Maybe DrawRequest) -handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do +handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do ad <- get let surf = drawSurface ad liftIO $ SDL.lockSurface surf - liftIO $ G.gegl_node_blit - requestNode + liftIO $ G.gegl_buffer_get + requestBuffer + (Just requestArea) 1 - requestArea - format + (Just $ drawFormat ad) (pixels `plusPtr` (rectangleX requestArea * cpp + rectangleY requestArea * stride)) stride - [G.GeglBlitDefault] + G.GeglAbyssNone liftIO $ SDL.unlockSurface surf -- liftIO $ SDL.updateWindowSurface $ drawWindow ad if requestPersist @@ -123,25 +144,25 @@ handleDrawRequest pixels format stride cpp dr@DrawRequest{..} = do -- | clear a previously drawn area invalidateDrawRequest :: Ptr a -- ^ Pixel buffer to blit to - -> B.BablFormatPtr -- ^ format to blit in + -- -> B.BablFormatPtr -- ^ format to blit in -> Int -- ^ Stride -> Int -- ^ Components per Pixel -> DrawRequest -- ^ Drawrequest to invalidate -> Affection us () -invalidateDrawRequest pixels format stride cpp dr@DrawRequest{..} = do +invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do ad <- get let !surf = drawSurface ad liftIO $ clearArea requestBuffer requestArea liftIO $ SDL.lockSurface surf - liftIO $ G.gegl_node_blit - requestNode + liftIO $ G.gegl_buffer_get + requestBuffer + (Just requestArea) 1 - requestArea - format + (Just $ drawFormat ad) (pixels `plusPtr` (rectangleX requestArea * cpp + rectangleY requestArea * stride)) stride - [G.GeglBlitDefault] + G.GeglAbyssNone liftIO $ SDL.unlockSurface surf -- liftIO $ SDL.updateWindowSurface $ drawWindow ad diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index bca87d0..6a61f98 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -30,7 +30,9 @@ module Affection.Types import qualified SDL.Init as SDL import qualified SDL.Video as SDL import qualified Data.Text as T + import qualified GEGL as G +import qualified BABL as B import Control.Monad.IO.Class import Control.Monad.State @@ -58,6 +60,11 @@ data AffectionConfig us = AffectionConfig -- ^ Provide your own finisher function to clean your data. } +-- | Components to initialize in SDL. +data InitComponents + = All + | Only [SDL.InitFlag] + -- | Main type for defining the look, feel and action of the whole application. data AffectionData us = AffectionData -- { affectionConfig :: AffectionConfig us -- ^ Application configuration. @@ -65,22 +72,20 @@ data AffectionData us = AffectionData , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window , drawSurface :: SDL.Surface -- ^ SDL surface + , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed , clearStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be invalidated } -- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated data DrawRequest = DrawRequest - { requestNode :: G.GeglNode -- ^ The 'G.GeglNode' to blit - , requestArea :: G.GeglRectangle -- ^ The area to update + { requestArea :: G.GeglRectangle -- ^ The area to update , requestBuffer :: G.GeglBuffer -- ^ Buffer to draw , requestPersist :: Bool -- ^ Shall the drawRequest persist } --- | Components to initialize in SDL. -data InitComponents - = All - | Only [SDL.InitFlag] +-- | 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 From 257d7f8285627e0e38ad2172123b06807489bdc5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 21 Dec 2016 04:32:56 +0100 Subject: [PATCH 062/261] recent hanges in GEGL --- examples/example01.hs | 2 +- examples/example02.hs | 2 +- examples/example03.hs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index a3358ae..bb61e2b 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -52,7 +52,7 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation [ G.Property "buffer" $ G.PropertyBuffer buffer diff --git a/examples/example02.hs b/examples/example02.hs index 72cc6eb..c1fcfb5 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -42,7 +42,7 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation [ G.Property "buffer" $ G.PropertyBuffer buffer diff --git a/examples/example03.hs b/examples/example03.hs index 7b579d4..7fd540e 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -45,7 +45,7 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation [ G.Property "buffer" $ G.PropertyBuffer buffer From 95c008cc4edac2a369f0e952b6284c84b3857216 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 21 Dec 2016 04:33:28 +0100 Subject: [PATCH 063/261] new example --- examples/example04.hs | 120 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 examples/example04.hs diff --git a/examples/example04.hs b/examples/example04.hs new file mode 100644 index 0000000..a720487 --- /dev/null +++ b/examples/example04.hs @@ -0,0 +1,120 @@ +{-# LANGUAGE RecordWildCards #-} + +import Affection +import qualified SDL +import qualified GEGL as G +import qualified BABL as B +import qualified BABL.FFI.Format as B +import qualified Data.Map.Strict as M + +import Foreign.C.Types +import Foreign.Ptr (castPtr, nullPtr) +import Foreign.Marshal.Utils (new) + +import System.Random (randomRIO) + +import Control.Monad (unless) + +import Debug.Trace + +main :: IO () +main = do + conf <- return $ AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + , preLoop = return () + , drawLoop = draw + , updateLoop = update + , loadState = load + , cleanUp = clean + } + withAffection conf + +data UserData = UserData + { nodeGraph :: M.Map String G.GeglNode + , coordinates :: Maybe (Int, Int) + } + +load :: SDL.Surface -> IO UserData +load surface = do + traceM "loading" + root <- G.gegl_node_new + traceM "new root node" + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation + [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 + , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 + ] + traceM "checkerboard" + over <- G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] + traceM "nop" + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" + [ G.Property "x" $ G.PropertyDouble 0 + , G.Property "y" $ G.PropertyDouble 0 + , G.Property "width" $ G.PropertyDouble 800 + , G.Property "height" $ G.PropertyDouble 600 + ] + traceM "crop" + format <- B.babl_format $ B.PixelFormat B.RGBA B.CFu8 + pixels <- liftIO $ new =<< SDL.surfacePixels surface + sink <- G.gegl_node_new_child root $ G.Operation "gegl:buffer-sink" + [ G.Property "buffer" $ G.PropertyPointer $ castPtr $ pixels + , G.Property "format" $ G.PropertyPointer $ nullPtr + ] + traceM "sink" + G.gegl_node_link_many [checkerboard, over, crop, sink] + diw <- G.gegl_node_connect_to nop "output" over "aux" + unless diw (error "connect failed") + traceM "connections made" + myMap <- return $ M.fromList + [ ("root" , root) + , ("over" , over) + , ("background" , checkerboard) + , ("nop" , nop) + , ("crop" , crop) + , ("sink" , sink) + ] + traceM "loading complete" + return $ UserData + { nodeGraph = myMap + , coordinates = Nothing + } + +update :: Double -> AffectionState (AffectionData UserData) IO () +update sec = do + traceM "updating" + ad <- get + ud <- getAffection + traceM $ (show $ 1 / sec) ++ " FPS" + ev <- liftIO $ SDL.pollEvents + mapM_ (\e -> + case SDL.eventPayload e of + SDL.MouseButtonEvent dat -> do + let (SDL.P (SDL.V2 x y)) = SDL.mouseButtonEventPos dat + drawRect + (nodeGraph ud M.! "nop") + (G.RGBA 1 0 0 0.5) + Fill + (G.GeglRectangle (fromIntegral x - 10) (fromIntegral y - 10) 20 20) + traceM $ "drewn a rectangle at " ++ show x ++ " " ++ show y + SDL.WindowClosedEvent _ -> do + traceM "seeya!" + put $ ad + { quitEvent = True + } + _ -> + return () + ) ev + +draw :: Affection UserData () +draw = do + ad <- get + ud <- getAffection + liftIO $ SDL.lockSurface $ drawSurface ad + liftIO $ G.gegl_node_process (nodeGraph ud M.! "sink") + liftIO $ SDL.unlockSurface $ drawSurface ad + +clean :: UserData -> IO () +clean _ = return () From 388c141e232ab8c3fa504c5c8148b92447a8672f Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 23 Dec 2016 14:18:39 +0100 Subject: [PATCH 064/261] particle system now works as a cascade of nodes --- affection.cabal | 34 +++++++------ examples/example02.hs | 12 +++-- examples/example03.hs | 61 +++++++++++++++-------- src/Affection.hs | 4 +- src/Affection/Draw.hs | 102 ++++++++++++++++++++------------------ src/Affection/Particle.hs | 36 +++++++++++--- src/Affection/Types.hs | 28 ++++++++--- 7 files changed, 173 insertions(+), 104 deletions(-) diff --git a/affection.cabal b/affection.cabal index 92511b4..9c73464 100644 --- a/affection.cabal +++ b/affection.cabal @@ -59,7 +59,9 @@ library , gegl , babl , monad-loops + , containers , clock + , glib -- , sdl2-image executable example00 @@ -79,22 +81,22 @@ executable example00 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 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 diff --git a/examples/example02.hs b/examples/example02.hs index 7b62eb0..dcd0142 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -48,13 +48,17 @@ load _ = do buffer@(G.GeglBuffer b) <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) bufPtr <- new b - sink <- G.gegl_node_new_child root $ G.Operation "gegl:buffer-sink" - [ G.Property "buffer" $ G.PropertyPointer $ castPtr bufPtr + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" + [ G.Property "buffer" $ G.PropertyBuffer buffer ] traceM "buffer-sink" nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] traceM "nop" - G.gegl_node_link_many [checkerboard, over, sink] + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" + [ G.Property "width" $ G.PropertyDouble 800 + , G.Property "height" $ G.PropertyDouble 600 + ] + G.gegl_node_link_many [checkerboard, over, crop, sink] G.gegl_node_connect_to nop "output" over "aux" traceM "connections made" myMap <- return $ M.fromList @@ -63,6 +67,7 @@ load _ = do , ("background" , checkerboard) , ("sink" , sink) , ("nop" , nop) + , ("crop" , crop) ] traceM "loading complete" return $ UserData @@ -92,6 +97,7 @@ draw = do (G.GeglRectangle (x - 10) (y - 10) 20 20) foreground ) coordinates + liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do diff --git a/examples/example03.hs b/examples/example03.hs index 7fd540e..d565b55 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -29,7 +29,6 @@ main = do data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer - , coordinates :: Maybe (Int, Int) , partsys :: ParticleSystem } @@ -45,37 +44,47 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< + buffer@(G.GeglBuffer b) <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation - [ G.Property "buffer" $ G.PropertyBuffer buffer + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" + [G.Property "buffer" $ G.PropertyBuffer buffer ] - traceM "buffer-source" - G.gegl_node_link checkerboard over - G.gegl_node_connect_to bufsrc "output" over "aux" + traceM "buffer-sink" + nop <- G.gegl_node_new_child root $ G.Operation "nop" [] + traceM "nop" + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" + [ G.Property "width" $ G.PropertyDouble 800 + , G.Property "height" $ G.PropertyDouble 600 + ] + traceM "crop" + G.gegl_node_link_many [checkerboard, over, crop, sink] + G.gegl_node_connect_to nop "output" over "aux" traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) , ("over" , over) , ("background" , checkerboard) - , ("foreground" , bufsrc) + , ("sink" , sink) + , ("nop" , nop) + , ("crop" , crop) ] traceM "loading complete" return $ UserData { nodeGraph = myMap , foreground = buffer - , coordinates = Nothing - , partsys = ParticleSystem [] over buffer + , partsys = ParticleSystem [] nop buffer } drawInit :: Affection UserData () -drawInit = do - UserData{..} <- getAffection - present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True +drawInit = return () + -- UserData{..} <- getAffection + -- present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True draw :: Affection UserData () draw = do traceM "drawing" + UserData{..} <- getAffection + liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" -- ad <- get -- ud <- getAffection -- drawParticles partDraw $ particles ud @@ -109,17 +118,30 @@ update sec = do vx <- liftIO $ randomRIO (-20, 20) vy <- liftIO $ randomRIO (-20, 20) life <- liftIO $ randomRIO (1, 5) + tempRoot <- liftIO $ G.gegl_node_new + tempOver <- liftIO $ G.gegl_node_new_child tempRoot + G.defaultOverOperation + tempNop <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation + "gegl:nop" [] + liftIO $ G.gegl_node_connect_to tempNop "output" tempOver "aux" -- traceM $ "position is: " ++ show x ++ " " ++ show y -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy putAffection $ ud - { coordinates = Just (fromIntegral x, fromIntegral y) - , partsys = (partsys ud) + { partsys = (partsys ud) { psParts = (Particle { particleLife = life , particlePosition = (fromIntegral x, fromIntegral y) , particleRotation = Rad 0 , particleVelocity = (vx, vy) , particlePitchRate = Rad 0 + , particleRootNode = tempRoot + , particleNodeGraph = M.fromList + [ ("root", tempRoot) + , ("over", tempOver) + , ("nop" , tempNop) + ] + , particleStackCont = tempOver + , particleDrawFlange = tempNop }) : (psParts $ partsys ud) } } @@ -144,7 +166,6 @@ partUpd :: Double -> Particle -> Particle partUpd sec p@Particle{..} = p { particlePosition = (newX, newY) - , particleLife = particleLife - sec } where newX = (fst particlePosition) + sec * (fromIntegral $ fst particleVelocity) @@ -153,9 +174,9 @@ partUpd sec p@Particle{..} = partDraw :: G.GeglBuffer -> G.GeglNode -> Particle -> Affection UserData () partDraw buf node Particle{..} = do ud <- getAffection - drawRect - buf - node + drawRect' + particleDrawFlange (G.RGBA 1 0 0 0.5) - (Line 5) + (Fill) (G.GeglRectangle ((floor $ fst particlePosition) - 10) ((floor $ snd particlePosition) -10) 20 20) + buf diff --git a/src/Affection.hs b/src/Affection.hs index e2670e6..316f4db 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -83,11 +83,11 @@ withAffection AffectionConfig{..} = do mapM_ (invalidateDrawRequest pixels stride cpp) $ drawStack ad put $ ad { drawStack = [] } - -- execute user defined draw loop - drawLoop -- execute user defined update loop updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) + -- execute user defined draw loop + drawLoop -- handle all new draw requests ad2 <- get clear <- catMaybes <$> mapM (handleDrawRequest pixels stride cpp) (drawStack ad2) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 9df3acf..1a3ab75 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -2,8 +2,9 @@ -- | Module for drawing primitives module Affection.Draw - ( drawRect - , drawRect' + ( + -- drawRect + drawRect' -- , clear , handleDrawRequest , invalidateDrawRequest @@ -13,12 +14,14 @@ module Affection.Draw import Affection.Types -import Foreign.Ptr (Ptr, plusPtr, nullPtr) +import Foreign import Foreign.C.Types import Control.Monad.State (get, put) import Control.Monad (when, unless) +import System.Glib.GObject + import qualified SDL import qualified BABL as B @@ -28,43 +31,43 @@ import qualified GEGL as G import Debug.Trace -- | Draw a rectangle on target buffer -drawRect - :: G.GeglBuffer -- ^ Target buffer - -> G.GeglNode -- ^ Target node - -> G.Color -- ^ Color to draw in - -> DrawType -- ^ Draw type - -> GeglRectangle -- ^ Dimensions of Rectangle - -> Affection us () -drawRect buf node color (Fill) rect@G.GeglRectangle{..} = do - liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ - (\(x, y) -> - let col = unsafeColorize color - in - G.Pixel x y col - ) - ad@AffectionData{..} <- get - put $ ad - { drawStack = (DrawRequest rect buf False) : drawStack - } -drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do - liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ - (\(x, y) -> - let col = unsafeColorize color - in if not ((x >= rectangleX + size && x < rectangleX + rectangleWidth - size) && - (y >= rectangleY + size && y < rectangleY + rectangleHeight - size)) - then - G.Pixel x y col - else - G.Pixel x y $ unsafeColorize $ G.RGBA 0 0 0 0 - ) - ad@AffectionData{..} <- get - put $ ad - { drawStack = (DrawRequest rect buf False) : drawStack - } - -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) - -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) - -- drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) - -- drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) +-- drawRect +-- :: G.GeglBuffer -- ^ Target buffer +-- -> G.GeglNode -- ^ Target node +-- -> G.Color -- ^ Color to draw in +-- -> DrawType -- ^ Draw type +-- -> GeglRectangle -- ^ Dimensions of Rectangle +-- -> Affection us () +-- drawRect buf node color (Fill) rect@G.GeglRectangle{..} = do +-- liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ +-- (\(x, y) -> +-- let col = unsafeColorize color +-- in +-- G.Pixel x y col +-- ) +-- ad@AffectionData{..} <- get +-- put $ ad +-- { drawStack = (DrawRequest rect buf False) : drawStack +-- } +-- drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do +-- liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ +-- (\(x, y) -> +-- let col = unsafeColorize color +-- in if not ((x >= rectangleX + size && x < rectangleX + rectangleWidth - size) && +-- (y >= rectangleY + size && y < rectangleY + rectangleHeight - size)) +-- then +-- G.Pixel x y col +-- else +-- G.Pixel x y $ unsafeColorize $ G.RGBA 0 0 0 0 +-- ) +-- ad@AffectionData{..} <- get +-- put $ ad +-- { drawStack = (DrawRequest rect buf False) : drawStack +-- } +-- -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) +-- -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) +-- -- drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) +-- -- drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) drawRect' :: G.GeglNode -- ^ Target Node @@ -75,7 +78,8 @@ drawRect' -> Affection us () drawRect' node color Fill rect@GeglRectangle{..} buf = do ad <- get - opNode <- liftIO $ G.gegl_node_new_child node $ G.Operation "gegl:rectangle" + tempRoot <- liftIO $ G.gegl_node_new + opNode <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation "gegl:rectangle" [ G.Property "x" $ G.PropertyDouble $ fromIntegral rectangleX , G.Property "y" $ G.PropertyDouble $ fromIntegral rectangleY , G.Property "width" $ G.PropertyDouble $ fromIntegral rectangleWidth @@ -85,7 +89,7 @@ drawRect' node color Fill rect@GeglRectangle{..} buf = do diw <- liftIO $ G.gegl_node_connect_to opNode "output" node "input" unless diw $ error "Affection.Draw.drawRect': connect failed" put $ ad - { drawStack = (DrawRequest rect buf False) : drawStack ad + { drawStack = (DrawRequest rect buf (Kill tempRoot)) : drawStack ad } -- -- | force a blit of a specified area. Do not use often as it slows down the program @@ -135,11 +139,11 @@ handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do G.GeglAbyssNone liftIO $ SDL.unlockSurface surf -- liftIO $ SDL.updateWindowSurface $ drawWindow ad - if requestPersist - then - return Nothing - else - return $ Just dr + case requestPersist of + Yes -> + return Nothing + Kill _ -> + return $ Just dr -- | clear a previously drawn area invalidateDrawRequest @@ -151,7 +155,7 @@ invalidateDrawRequest -> Affection us () invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do ad <- get - let !surf = drawSurface ad + let surf = drawSurface ad liftIO $ clearArea requestBuffer requestArea liftIO $ SDL.lockSurface surf liftIO $ G.gegl_buffer_get @@ -164,6 +168,8 @@ invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do stride G.GeglAbyssNone liftIO $ SDL.unlockSurface surf + let Kill victim = requestPersist + liftIO $ G.gegl_node_drop victim -- liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | compute color for a single pixel diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 1d4e900..884bc6f 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -9,7 +9,9 @@ module Affection.Particle import Affection.Types -import Data.Maybe (catMaybes) +import Control.Monad + +import Data.Maybe import qualified GEGL as G @@ -22,13 +24,28 @@ updateParticle -- This Function should take the elapsed time -- in seconds and the initial particle as arguments. -> Particle -- ^ 'Particle' to be processed - -> Maybe Particle -- ^ resulting 'Particle' -updateParticle time funct p@Particle{..} = - if particleLife - time < 0 - then - Nothing + -> IO (Maybe Particle) -- ^ resulting 'Particle' +updateParticle time funct pa = + if particleLife pa - time < 0 + then do + G.gegl_node_drop $ particleRootNode pa + return $ Nothing else - Just $ funct time $ p { particleLife = particleLife - time } + return $ Just $ funct time $ pa { particleLife = particleLife pa - time } +-- updateParticle time funct acc@[p] pa = +-- if particleLife pa - time < 0 +-- then do +-- G.gegl_node_drop $ particleRootNode pa +-- return $ Nothing : acc +-- else +-- return $ (Just $ funct time $ pa { particleLife = particleLife pa - time }) : acc +-- updateParticle time funct acc@(p:ps) pa = +-- if particleLife pa - time < 0 +-- then do +-- G.gegl_node_drop $ particleRootNode pa +-- return $ Nothing : acc +-- else +-- return $ (Just $ funct time $ pa { particleLife = particleLife pa - time }) : acc drawParticles :: (Particle -> Affection us ()) @@ -43,7 +60,10 @@ updateParticleSystem -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) -> Affection us ParticleSystem updateParticleSystem sys sec upd draw = do - let x = catMaybes $ map (updateParticle sec upd) (psParts sys) + -- let x = catMaybes <$> mapM (updateParticle sec upd) (psParts sys) + -- x <- liftIO $ catMaybes <$> foldM (updateParticle sec upd) [] (psParts sys) + x <- liftIO $ catMaybes <$> mapM (updateParticle sec upd) (psParts sys) + liftIO $ G.gegl_node_link_many $ map particleStackCont x ++ [psNode sys] mapM_ (draw (psBuffer sys) (psNode sys)) x return $ sys { psParts = x } diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 6a61f98..c02e889 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -13,6 +13,7 @@ module Affection.Types , RGBA(..) , DrawType(..) , DrawRequest(..) + , RequestPersist(..) , Angle(..) , ConvertAngle(..) -- | Particle system @@ -30,6 +31,7 @@ module Affection.Types import qualified SDL.Init as SDL import qualified SDL.Video as SDL import qualified Data.Text as T +import Data.Map import qualified GEGL as G import qualified BABL as B @@ -81,9 +83,13 @@ data AffectionData us = AffectionData data DrawRequest = DrawRequest { requestArea :: G.GeglRectangle -- ^ The area to update , requestBuffer :: G.GeglBuffer -- ^ Buffer to draw - , requestPersist :: Bool -- ^ Shall the drawRequest persist + , requestPersist :: RequestPersist -- ^ Shall the drawRequest persist } +data RequestPersist + = Yes + | Kill G.GeglNode + -- | A type for storing 'DrawRequest' results to be executed frequently. TODO data DrawAsset = DrawAsset @@ -159,17 +165,25 @@ instance Eq Angle where -- | A single particle data Particle = Particle - { particleLife :: Double + { particleLife :: Double -- ^ Time to live in seconds - , particlePosition :: (Double, Double) + , particlePosition :: (Double, Double) -- ^ Position of particle on canvas - , particleRotation :: Angle + , particleRotation :: Angle -- ^ Particle rotation - , particleVelocity :: (Int, Int) + , particleVelocity :: (Int, Int) -- ^ particle velocity as vector of pixels per second - , particlePitchRate :: Angle + , particlePitchRate :: Angle -- ^ Rotational velocity of particle in angle per second - } deriving (Show, Eq) + , 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 + } data ParticleSystem = ParticleSystem { psParts :: [Particle] From da79d55a0de7eb3252966bb00a585be54c71ab04 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 24 Dec 2016 01:10:18 +0100 Subject: [PATCH 065/261] make text more visible --- examples/example00.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example00.hs b/examples/example00.hs index 8046990..84b370d 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -53,7 +53,7 @@ load _ = do traceM "over" text <- G.gegl_node_new_child root $ G.textOperation [ G.Property "string" $ G.PropertyString "Hello world!" - , G.Property "color" $ G.PropertyColor $ G.RGBA 0 0 1 0.1 + , G.Property "color" $ G.PropertyColor $ G.RGBA 0 0 1 0.5 , G.Property "size" $ G.PropertyDouble 40 ] traceM "text" From 158e4ddc800521411ffb785a94b6b8bb8ad0b05e Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 24 Dec 2016 01:11:59 +0100 Subject: [PATCH 066/261] trying to enable initial draw again --- examples/example03.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index d565b55..3d296b4 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -76,9 +76,9 @@ load _ = do } drawInit :: Affection UserData () -drawInit = return () - -- UserData{..} <- getAffection - -- present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True +drawInit = do + UserData{..} <- getAffection + present (GeglRectangle 0 0 800 600) foreground True draw :: Affection UserData () draw = do From ee1200b597281938c007dc9453746780d2af7b6d Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 24 Dec 2016 01:12:34 +0100 Subject: [PATCH 067/261] random colors --- examples/example03.hs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index 3d296b4..da2ed00 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -118,12 +118,21 @@ update sec = do vx <- liftIO $ randomRIO (-20, 20) vy <- liftIO $ randomRIO (-20, 20) life <- liftIO $ randomRIO (1, 5) + r <- liftIO $ randomRIO (0,1) + g <- liftIO $ randomRIO (0,1) + b <- liftIO $ randomRIO (0,1) tempRoot <- liftIO $ G.gegl_node_new tempOver <- liftIO $ G.gegl_node_new_child tempRoot G.defaultOverOperation - tempNop <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation - "gegl:nop" [] - liftIO $ G.gegl_node_connect_to tempNop "output" tempOver "aux" + tempRect <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation + "gegl:rectangle" + [ G.Property "x" $ G.PropertyDouble $ fromIntegral x - 10 + , G.Property "y" $ G.PropertyDouble $ fromIntegral y - 10 + , G.Property "width" $ G.PropertyDouble 20 + , G.Property "height" $ G.PropertyDouble 20 + , G.Property "color" $ G.PropertyColor $ (G.RGBA r g b 0.5) + ] + liftIO $ G.gegl_node_connect_to tempRect "output" tempOver "aux" -- traceM $ "position is: " ++ show x ++ " " ++ show y -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy putAffection $ ud @@ -138,10 +147,10 @@ update sec = do , particleNodeGraph = M.fromList [ ("root", tempRoot) , ("over", tempOver) - , ("nop" , tempNop) + , ("rect" , tempRect) ] , particleStackCont = tempOver - , particleDrawFlange = tempNop + , particleDrawFlange = tempOver }) : (psParts $ partsys ud) } } From d5bf09919cf3ad7ae37c8b42a3d6f51f5f583be8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 24 Dec 2016 01:13:00 +0100 Subject: [PATCH 068/261] make it work! --- examples/example03.hs | 31 +++++++++++++++++++------------ src/Affection/Draw.hs | 24 ++++++++++++++++++++---- src/Affection/Particle.hs | 24 ++++++++++++++---------- src/Affection/Types.hs | 2 +- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index da2ed00..02ed06d 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -171,21 +171,28 @@ update sec = do clean :: UserData -> IO () clean _ = return () -partUpd :: Double -> Particle -> Particle -partUpd sec p@Particle{..} = - p +partUpd :: Double -> Particle -> Affection UserData Particle +partUpd sec p@Particle{..} = do + let newX = (fst particlePosition) + sec * (fromIntegral $ fst particleVelocity) + newY = (snd particlePosition) + sec * (fromIntegral $ snd particleVelocity) + liftIO $ G.gegl_node_set (particleNodeGraph M.! "rect") $ G.Operation "gegl:rectangle" + [ G.Property "x" $ G.PropertyDouble $ newX - 10 + , G.Property "y" $ G.PropertyDouble $ newY - 10 + ] + return p { particlePosition = (newX, newY) } - where - newX = (fst particlePosition) + sec * (fromIntegral $ fst particleVelocity) - newY = (snd particlePosition) + sec * (fromIntegral $ snd particleVelocity) partDraw :: G.GeglBuffer -> G.GeglNode -> Particle -> Affection UserData () partDraw buf node Particle{..} = do - ud <- getAffection - drawRect' - particleDrawFlange - (G.RGBA 1 0 0 0.5) - (Fill) - (G.GeglRectangle ((floor $ fst particlePosition) - 10) ((floor $ snd particlePosition) -10) 20 20) + present + (G.GeglRectangle (floor $ fst particlePosition - 10) (floor $ snd particlePosition - 10) 20 20) buf + False + -- ud <- getAffection + -- drawRect' + -- particleDrawFlange + -- (G.RGBA 1 0 0 0.5) + -- (Fill) + -- (G.GeglRectangle ((floor $ fst particlePosition) - 10) ((floor $ snd particlePosition) -10) 20 20) + -- buf diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 1a3ab75..8826e10 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -8,7 +8,7 @@ module Affection.Draw -- , clear , handleDrawRequest , invalidateDrawRequest - -- , present + , present , clearArea ) where @@ -89,7 +89,20 @@ drawRect' node color Fill rect@GeglRectangle{..} buf = do diw <- liftIO $ G.gegl_node_connect_to opNode "output" node "input" unless diw $ error "Affection.Draw.drawRect': connect failed" put $ ad - { drawStack = (DrawRequest rect buf (Kill tempRoot)) : drawStack ad + { drawStack = (DrawRequest rect buf (Kill (Just tempRoot))) : drawStack ad + } + +-- | Force update of a specific region on screen +present + :: G.GeglRectangle -- ^ Area to be updated + -> G.GeglBuffer -- ^ Target buffer + -> Bool -- ^ Shall the 'DrawRequest' persist? + -> Affection us () +present rect buf kill = do + ad <- get + let k = if not kill then Kill Nothing else Yes + put ad + { drawStack = (DrawRequest rect buf k) : drawStack ad } -- -- | force a blit of a specified area. Do not use often as it slows down the program @@ -168,8 +181,11 @@ invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do stride G.GeglAbyssNone liftIO $ SDL.unlockSurface surf - let Kill victim = requestPersist - liftIO $ G.gegl_node_drop victim + case requestPersist of + Kill (Just victim) -> + liftIO $ G.gegl_node_drop victim + _ -> + return () -- liftIO $ SDL.updateWindowSurface $ drawWindow ad -- | compute color for a single pixel diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 884bc6f..5a5d45d 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -19,19 +19,23 @@ import qualified GEGL as G -- and death is being handled by 'updateParticles' itself and does not need to -- bother you. updateParticle - :: Double -- ^ Elapsed time in seconds - -> (Double -> Particle -> Particle) -- ^ Update function for a single 'Particle' - -- This Function should take the elapsed time - -- in seconds and the initial particle as arguments. - -> Particle -- ^ 'Particle' to be processed - -> IO (Maybe Particle) -- ^ resulting 'Particle' + :: Double + -- ^ Elapsed time in seconds + -> (Double -> Particle -> Affection us Particle) + -- ^ Update function for a single 'Particle' + -- This Function should take the elapsed time + -- in seconds and the initial particle as arguments. + -> Particle + -- ^ 'Particle' to be processed + -> Affection us (Maybe Particle) + -- ^ resulting 'Particle' updateParticle time funct pa = if particleLife pa - time < 0 then do - G.gegl_node_drop $ particleRootNode pa + liftIO $ G.gegl_node_drop $ particleRootNode pa return $ Nothing else - return $ Just $ funct time $ pa { particleLife = particleLife pa - time } + Just <$> funct time pa { particleLife = particleLife pa - time } -- updateParticle time funct acc@[p] pa = -- if particleLife pa - time < 0 -- then do @@ -56,13 +60,13 @@ drawParticles = mapM_ updateParticleSystem :: ParticleSystem -> Double - -> (Double -> Particle -> Particle) + -> (Double -> Particle -> Affection us Particle) -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) -> Affection us ParticleSystem updateParticleSystem sys sec upd draw = do -- let x = catMaybes <$> mapM (updateParticle sec upd) (psParts sys) -- x <- liftIO $ catMaybes <$> foldM (updateParticle sec upd) [] (psParts sys) - x <- liftIO $ catMaybes <$> mapM (updateParticle sec upd) (psParts sys) + x <- catMaybes <$> mapM (updateParticle sec upd) (psParts sys) liftIO $ G.gegl_node_link_many $ map particleStackCont x ++ [psNode sys] mapM_ (draw (psBuffer sys) (psNode sys)) x return $ sys diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index c02e889..9d43022 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -88,7 +88,7 @@ data DrawRequest = DrawRequest data RequestPersist = Yes - | Kill G.GeglNode + | Kill (Maybe G.GeglNode) -- | A type for storing 'DrawRequest' results to be executed frequently. TODO data DrawAsset = DrawAsset From a564eed511298b1141804d05c84a5948373a82de Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 24 Dec 2016 08:26:45 +0100 Subject: [PATCH 069/261] enable debugging --- affection.cabal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/affection.cabal b/affection.cabal index 9c73464..1ff5b90 100644 --- a/affection.cabal +++ b/affection.cabal @@ -49,7 +49,7 @@ library , UndecidableInstances hs-source-dirs: src default-language: Haskell2010 - ghc-options: -Wall + ghc-options: -Wall -debug -- Other library packages from which modules are imported. build-depends: base >=4.9 && <4.10 , sdl2 @@ -118,7 +118,7 @@ executable example02 executable example03 hs-source-dirs: examples main-is: example03.hs - ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts + ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts -debug default-language: Haskell2010 default-extensions: OverloadedStrings if flag(examples) From b75f20c3d59945a349cbf2e2cf1bfbc7138fe21a Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 24 Dec 2016 08:27:09 +0100 Subject: [PATCH 070/261] link all new particles --- examples/example03.hs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/example03.hs b/examples/example03.hs index 02ed06d..a50a4d3 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -4,8 +4,11 @@ import Affection import qualified SDL import qualified GEGL as G import qualified BABL as B + import qualified Data.Map.Strict as M +import Control.Monad (when) + import Foreign.C.Types import System.Random (randomRIO) @@ -154,6 +157,10 @@ update sec = do }) : (psParts $ partsys ud) } } + when (not $ null $ psParts $ partsys ud) $ + liftIO $ G.gegl_node_link + tempOver + (particleStackCont $ head $ psParts $ partsys ud) else return () SDL.WindowClosedEvent _ -> do From c6a37e80f71241ab59f50aeffa81e6e719d7dd5f Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 24 Dec 2016 08:27:47 +0100 Subject: [PATCH 071/261] link only when needed --- src/Affection/Particle.hs | 75 ++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 5a5d45d..c1a53fc 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -15,6 +15,8 @@ import Data.Maybe import qualified GEGL as G +import Debug.Trace + -- This function updates particles through a specified function. Particle ageing -- and death is being handled by 'updateParticles' itself and does not need to -- bother you. @@ -25,31 +27,60 @@ updateParticle -- ^ Update function for a single 'Particle' -- This Function should take the elapsed time -- in seconds and the initial particle as arguments. + -> [Maybe Particle] -> Particle -- ^ 'Particle' to be processed - -> Affection us (Maybe Particle) + -> Affection us [Maybe Particle] -- ^ resulting 'Particle' -updateParticle time funct pa = +updateParticle time funct acc@[] pa = if particleLife pa - time < 0 then do liftIO $ G.gegl_node_drop $ particleRootNode pa - return $ Nothing - else - Just <$> funct time pa { particleLife = particleLife pa - time } --- updateParticle time funct acc@[p] pa = --- if particleLife pa - time < 0 --- then do --- G.gegl_node_drop $ particleRootNode pa --- return $ Nothing : acc --- else --- return $ (Just $ funct time $ pa { particleLife = particleLife pa - time }) : acc --- updateParticle time funct acc@(p:ps) pa = --- if particleLife pa - time < 0 --- then do --- G.gegl_node_drop $ particleRootNode pa --- return $ Nothing : acc --- else --- return $ (Just $ funct time $ pa { particleLife = particleLife pa - time }) : acc + return $ Nothing : acc + else do + np <- Just <$> funct time pa { particleLife = particleLife pa - time } + return $ np : acc +updateParticle time funct acc@[p] pa = + if particleLife pa - time < 0 + then do + liftIO $ G.gegl_node_drop $ particleRootNode pa + return $ Nothing : acc + else do + when (not $ isNothing p) $ do + -- liftIO $ traceIO "linking second node in list" + liftIO $ G.gegl_node_link + (particleStackCont pa) + (particleStackCont $ fromJust p) + np <- Just <$> funct time pa { particleLife = particleLife pa - time } + return $ np : acc +updateParticle time funct acc@(p:ps) pa = + if particleLife pa - time < 0 + then do + liftIO $ G.gegl_node_drop $ particleRootNode pa + return $ Nothing : acc + else do + when (isNothing p) $ do + let mnl = nextLiving ps + maybe + (return ()) + (\nl -> do + -- liftIO $ traceIO "linking nth node on list" + liftIO $ G.gegl_node_link (particleStackCont pa) (particleStackCont nl)) + mnl + np <- Just <$> funct time pa { particleLife = particleLife pa - time } + return $ np : acc + +-- | Get the next living particle from a list +nextLiving + :: [Maybe Particle] + -> Maybe Particle +nextLiving [] = Nothing +nextLiving acc = case catMaybes acc of + [] -> Nothing + ps -> Just $ head $ ps + -- if isNothing p + -- then nextLiving ps + -- else p drawParticles :: (Particle -> Affection us ()) @@ -66,8 +97,10 @@ updateParticleSystem updateParticleSystem sys sec upd draw = do -- let x = catMaybes <$> mapM (updateParticle sec upd) (psParts sys) -- x <- liftIO $ catMaybes <$> foldM (updateParticle sec upd) [] (psParts sys) - x <- catMaybes <$> mapM (updateParticle sec upd) (psParts sys) - liftIO $ G.gegl_node_link_many $ map particleStackCont x ++ [psNode sys] + x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) + when (not $ null x) $ do + -- liftIO $ traceIO "linking last node to output" + liftIO $ G.gegl_node_link (particleStackCont $ last x) (psNode sys) mapM_ (draw (psBuffer sys) (psNode sys)) x return $ sys { psParts = x } From 428ab736f82037adf5de7a4e34dda32759f5b159 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 25 Dec 2016 08:14:27 +0100 Subject: [PATCH 072/261] disabling example --- affection.cabal | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/affection.cabal b/affection.cabal index 1ff5b90..76b39b0 100644 --- a/affection.cabal +++ b/affection.cabal @@ -64,22 +64,22 @@ library , glib -- , sdl2-image -executable example00 - hs-source-dirs: examples - main-is: example00.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 example00 +-- hs-source-dirs: examples +-- main-is: example00.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 example01 -- hs-source-dirs: examples From 2095bb1924bb5002a42a564f3b731f06d8dc63dd Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 25 Dec 2016 08:14:51 +0100 Subject: [PATCH 073/261] fixed routing --- examples/example03.hs | 45 ++++++------ src/Affection.hs | 10 ++- src/Affection/Particle.hs | 146 +++++++++++++++++++++++++++----------- src/Affection/Types.hs | 19 +++-- 4 files changed, 149 insertions(+), 71 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index a50a4d3..53af30b 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -75,7 +75,7 @@ load _ = do return $ UserData { nodeGraph = myMap , foreground = buffer - , partsys = ParticleSystem [] nop buffer + , partsys = ParticleSystem (ParticleStorage Nothing []) nop buffer } drawInit :: Affection UserData () @@ -138,29 +138,30 @@ update sec = do liftIO $ G.gegl_node_connect_to tempRect "output" tempOver "aux" -- traceM $ "position is: " ++ show x ++ " " ++ show y -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy + ips <- insertParticle (partsys ud) $ + Particle + { particleTimeToLive = life + , particleCreation = elapsedTime ad + , particlePosition = (fromIntegral x, fromIntegral y) + , particleRotation = Rad 0 + , particleVelocity = (vx, vy) + , particlePitchRate = Rad 0 + , particleRootNode = tempRoot + , particleNodeGraph = M.fromList + [ ("root", tempRoot) + , ("over", tempOver) + , ("rect", tempRect) + ] + , particleStackCont = tempOver + , particleDrawFlange = tempOver + } putAffection $ ud - { partsys = (partsys ud) - { psParts = (Particle - { particleLife = life - , particlePosition = (fromIntegral x, fromIntegral y) - , particleRotation = Rad 0 - , particleVelocity = (vx, vy) - , particlePitchRate = Rad 0 - , particleRootNode = tempRoot - , particleNodeGraph = M.fromList - [ ("root", tempRoot) - , ("over", tempOver) - , ("rect" , tempRect) - ] - , particleStackCont = tempOver - , particleDrawFlange = tempOver - }) : (psParts $ partsys ud) - } + { partsys = ips } - when (not $ null $ psParts $ partsys ud) $ - liftIO $ G.gegl_node_link - tempOver - (particleStackCont $ head $ psParts $ partsys ud) + -- when (not $ null $ psParts $ partsys ud) $ + -- liftIO $ G.gegl_node_link + -- tempOver + -- (particleStackCont $ head $ psParts $ partsys ud) else return () SDL.WindowClosedEvent _ -> do diff --git a/src/Affection.hs b/src/Affection.hs index 316f4db..343e16e 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -65,6 +65,7 @@ withAffection AffectionConfig{..} = do , drawSurface = surface , drawFormat = format , drawStack = [] + , elapsedTime = 0 }) <$> loadState surface (_, nState) <- runStateT ( A.runState $ do preLoop @@ -81,11 +82,14 @@ withAffection AffectionConfig{..} = do ad <- get -- clean draw requests from last run mapM_ (invalidateDrawRequest pixels stride cpp) $ drawStack ad + -- compute dt and update elapsedTime + let dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) put $ ad - { drawStack = [] } + { drawStack = [] + , elapsedTime = elapsedTime ad + dt + } -- execute user defined update loop - updateLoop $ (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / - (fromIntegral 10 ^ 9) + updateLoop dt -- execute user defined draw loop drawLoop -- handle all new draw requests diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index c1a53fc..311048f 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -5,11 +5,13 @@ module Affection.Particle ( updateParticle , drawParticles , updateParticleSystem + , insertParticle ) where import Affection.Types import Control.Monad +import Control.Monad.State (get) import Data.Maybe @@ -27,48 +29,67 @@ updateParticle -- ^ Update function for a single 'Particle' -- This Function should take the elapsed time -- in seconds and the initial particle as arguments. - -> [Maybe Particle] + -- -> [Maybe Particle] -> Particle -- ^ 'Particle' to be processed - -> Affection us [Maybe Particle] + -- -> Affection us [Maybe Particle] + -> Affection us (Maybe Particle) -- ^ resulting 'Particle' -updateParticle time funct acc@[] pa = - if particleLife pa - time < 0 +updateParticle time funct pa = do + now <- elapsedTime <$> get + if particleCreation pa + particleTimeToLive pa < now then do + mproducer <- liftIO $ G.gegl_node_get_producer + (particleStackCont pa) + "input" + case mproducer of + Just (producer, padname) -> do + consumers <- liftIO $ G.gegl_node_get_consumers + (particleStackCont pa) + "output" + liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to + producer + padname + node + inpad + ) consumers + Nothing -> return () liftIO $ G.gegl_node_drop $ particleRootNode pa - return $ Nothing : acc + return $ Nothing else do - np <- Just <$> funct time pa { particleLife = particleLife pa - time } - return $ np : acc -updateParticle time funct acc@[p] pa = - if particleLife pa - time < 0 - then do - liftIO $ G.gegl_node_drop $ particleRootNode pa - return $ Nothing : acc - else do - when (not $ isNothing p) $ do - -- liftIO $ traceIO "linking second node in list" - liftIO $ G.gegl_node_link - (particleStackCont pa) - (particleStackCont $ fromJust p) - np <- Just <$> funct time pa { particleLife = particleLife pa - time } - return $ np : acc -updateParticle time funct acc@(p:ps) pa = - if particleLife pa - time < 0 - then do - liftIO $ G.gegl_node_drop $ particleRootNode pa - return $ Nothing : acc - else do - when (isNothing p) $ do - let mnl = nextLiving ps - maybe - (return ()) - (\nl -> do - -- liftIO $ traceIO "linking nth node on list" - liftIO $ G.gegl_node_link (particleStackCont pa) (particleStackCont nl)) - mnl - np <- Just <$> funct time pa { particleLife = particleLife pa - time } - return $ np : acc + np <- Just <$> funct time pa + return $ np +-- updateParticle time funct acc@[p] pa = do +-- now <- elapsedTime <$> get +-- if particleCreation pa + particleTimeToLive pa > now +-- then do +-- liftIO $ G.gegl_node_drop $ particleRootNode pa +-- return $ Nothing : acc +-- else do +-- when (not $ isNothing p) $ do +-- -- liftIO $ traceIO "linking second node in list" +-- liftIO $ G.gegl_node_link +-- (particleStackCont pa) +-- (particleStackCont $ fromJust p) +-- np <- Just <$> funct time pa +-- return $ np : acc +-- updateParticle time funct acc@(p:ps) pa = do +-- now <- elapsedTime <$> get +-- if particleCreation pa + particleTimeToLive pa > now +-- then do +-- liftIO $ G.gegl_node_drop $ particleRootNode pa +-- return $ Nothing : acc +-- else do +-- when (isNothing p) $ do +-- let mnl = nextLiving ps +-- maybe +-- (return ()) +-- (\nl -> do +-- -- liftIO $ traceIO "linking nth node on list" +-- liftIO $ G.gegl_node_link (particleStackCont pa) (particleStackCont nl)) +-- mnl +-- np <- Just <$> funct time pa +-- return $ np : acc -- | Get the next living particle from a list nextLiving @@ -95,12 +116,53 @@ updateParticleSystem -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) -> Affection us ParticleSystem updateParticleSystem sys sec upd draw = do - -- let x = catMaybes <$> mapM (updateParticle sec upd) (psParts sys) - -- x <- liftIO $ catMaybes <$> foldM (updateParticle sec upd) [] (psParts sys) - x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) + x <- catMaybes <$> mapM (updateParticle sec upd) (partStorList $ partSysParts sys) + -- x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) when (not $ null x) $ do -- liftIO $ traceIO "linking last node to output" - liftIO $ G.gegl_node_link (particleStackCont $ last x) (psNode sys) - mapM_ (draw (psBuffer sys) (psNode sys)) x + liftIO $ G.gegl_node_link (particleStackCont $ last x) (partSysNode sys) + mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x return $ sys - { psParts = x } + { partSysParts = (partSysParts sys) + { partStorList = x + , partStorLatest = + if null x + then Nothing + else partStorLatest (partSysParts sys) + } + } + +-- | Function for inserting a new 'Particle' into its 'PartileSystem' +insertParticle + :: ParticleSystem -- ^ 'ParticleSystem' to insert into + -> Particle -- ^ 'Particle' to insert + -> Affection us ParticleSystem -- ^ resulting new 'ParticleSystem' +insertParticle ps p = do + now <- elapsedTime <$> get + let newList = chronoInsert now (partStorList $ partSysParts ps) p + when (not $ isNothing $ partStorLatest $ partSysParts ps) $ + liftIO $ G.gegl_node_link + (particleStackCont p) + (particleStackCont $ fromJust $ partStorLatest $ partSysParts ps) + return $ ps + { partSysParts = (partSysParts ps) + { partStorLatest = Just p + , partStorList = newList + } + } + +-- | Insert a 'Particle' into its chronologically ordered list +chronoInsert + :: Double -- ^ Time in seconds from beginning of program execution + -> [Particle] -- ^ List to insert to + -> Particle -- ^ 'Particle' to insert + -> [Particle] -- ^ Resulting list +chronoInsert now [] np = [np] +chronoInsert now [p] np = + if now + particleTimeToLive p < particleCreation np + then p : [np] + else np : [p] +chronoInsert now l@(p:ps) np = + if now + particleTimeToLive p < particleCreation np + then p : chronoInsert now ps np + else np : l diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 9d43022..c197961 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -19,6 +19,7 @@ module Affection.Types -- | Particle system , Particle(..) , ParticleSystem(..) + , ParticleStorage(..) -- | Convenience exports , liftIO , SDL.WindowConfig(..) @@ -77,6 +78,7 @@ data AffectionData us = AffectionData , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed , clearStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be invalidated + , elapsedTime :: Double -- ^ Elapsed time in seconds } -- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated @@ -165,8 +167,10 @@ instance Eq Angle where -- | A single particle data Particle = Particle - { particleLife :: Double + { 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 @@ -185,8 +189,15 @@ data Particle = Particle -- ^ 'G.GeglNode' to connect draw actions to } +-- | The particle system data ParticleSystem = ParticleSystem - { psParts :: [Particle] - , psNode :: G.GeglNode - , psBuffer :: G.GeglBuffer + { 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 } From ebc0eea6c76ba793cc5ff045e18a62e7d0fd4ddb Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 26 Dec 2016 14:06:51 +0100 Subject: [PATCH 074/261] make previous examples work again --- affection.cabal | 64 ++++++++++++++-------------- examples/example00.hs | 20 +++------ examples/example01.hs | 98 ++++++++----------------------------------- examples/example03.hs | 2 +- 4 files changed, 57 insertions(+), 127 deletions(-) diff --git a/affection.cabal b/affection.cabal index 76b39b0..e88248e 100644 --- a/affection.cabal +++ b/affection.cabal @@ -64,39 +64,39 @@ library , glib -- , sdl2-image --- executable example00 --- hs-source-dirs: examples --- main-is: example00.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 example00 + hs-source-dirs: examples + main-is: example00.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 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 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 diff --git a/examples/example00.hs b/examples/example00.hs index 84b370d..f2569af 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -7,6 +7,8 @@ import qualified GEGL as G import qualified BABL as B import qualified Data.Map.Strict as M +import Control.Monad (when) + import Foreign.Storable (peek) import Foreign.C.Types (CInt(..)) @@ -36,7 +38,6 @@ main = do data UserData = UserData { nodeGraph :: M.Map String G.GeglNode - , elapsedTime :: Double } load :: SDL.Surface -> IO UserData @@ -69,7 +70,6 @@ load _ = do traceM "loading complete" return $ UserData { nodeGraph = myMap - , elapsedTime = 0 } draw :: Affection UserData () @@ -99,18 +99,10 @@ draw = do update :: Double -> Affection UserData () update sec = do traceM "updating" - -- liftIO $ delaySec 5 - ad@AffectionData{..} <- get - let ud@UserData{..} = userState - traceM $ show elapsedTime - if elapsedTime < 5 - then - put $ ad - { userState = ud - { elapsedTime = elapsedTime + sec - } - } - else + ad <- get + ud@UserData{..} <- getAffection + traceM $ (show $ 1 / sec) ++ " FPS" + when (elapsedTime ad > 5) $ put $ ad { quitEvent = True } diff --git a/examples/example01.hs b/examples/example01.hs index bb61e2b..89675e5 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -7,6 +7,8 @@ import qualified GEGL as G import qualified BABL as B import qualified Data.Map.Strict as M +import Control.Monad (when) + import Foreign.Storable (peek) import Foreign.C.Types @@ -37,7 +39,6 @@ main = do data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer - , elapsedTime :: Double } load :: SDL.Surface -> IO UserData @@ -54,111 +55,48 @@ load _ = do traceM "over" buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - bufsrc <- G.gegl_node_new_child root $ G.bufferSourceOperation + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" [ G.Property "buffer" $ G.PropertyBuffer buffer ] traceM "buffer-source" - G.gegl_node_link checkerboard over - G.gegl_node_connect_to bufsrc "output" over "aux" + nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] + traceM "nop" + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" + [ G.Property "width" $ G.PropertyDouble 800 + , G.Property "height" $ G.PropertyDouble 600 + ] + G.gegl_node_link_many [checkerboard, over, crop, sink] + G.gegl_node_connect_to nop "output" over "aux" traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) , ("over" , over) , ("background" , checkerboard) - , ("foreground" , bufsrc) + , ("sink" , sink) + , ("nop" , nop) ] let roi = G.GeglRectangle 0 0 20 20 - -- G.iterateOver - -- buffer - -- roi - -- (B.PixelFormat B.RGBA B.CFfloat) - -- G.GeglAccessReadWrite - -- G.GeglAbyssNone - -- (\(G.Pixel px py pc) -> - -- let dsqr = (((10 - px) ^ 2) + ((10 - py) ^ 2)) - -- (G.CVfloat (CFloat pr), G.CVfloat (CFloat pg), G.CVfloat (CFloat pb), G.CVfloat (CFloat pa)) = pc - -- dist = (sqrt (fromIntegral dsqr :: Float)) - -- in if dsqr < 100 - -- then - -- if dist < fromIntegral 9 - -- then - -- G.Pixel px py - -- ( G.CVfloat $ CFloat 0 - -- , G.CVfloat $ CFloat 0 - -- , G.CVfloat $ CFloat 0 - -- , G.CVfloat $ CFloat $ if pa < 1 then 1 else pa - -- ) - -- else - -- let alpha = fromIntegral 10 - dist - -- dst_a = pa - -- a = alpha + dst_a * (1 - alpha) - -- a_term = dst_a * (1 - alpha) - -- red = 0 * alpha + pr * a_term - -- gre = 0 * alpha + pg * a_term - -- blu = 0 * alpha + pb * a_term - -- in - -- G.Pixel px py - -- ( G.CVfloat $ CFloat $ red / a - -- , G.CVfloat $ CFloat $ gre / a - -- , G.CVfloat $ CFloat $ blu / a - -- , G.CVfloat $ CFloat $ if pa < alpha then alpha else pa - -- ) - -- else - -- G.Pixel px py pc - -- ) traceM "loading complete" return $ UserData { nodeGraph = myMap , foreground = buffer - , elapsedTime = 0 } draw :: Affection UserData () draw = do traceM "drawing" UserData{..} <- getAffection - -- AffectionData{..} <- get - -- let UserData{..} = userState - -- -- liftIO $ SDL.lockSurface drawSurface - -- pixels <- liftIO $ SDL.surfacePixels drawSurface - -- let SDL.Surface rawSurfacePtr _ = drawSurface - -- rawSurface <- liftIO $ peek rawSurfacePtr - -- pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface - -- format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) - -- SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface - -- let (w, h) = (fromIntegral rw, fromIntegral rh) - drawRect foreground (nodeGraph M.! "over") (G.RGB 1 0 0) (Line 2) (G.GeglRectangle 10 10 500 500) - -- liftIO $ G.gegl_node_blit - -- (nodeGraph M.! "over" :: G.GeglNode) - -- 1 - -- (G.GeglRectangle 0 0 w h) - -- format - -- pixels - -- (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) - -- [G.GeglBlitDefault] - -- liftIO $ SDL.unlockSurface drawSurface - -- liftIO $ SDL.updateWindowSurface drawWindow + drawRect' (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground + liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" update :: Double -> AffectionState (AffectionData UserData) IO () update sec = do traceM "updating" -- liftIO $ delaySec 5 - ad@AffectionData{..} <- get - let ud@UserData{..} = userState + ad <- get + ud@UserData{..} <- getAffection traceM $ (show $ 1 / sec) ++ " FPS" - if elapsedTime < 20 - then do - fg <- liftIO $ G.gegl_buffer_get_extent foreground - _ <- liftIO $ G.gegl_buffer_set_extent foreground $ fg - { G.rectangleX = G.rectangleX fg + 1 - , G.rectangleY = G.rectangleY fg + 1 - } - put $ ad - { userState = ud - { elapsedTime = elapsedTime + sec - } - } - else + when (elapsedTime ad > 20) $ put $ ad { quitEvent = True } diff --git a/examples/example03.hs b/examples/example03.hs index 53af30b..8121058 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -53,7 +53,7 @@ load _ = do [G.Property "buffer" $ G.PropertyBuffer buffer ] traceM "buffer-sink" - nop <- G.gegl_node_new_child root $ G.Operation "nop" [] + nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] traceM "nop" crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" [ G.Property "width" $ G.PropertyDouble 800 From 7837a14456d4ee6f5c44fb6197e13ab814bad51b Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 26 Dec 2016 14:14:34 +0100 Subject: [PATCH 075/261] disable example04 --- affection.cabal | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/affection.cabal b/affection.cabal index e88248e..6b7c526 100644 --- a/affection.cabal +++ b/affection.cabal @@ -133,20 +133,20 @@ executable example03 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 - 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 +-- else +-- buildable: False From 10c889af56841a32a5d0fdb8af06704ca846fbd5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 26 Dec 2016 14:14:54 +0100 Subject: [PATCH 076/261] rename drawRect' to drawRect and clean Affection.Draw --- examples/example01.hs | 2 +- examples/example02.hs | 2 +- src/Affection/Draw.hs | 77 ++----------------------------------------- 3 files changed, 5 insertions(+), 76 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index 89675e5..d13abd1 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -86,7 +86,7 @@ draw :: Affection UserData () draw = do traceM "drawing" UserData{..} <- getAffection - drawRect' (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground + drawRect (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" update :: Double -> AffectionState (AffectionData UserData) IO () diff --git a/examples/example02.hs b/examples/example02.hs index dcd0142..00f538a 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -90,7 +90,7 @@ draw = do let (w, h) = (fromIntegral rw, fromIntegral rh) liftIO $ clearArea foreground (GeglRectangle 0 0 w h) maybe (return ()) (\(x, y) -> - drawRect' + drawRect (nodeGraph M.! "nop") (G.RGBA 1 0 0 0.5) (Fill) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 8826e10..557c5ab 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -2,9 +2,7 @@ -- | Module for drawing primitives module Affection.Draw - ( - -- drawRect - drawRect' + ( drawRect -- , clear , handleDrawRequest , invalidateDrawRequest @@ -30,53 +28,14 @@ import qualified GEGL as G import Debug.Trace --- | Draw a rectangle on target buffer --- drawRect --- :: G.GeglBuffer -- ^ Target buffer --- -> G.GeglNode -- ^ Target node --- -> G.Color -- ^ Color to draw in --- -> DrawType -- ^ Draw type --- -> GeglRectangle -- ^ Dimensions of Rectangle --- -> Affection us () --- drawRect buf node color (Fill) rect@G.GeglRectangle{..} = do --- liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ --- (\(x, y) -> --- let col = unsafeColorize color --- in --- G.Pixel x y col --- ) --- ad@AffectionData{..} <- get --- put $ ad --- { drawStack = (DrawRequest rect buf False) : drawStack --- } --- drawRect buf node color (Line size) rect@G.GeglRectangle{..} = do --- liftIO $ G.pixelPoke buf rect (B.PixelFormat B.RGBA B.CFdouble) G.GeglAccessReadWrite G.GeglAbyssNone $ --- (\(x, y) -> --- let col = unsafeColorize color --- in if not ((x >= rectangleX + size && x < rectangleX + rectangleWidth - size) && --- (y >= rectangleY + size && y < rectangleY + rectangleHeight - size)) --- then --- G.Pixel x y col --- else --- G.Pixel x y $ unsafeColorize $ G.RGBA 0 0 0 0 --- ) --- ad@AffectionData{..} <- get --- put $ ad --- { drawStack = (DrawRequest rect buf False) : drawStack --- } --- -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY rectangleWidth size) --- -- drawRect buf node color Fill (G.GeglRectangle rectangleX rectangleY size rectangleHeight) --- -- drawRect buf node color Fill (G.GeglRectangle (rectangleX + rectangleWidth - size) rectangleY size rectangleHeight) --- -- drawRect buf node color Fill (G.GeglRectangle rectangleX (rectangleY + rectangleHeight - size) rectangleWidth size) - -drawRect' +drawRect :: G.GeglNode -- ^ Target Node -> G.Color -- ^ Color to draw in -> DrawType -- ^ Draw type -> G.GeglRectangle -- ^ Dimensions of Rectangle -> G.GeglBuffer -- ^ Final Buffer -> Affection us () -drawRect' node color Fill rect@GeglRectangle{..} buf = do +drawRect node color Fill rect@GeglRectangle{..} buf = do ad <- get tempRoot <- liftIO $ G.gegl_node_new opNode <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation "gegl:rectangle" @@ -105,30 +64,6 @@ present rect buf kill = do { drawStack = (DrawRequest rect buf k) : drawStack ad } --- -- | force a blit of a specified area. Do not use often as it slows down the program --- present --- :: G.GeglNode -- ^ Node to blit --- -> G.GeglBuffer -- ^ BUffer to draw --- -> G.GeglRectangle -- ^ Area to blit --- -> Bool -- ^ Shall the drawing be cleared in the next run? --- -> Affection us () --- present node buf rect pers = do --- ad <- get --- put $ ad --- { drawStack = (DrawRequest rect buf pers) : drawStack ad --- } --- ad <- get --- format <- liftIO $ B.babl_format $ B.PixelFormat B.RGBA B.CFu8 --- liftIO $ SDL.lockSurface $ drawSurface ad --- liftIO $ G.gegl_node_blit --- rect --- format --- nullPtr --- 0 --- [G.GeglBlitDefault] --- liftIO $ SDL.unlockSurface $ drawSurface ad --- liftIO $ SDL.updateWindowSurface $ drawWindow ad - -- | function for handling 'DrawRequest's and updating the output handleDrawRequest :: Ptr a -- ^ Pixel buffer to blit to @@ -233,12 +168,6 @@ unsafeColorize col = , G.CVdouble $ CDouble $ a ) --- -- | Clear all data from a buffer --- clear --- :: G.GeglBuffer -- ^ Target buffer --- -> IO () --- clear buf = clearArea buf =<< G.gegl_rectangle_infinite_plane - -- | Clear a specified area of a buffer from all data clearArea :: G.GeglBuffer -- ^ Target buffer From 84b1a86f224e50a16efdc50d8755c89287195b65 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 26 Dec 2016 14:19:03 +0100 Subject: [PATCH 077/261] cleaning --- examples/example02.hs | 3 +-- examples/example03.hs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 00f538a..b9dd1fb 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -45,9 +45,8 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer@(G.GeglBuffer b) <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - bufPtr <- new b sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" [ G.Property "buffer" $ G.PropertyBuffer buffer ] diff --git a/examples/example03.hs b/examples/example03.hs index 8121058..821fbca 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -47,7 +47,7 @@ load _ = do traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer@(G.GeglBuffer b) <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" [G.Property "buffer" $ G.PropertyBuffer buffer From 18f61d37afb3f0f38f909fee86928b6bd2aeb7f7 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 29 Dec 2016 20:15:09 +0100 Subject: [PATCH 078/261] stabilizing --- src/Affection/Draw.hs | 2 +- src/Affection/Particle.hs | 31 ++++++++++++++++--------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 557c5ab..620297a 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -46,7 +46,7 @@ drawRect node color Fill rect@GeglRectangle{..} buf = do , G.Property "color" $ G.PropertyColor color ] diw <- liftIO $ G.gegl_node_connect_to opNode "output" node "input" - unless diw $ error "Affection.Draw.drawRect': connect failed" + unless diw $ error "Affection.Draw.drawRect: connect failed" put $ ad { drawStack = (DrawRequest rect buf (Kill (Just tempRoot))) : drawStack ad } diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 311048f..075e820 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -39,21 +39,21 @@ updateParticle time funct pa = do now <- elapsedTime <$> get if particleCreation pa + particleTimeToLive pa < now then do - mproducer <- liftIO $ G.gegl_node_get_producer - (particleStackCont pa) - "input" - case mproducer of - Just (producer, padname) -> do - consumers <- liftIO $ G.gegl_node_get_consumers - (particleStackCont pa) - "output" - liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to - producer - padname - node - inpad - ) consumers - Nothing -> return () + -- mproducer <- liftIO $ G.gegl_node_get_producer + -- (particleStackCont pa) + -- "input" + -- case mproducer of + -- Just (producer, padname) -> do + -- consumers <- liftIO $ G.gegl_node_get_consumers + -- (particleStackCont pa) + -- "output" + -- liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to + -- producer + -- padname + -- node + -- inpad + -- ) consumers + -- Nothing -> return () liftIO $ G.gegl_node_drop $ particleRootNode pa return $ Nothing else do @@ -119,6 +119,7 @@ updateParticleSystem sys sec upd draw = do x <- catMaybes <$> mapM (updateParticle sec upd) (partStorList $ partSysParts sys) -- x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) when (not $ null x) $ do + liftIO $ G.gegl_node_link_many $ map particleStackCont (partStorList $ partSysParts sys) -- liftIO $ traceIO "linking last node to output" liftIO $ G.gegl_node_link (particleStackCont $ last x) (partSysNode sys) mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x From 93d79d16af2a973436c8cb6100dfced9ffb4c9b6 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 31 Dec 2016 22:11:32 +0100 Subject: [PATCH 079/261] prepare for hackage --- README.md | 3 ++- affection.cabal | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2958e76..e86b62d 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,5 @@ A simple Game Engine in Haskell using SDL ## Disclaimer -This Engine is not stable yet. Things may change. horribly. I do not intend bumping versions for now. +This Engine is not stable yet. Things may change. horribly. +Even minor version bumps may break the API. diff --git a/affection.cabal b/affection.cabal index 6b7c526..bb0f82b 100644 --- a/affection.cabal +++ b/affection.cabal @@ -8,7 +8,7 @@ name: affection -- | | | +--- code changes with no API change version: 0.0.0.0 synopsis: A simple Game Engine using SDL --- description: +description: See homepage for description. homepage: https://github.com/nek0/affection#readme license: LGPL-3 license-file: LICENSE From 97186c5709182bc4b667211c7d1fd70fcdb7cb82 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 17 Feb 2017 17:15:06 +0100 Subject: [PATCH 080/261] it works only halfway --- affection.cabal | 11 ++++--- examples/example00.hs | 4 +-- examples/example01.hs | 4 +-- examples/example02.hs | 10 +++---- examples/example03.hs | 8 ++--- src/Affection.hs | 61 +++++++++++++++++++++++++++++++-------- src/Affection/Particle.hs | 47 +++++++++++++++--------------- src/Affection/Types.hs | 12 +++++--- 8 files changed, 101 insertions(+), 56 deletions(-) diff --git a/affection.cabal b/affection.cabal index bb0f82b..dd62652 100644 --- a/affection.cabal +++ b/affection.cabal @@ -6,9 +6,12 @@ name: affection -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.0.0.0 +version: 0.0.0.1 synopsis: A simple Game Engine using SDL -description: See homepage for description. +description: This package contains Affection, a simple game engine + written in Haskell using SDL and GEGL. + This Engine is still work in progress and even minor + version bumps may contain breaking api changes. homepage: https://github.com/nek0/affection#readme license: LGPL-3 license-file: LICENSE @@ -49,7 +52,7 @@ library , UndecidableInstances hs-source-dirs: src default-language: Haskell2010 - ghc-options: -Wall -debug + ghc-options: -Wall -- Other library packages from which modules are imported. build-depends: base >=4.9 && <4.10 , sdl2 @@ -118,7 +121,7 @@ executable example02 executable example03 hs-source-dirs: examples main-is: example03.hs - ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts -debug + ghc-options: -threaded -Wall -auto-all -caf-all -rtsopts default-language: Haskell2010 default-extensions: OverloadedStrings if flag(examples) diff --git a/examples/example00.hs b/examples/example00.hs index f2569af..9d4808f 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -96,8 +96,8 @@ draw = do liftIO $ SDL.unlockSurface drawSurface liftIO $ SDL.updateWindowSurface drawWindow -update :: Double -> Affection UserData () -update sec = do +update :: Double -> [SDL.Event] -> Affection UserData () +update sec _ = do traceM "updating" ad <- get ud@UserData{..} <- getAffection diff --git a/examples/example01.hs b/examples/example01.hs index d13abd1..10351d9 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -89,8 +89,8 @@ draw = do drawRect (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" -update :: Double -> AffectionState (AffectionData UserData) IO () -update sec = do +update :: Double -> [SDL.Event] -> Affection UserData () +update sec _ = do traceM "updating" -- liftIO $ delaySec 5 ad <- get diff --git a/examples/example02.hs b/examples/example02.hs index b9dd1fb..a884b7f 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -98,14 +98,14 @@ draw = do ) coordinates liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" -update :: Double -> AffectionState (AffectionData UserData) IO () -update sec = do +update :: Double -> [SDL.Event] -> Affection UserData () +update sec evs = do traceM "updating" ad <- get ud <- getAffection traceM $ (show $ 1 / sec) ++ " FPS" - ev <- liftIO $ SDL.pollEvent - maybe (return ()) (\e -> + -- ev <- liftIO $ SDL.pollEvent + mapM_ (\e -> case SDL.eventPayload e of SDL.MouseMotionEvent dat -> do let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat @@ -119,7 +119,7 @@ update sec = do } _ -> return () - ) ev + ) evs clean :: UserData -> IO () clean _ = return () diff --git a/examples/example03.hs b/examples/example03.hs index 821fbca..8e4f5ca 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -103,15 +103,15 @@ draw = do -- (G.GeglRectangle (x - 10) (y - 10) 20 20) -- ) $ coordinates ud -update :: Double -> AffectionState (AffectionData UserData) IO () -update sec = do +update :: Double -> [SDL.Event] -> Affection UserData () +update sec evs = do traceM "updating" ad <- get ud <- getAffection -- let newPart = updateParticles sec partUpd $ particles ud -- putAffection $ ud { particles = newPart } traceM $ (show $ 1 / sec) ++ " FPS" - ev <- liftIO $ SDL.pollEvents + -- ev <- liftIO $ SDL.pollEvents mapM_ (\e -> case SDL.eventPayload e of SDL.MouseMotionEvent dat -> @@ -171,7 +171,7 @@ update sec = do } _ -> return () - ) ev + ) evs ud2 <- getAffection nps <- updateParticleSystem (partsys ud2) sec partUpd partDraw putAffection $ ud2 { partsys = nps } diff --git a/src/Affection.hs b/src/Affection.hs index 343e16e..6621f7f 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -28,6 +28,8 @@ import Control.Concurrent.MVar import Foreign.C.Types (CInt(..)) import Foreign.Storable (peek) +import Debug.Trace + import Affection.Types as A import Affection.Draw as A import Affection.Particle as A @@ -47,21 +49,15 @@ withAffection AffectionConfig{..} = do G.gegl_init execTime <- newMVar =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig - oldSurf@(SDL.Surface ptr _) <- SDL.getWindowSurface window - rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 - let surface = (flip SDL.Surface Nothing) rawSurfacePtr - bablFormat = B.PixelFormat B.RGBA B.CFu8 - pixels <- SDL.surfacePixels surface + -- let surface = (flip SDL.Surface Nothing) rawSurfacePtr + (oldSurf, surface) <- getSurfaces window + let bablFormat = B.PixelFormat B.RGBA B.CFu8 format <- B.babl_format bablFormat - SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions surface - pixelFormat <- peek . Raw.surfaceFormat =<< peek rawSurfacePtr - let (w, h) = (fromIntegral rw, fromIntegral rh) - stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w - cpp = B.babl_components_per_pixel bablFormat initContainer <- (\x -> AffectionData { quitEvent = False , userState = x , drawWindow = window + , windowSurface = oldSurf , drawSurface = surface , drawFormat = format , drawStack = [] @@ -81,6 +77,14 @@ withAffection AffectionConfig{..} = do -- get state ad <- get -- clean draw requests from last run + pixels <- SDL.surfacePixels $ drawSurface ad + (SDL.Surface ptr _) <- SDL.getWindowSurface window + rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 + pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr + SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface + let (w, h) = (fromIntegral rw, fromIntegral rh) + stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w + cpp = B.babl_components_per_pixel bablFormat mapM_ (invalidateDrawRequest pixels stride cpp) $ drawStack ad -- compute dt and update elapsedTime let dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) @@ -88,8 +92,10 @@ withAffection AffectionConfig{..} = do { drawStack = [] , elapsedTime = elapsedTime ad + dt } + -- poll events + evs <- preHandleEvents =<< liftIO SDL.pollEvents -- execute user defined update loop - updateLoop dt + updateLoop dt evs -- execute user defined draw loop drawLoop -- handle all new draw requests @@ -99,7 +105,11 @@ withAffection AffectionConfig{..} = do put $ ad2 { drawStack = clear } -- blit surface and update window - liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing + liftIO $ SDL.surfaceBlit + (drawSurface ad2) + Nothing + (windowSurface ad2) + Nothing liftIO $ SDL.updateWindowSurface $ drawWindow ad2 -- save new time _ <- liftIO $ swapMVar execTime $ now @@ -110,6 +120,33 @@ withAffection AffectionConfig{..} = do cleanUp $ userState nState SDL.quit +getSurfaces :: SDL.Window -> IO (SDL.Surface, SDL.Surface) +getSurfaces window = do + oldSurf@(SDL.Surface ptr _) <- SDL.getWindowSurface window + rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 + let surface = (flip SDL.Surface Nothing) rawSurfacePtr + return (oldSurf, surface) + +preHandleEvents :: [SDL.Event] -> Affection us [SDL.Event] +preHandleEvents evs = + catMaybes <$> mapM handle evs + where + handle e = + case SDL.eventPayload e of + SDL.WindowMovedEvent _ -> do + liftIO $ traceIO "I was moved" + putNewSurface + return Nothing + _ -> + return $ Just e + putNewSurface = do + ad <- get + (oldSurf, surface) <- liftIO $ getSurfaces $ drawWindow ad + put ad + { windowSurface = oldSurf + , drawSurface = surface + } + -- | Return the userstate to the user getAffection :: Affection us us getAffection = do diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 075e820..b6b92b0 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -39,21 +39,21 @@ updateParticle time funct pa = do now <- elapsedTime <$> get if particleCreation pa + particleTimeToLive pa < now then do - -- mproducer <- liftIO $ G.gegl_node_get_producer - -- (particleStackCont pa) - -- "input" - -- case mproducer of - -- Just (producer, padname) -> do - -- consumers <- liftIO $ G.gegl_node_get_consumers - -- (particleStackCont pa) - -- "output" - -- liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to - -- producer - -- padname - -- node - -- inpad - -- ) consumers - -- Nothing -> return () + mproducer <- liftIO $ G.gegl_node_get_producer + (particleStackCont pa) + "input" + case mproducer of + Just (producer, padname) -> do + consumers <- liftIO $ G.gegl_node_get_consumers + (particleStackCont pa) + "output" + liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to + producer + padname + node + inpad + ) consumers + Nothing -> return () liftIO $ G.gegl_node_drop $ particleRootNode pa return $ Nothing else do @@ -119,9 +119,9 @@ updateParticleSystem sys sec upd draw = do x <- catMaybes <$> mapM (updateParticle sec upd) (partStorList $ partSysParts sys) -- x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) when (not $ null x) $ do - liftIO $ G.gegl_node_link_many $ map particleStackCont (partStorList $ partSysParts sys) + -- liftIO $ G.gegl_node_link_many $ map particleStackCont (partStorList $ partSysParts sys) -- liftIO $ traceIO "linking last node to output" - liftIO $ G.gegl_node_link (particleStackCont $ last x) (partSysNode sys) + liftIO $ G.gegl_node_link (particleStackCont $ head x) (partSysNode sys) mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x return $ sys { partSysParts = (partSysParts sys) @@ -141,10 +141,11 @@ insertParticle insertParticle ps p = do now <- elapsedTime <$> get let newList = chronoInsert now (partStorList $ partSysParts ps) p - when (not $ isNothing $ partStorLatest $ partSysParts ps) $ - liftIO $ G.gegl_node_link - (particleStackCont p) - (particleStackCont $ fromJust $ partStorLatest $ partSysParts ps) + liftIO $ G.gegl_node_link_many (reverse $ map particleStackCont newList) + -- when (not $ isNothing $ partStorLatest $ partSysParts ps) $ + -- liftIO $ G.gegl_node_link + -- (particleStackCont p) + -- (particleStackCont $ fromJust $ partStorLatest $ partSysParts ps) return $ ps { partSysParts = (partSysParts ps) { partStorLatest = Just p @@ -160,10 +161,10 @@ chronoInsert -> [Particle] -- ^ Resulting list chronoInsert now [] np = [np] chronoInsert now [p] np = - if now + particleTimeToLive p < particleCreation np + if now + particleTimeToLive p < (particleCreation np + particleTimeToLive np) then p : [np] else np : [p] chronoInsert now l@(p:ps) np = - if now + particleTimeToLive p < particleCreation np + if now + particleTimeToLive p < (particleCreation np + particleTimeToLive np) then p : chronoInsert now ps np else np : l diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index c197961..5f8bfdb 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -31,6 +31,7 @@ module Affection.Types 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 @@ -55,7 +56,7 @@ data AffectionConfig us = AffectionConfig -- ^ Actions to be performed, before loop starts , drawLoop :: Affection us () -- ^ Function for updating graphics. - , updateLoop :: Double -> Affection us () + , updateLoop :: Double -> [SDL.Event] -> Affection us () -- ^ Main update function. Takes fractions of a second as input. , loadState :: SDL.Surface -> IO us -- ^ Provide your own load function to create this data. @@ -74,6 +75,7 @@ data AffectionData us = AffectionData { quitEvent :: Bool -- ^ Loop breaker. , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window + , windowSurface :: SDL.Surface -- ^ Internal surface of window , drawSurface :: SDL.Surface -- ^ SDL surface , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed @@ -136,8 +138,10 @@ data RGBA = RGBA -- | Type for defining the draw type of draw functions data DrawType - = Fill -- ^ Fill the specified area completely with color - | Line -- ^ only draw the outline of the area + -- | Fill the specified area completely with color + = Fill + -- | only draw the outline of the area + | Line { lineWidth :: Int -- ^ Width of line in pixels } @@ -187,7 +191,7 @@ data Particle = Particle -- ^ '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 From 79f3bd4b9f2f481c24daba4d56413b0c58094871 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 19 Feb 2017 22:28:10 +0100 Subject: [PATCH 081/261] mitigating memory leak, changing to IORef and making widnow movable --- src/Affection.hs | 51 +++++++++++++++++++++++++++--------------- src/Affection/Types.hs | 6 +++++ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 6621f7f..ce35237 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -18,12 +18,12 @@ import qualified GEGL as G import qualified Data.Text as T import Data.Maybe +import Data.IORef import System.Clock import Control.Monad.Loops import Control.Monad.State -import Control.Concurrent.MVar import Foreign.C.Types (CInt(..)) import Foreign.Storable (peek) @@ -47,11 +47,19 @@ withAffection AffectionConfig{..} = do Only is -> SDL.initialize is G.gegl_init - execTime <- newMVar =<< getTime Monotonic + execTime <- newIORef =<< getTime Monotonic window <- SDL.createWindow windowTitle windowConfig -- let surface = (flip SDL.Surface Nothing) rawSurfacePtr (oldSurf, surface) <- getSurfaces window + let (SDL.Surface ptr _) = surface + rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 + pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr + SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface + let (w, h) = (fromIntegral rw, fromIntegral rh) + stride = (fromIntegral $ Raw.pixelFormatBytesPerPixel pixelFormat) * w + pixels <- SDL.surfacePixels $ surface let bablFormat = B.PixelFormat B.RGBA B.CFu8 + cpp = B.babl_components_per_pixel bablFormat format <- B.babl_format bablFormat initContainer <- (\x -> AffectionData { quitEvent = False @@ -60,12 +68,17 @@ withAffection AffectionConfig{..} = do , windowSurface = oldSurf , drawSurface = surface , drawFormat = format + , drawPixels = pixels + , drawDimensions = (w, h) + , drawStride = stride + , drawCPP = cpp , drawStack = [] , elapsedTime = 0 }) <$> loadState surface (_, nState) <- runStateT ( A.runState $ do preLoop - liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing + pad <- get + liftIO $ SDL.surfaceBlit (drawSurface pad) Nothing (windowSurface pad) Nothing whileM_ (do current <- get return $ not $ A.quitEvent current @@ -73,19 +86,11 @@ withAffection AffectionConfig{..} = do (do -- Measure time difference form last run now <- liftIO $ getTime Monotonic - lastTime <- liftIO $ fromMaybe now <$> tryReadMVar execTime + lastTime <- liftIO $ readIORef execTime -- get state ad <- get -- clean draw requests from last run - pixels <- SDL.surfacePixels $ drawSurface ad - (SDL.Surface ptr _) <- SDL.getWindowSurface window - rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 - pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr - SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface - let (w, h) = (fromIntegral rw, fromIntegral rh) - stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w - cpp = B.babl_components_per_pixel bablFormat - mapM_ (invalidateDrawRequest pixels stride cpp) $ drawStack ad + mapM_ (invalidateDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad) -- compute dt and update elapsedTime let dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) put $ ad @@ -100,7 +105,8 @@ withAffection AffectionConfig{..} = do drawLoop -- handle all new draw requests ad2 <- get - clear <- catMaybes <$> mapM (handleDrawRequest pixels stride cpp) (drawStack ad2) + clear <- catMaybes <$> + mapM (handleDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad2) -- save all draw requests to clear in next run put $ ad2 { drawStack = clear } @@ -112,8 +118,7 @@ withAffection AffectionConfig{..} = do Nothing liftIO $ SDL.updateWindowSurface $ drawWindow ad2 -- save new time - _ <- liftIO $ swapMVar execTime $ now - return () + liftIO $ writeIORef execTime $ now ) ) initContainer G.gegl_exit @@ -142,9 +147,19 @@ preHandleEvents evs = putNewSurface = do ad <- get (oldSurf, surface) <- liftIO $ getSurfaces $ drawWindow ad + pixels <- SDL.surfacePixels $ surface + SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface + let (SDL.Surface ptr _) = surface + rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 + pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr + let (w, h) = (fromIntegral rw, fromIntegral rh) + stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w put ad - { windowSurface = oldSurf - , drawSurface = surface + { windowSurface = oldSurf + , drawSurface = surface + , drawPixels = pixels + , drawDimensions = (w, h) + , drawStride = stride } -- | Return the userstate to the user diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 5f8bfdb..fc6ff6b 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -44,6 +44,8 @@ import Control.Monad.State -- import Control.Concurrent.MVar +import Foreign.Ptr (Ptr) + -- | Configuration for the aplication. needed at startup. data AffectionConfig us = AffectionConfig { initComponents :: InitComponents @@ -79,6 +81,10 @@ data AffectionData us = AffectionData , drawSurface :: SDL.Surface -- ^ SDL surface , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed + , drawPixels :: Ptr () -- ^ Destination Pixel buffer + , drawDimensions :: (Int, Int) -- ^ Dimensions of target surface + , drawStride :: Int -- ^ Stride of target buffer + , drawCPP :: Int -- ^ Number of components per pixel , clearStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be invalidated , elapsedTime :: Double -- ^ Elapsed time in seconds } From 2869e4b0d4684b9e9ce76d48eeed3e3cae10f952 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 19 Feb 2017 22:28:49 +0100 Subject: [PATCH 082/261] version bump --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index dd62652..62ca11f 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.1 +version: 0.0.0.2 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL and GEGL. From cb52296c8f02255ac71178288d222db64dafefb2 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Feb 2017 20:22:01 +0100 Subject: [PATCH 083/261] some cleaning --- src/Affection.hs | 7 +++---- src/Affection/Types.hs | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index ce35237..95407df 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -16,7 +16,6 @@ import qualified SDL.Internal.Numbered as SDL (toNumber) import qualified SDL.Raw as Raw import qualified GEGL as G -import qualified Data.Text as T import Data.Maybe import Data.IORef @@ -134,16 +133,16 @@ getSurfaces window = do preHandleEvents :: [SDL.Event] -> Affection us [SDL.Event] preHandleEvents evs = - catMaybes <$> mapM handle evs + mapM handle evs where handle e = case SDL.eventPayload e of SDL.WindowMovedEvent _ -> do liftIO $ traceIO "I was moved" putNewSurface - return Nothing + return e _ -> - return $ Just e + return e putNewSurface = do ad <- get (oldSurf, surface) <- liftIO $ getSurfaces $ drawWindow ad diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index fc6ff6b..7ac003d 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -85,7 +85,6 @@ data AffectionData us = AffectionData , drawDimensions :: (Int, Int) -- ^ Dimensions of target surface , drawStride :: Int -- ^ Stride of target buffer , drawCPP :: Int -- ^ Number of components per pixel - , clearStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be invalidated , elapsedTime :: Double -- ^ Elapsed time in seconds } From 70ead790d1e4a499b141aa98806c1293b6cf388f Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Feb 2017 20:43:15 +0100 Subject: [PATCH 084/261] ignore profiling data --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 3d66116..ced8b76 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ .cabal-sandbox/ cabal.sandbox.config dist/ +*.prof +*.aux +*.hp +*.ps From b2f3355bd685b52f2d2d9088fa26956d8304d65b Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Feb 2017 20:43:28 +0100 Subject: [PATCH 085/261] version bump --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index 62ca11f..7868f38 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.2 +version: 0.0.0.3 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL and GEGL. From 1846ae051a335a6b1dca5b4ebc5ff7bec46b31c3 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Feb 2017 23:17:26 +0100 Subject: [PATCH 086/261] removing zombie dependency --- affection.cabal | 1 - 1 file changed, 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index 7868f38..cd72b7e 100644 --- a/affection.cabal +++ b/affection.cabal @@ -57,7 +57,6 @@ library build-depends: base >=4.9 && <4.10 , sdl2 , text - , linear , mtl , gegl , babl From a028bb420450a6ef06d3943124f17186dab9f135 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 22 Feb 2017 18:02:34 +0100 Subject: [PATCH 087/261] thanks @dodo. Now it works more smoothly --- src/Affection.hs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 95407df..00a2837 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -131,6 +131,7 @@ getSurfaces window = do let surface = (flip SDL.Surface Nothing) rawSurfacePtr return (oldSurf, surface) +-- Prehandle SDL events in case any window events occur preHandleEvents :: [SDL.Event] -> Affection us [SDL.Event] preHandleEvents evs = mapM handle evs @@ -145,20 +146,9 @@ preHandleEvents evs = return e putNewSurface = do ad <- get - (oldSurf, surface) <- liftIO $ getSurfaces $ drawWindow ad - pixels <- SDL.surfacePixels $ surface - SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface - let (SDL.Surface ptr _) = surface - rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 - pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr - let (w, h) = (fromIntegral rw, fromIntegral rh) - stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w + (oldSurf, _) <- liftIO $ getSurfaces $ drawWindow ad put ad { windowSurface = oldSurf - , drawSurface = surface - , drawPixels = pixels - , drawDimensions = (w, h) - , drawStride = stride } -- | Return the userstate to the user From 379a5711479a3a4de9e9288c65df580d2d15ba90 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 22 Feb 2017 18:03:16 +0100 Subject: [PATCH 088/261] a little optimization --- src/Affection/Particle.hs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index b6b92b0..9c4b85a 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -117,21 +117,25 @@ updateParticleSystem -> Affection us ParticleSystem updateParticleSystem sys sec upd draw = do x <- catMaybes <$> mapM (updateParticle sec upd) (partStorList $ partSysParts sys) + -- liftIO $ traceIO $ show $ length x -- x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) - when (not $ null x) $ do - -- liftIO $ G.gegl_node_link_many $ map particleStackCont (partStorList $ partSysParts sys) - -- liftIO $ traceIO "linking last node to output" - liftIO $ G.gegl_node_link (particleStackCont $ head x) (partSysNode sys) - mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x - return $ sys - { partSysParts = (partSysParts sys) - { partStorList = x - , partStorLatest = - if null x - then Nothing - else partStorLatest (partSysParts sys) - } - } + if (not $ null x) + then do + -- liftIO $ G.gegl_node_link_many $ map particleStackCont (partStorList $ partSysParts sys) + -- liftIO $ traceIO "linking last node to output" + liftIO $ G.gegl_node_link (particleStackCont $ head x) (partSysNode sys) + mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x + return $ sys + { partSysParts = (partSysParts sys) + { partStorList = x + , partStorLatest = + if null x + then Nothing + else partStorLatest (partSysParts sys) + } + } + else + return sys -- | Function for inserting a new 'Particle' into its 'PartileSystem' insertParticle From 31d48f021ae5a50e6ea225e6cb3270b75afdbfd4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Feb 2017 22:54:26 +0100 Subject: [PATCH 089/261] fixed memory leak by using renderer --- src/Affection.hs | 53 +++++++++++++++++++++++++----------------- src/Affection/Types.hs | 2 +- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 00a2837..7210c40 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -40,19 +40,36 @@ withAffection :: AffectionConfig us -- ^ Configuration of the Game and its engine. -> IO () withAffection AffectionConfig{..} = do + -- intialiaze SDL case initComponents of All -> SDL.initializeAll Only is -> SDL.initialize is G.gegl_init + -- give SDL render quality + SDL.HintRenderScaleQuality SDL.$= SDL.ScaleLinear + -- just checking… + do + renderQuality <- SDL.get SDL.HintRenderScaleQuality + when (renderQuality /= SDL.ScaleLinear) $ + putStrLn "Warning: Linear texture filtering not enabled!" + -- get current time execTime <- newIORef =<< getTime Monotonic + -- construct window window <- SDL.createWindow windowTitle windowConfig - -- let surface = (flip SDL.Surface Nothing) rawSurfacePtr - (oldSurf, surface) <- getSurfaces window + SDL.showWindow window + -- create renderer + renderer <- SDL.createRenderer + window + (-1) + SDL.defaultRenderer + -- make draw surface + surface <- SDL.createRGBSurface + (SDL.windowInitialSize windowConfig) + SDL.ABGR8888 let (SDL.Surface ptr _) = surface - rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 - pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek rawSurfacePtr + pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface let (w, h) = (fromIntegral rw, fromIntegral rh) stride = (fromIntegral $ Raw.pixelFormatBytesPerPixel pixelFormat) * w @@ -64,7 +81,7 @@ withAffection AffectionConfig{..} = do { quitEvent = False , userState = x , drawWindow = window - , windowSurface = oldSurf + , windowRenderer = renderer , drawSurface = surface , drawFormat = format , drawPixels = pixels @@ -76,8 +93,6 @@ withAffection AffectionConfig{..} = do }) <$> loadState surface (_, nState) <- runStateT ( A.runState $ do preLoop - pad <- get - liftIO $ SDL.surfaceBlit (drawSurface pad) Nothing (windowSurface pad) Nothing whileM_ (do current <- get return $ not $ A.quitEvent current @@ -90,6 +105,8 @@ withAffection AffectionConfig{..} = do ad <- get -- clean draw requests from last run mapM_ (invalidateDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad) + -- clean the renderer form last time + SDL.clear renderer -- compute dt and update elapsedTime let dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) put $ ad @@ -109,19 +126,20 @@ withAffection AffectionConfig{..} = do -- save all draw requests to clear in next run put $ ad2 { drawStack = clear } - -- blit surface and update window - liftIO $ SDL.surfaceBlit - (drawSurface ad2) - Nothing - (windowSurface ad2) - Nothing - liftIO $ SDL.updateWindowSurface $ drawWindow ad2 + -- get texture from surface + texture <- SDL.createTextureFromSurface (windowRenderer ad2) (drawSurface ad2) + -- actual drawing + SDL.copy (windowRenderer ad2) texture Nothing Nothing + SDL.present (windowRenderer ad2) + -- clean the texture + SDL.destroyTexture texture -- save new time liftIO $ writeIORef execTime $ now ) ) initContainer G.gegl_exit cleanUp $ userState nState + SDL.destroyWindow window SDL.quit getSurfaces :: SDL.Window -> IO (SDL.Surface, SDL.Surface) @@ -140,16 +158,9 @@ preHandleEvents evs = case SDL.eventPayload e of SDL.WindowMovedEvent _ -> do liftIO $ traceIO "I was moved" - putNewSurface return e _ -> return e - putNewSurface = do - ad <- get - (oldSurf, _) <- liftIO $ getSurfaces $ drawWindow ad - put ad - { windowSurface = oldSurf - } -- | Return the userstate to the user getAffection :: Affection us us diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 7ac003d..1bb4d24 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -77,7 +77,7 @@ data AffectionData us = AffectionData { quitEvent :: Bool -- ^ Loop breaker. , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window - , windowSurface :: SDL.Surface -- ^ Internal surface of window + , windowRenderer :: SDL.Renderer -- ^ Internal renderer of window , drawSurface :: SDL.Surface -- ^ SDL surface , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed From 3ac1049ec7486400e621218a18ed7894beafd6c1 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Feb 2017 23:28:00 +0100 Subject: [PATCH 090/261] version bump --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index cd72b7e..ba0d35d 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.3 +version: 0.0.0.4 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL and GEGL. From ea8fdf37eeb2c123a3aadff055e3807b34cd4138 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 24 Feb 2017 00:18:29 +0100 Subject: [PATCH 091/261] fixed little memory leak concerning float values --- src/Affection.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 7210c40..c989a9b 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE BangPatterns #-} module Affection ( withAffection , getAffection @@ -108,10 +109,11 @@ withAffection AffectionConfig{..} = do -- clean the renderer form last time SDL.clear renderer -- compute dt and update elapsedTime - let dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) + let !dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) + !ne = elapsedTime ad + dt put $ ad { drawStack = [] - , elapsedTime = elapsedTime ad + dt + , elapsedTime = ne } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents From 130d9417791d17c621b68871b41a5203e88e8248 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 24 Feb 2017 17:11:10 +0100 Subject: [PATCH 092/261] only drawing on available surface area --- src/Affection/Draw.hs | 62 ++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 620297a..c22aa8d 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -12,6 +12,8 @@ module Affection.Draw import Affection.Types +import Data.Maybe (maybe) + import Foreign import Foreign.C.Types @@ -75,18 +77,23 @@ handleDrawRequest handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do ad <- get let surf = drawSurface ad - liftIO $ SDL.lockSurface surf - liftIO $ G.gegl_buffer_get - requestBuffer - (Just requestArea) - 1 - (Just $ drawFormat ad) - (pixels `plusPtr` - (rectangleX requestArea * cpp + rectangleY requestArea * stride)) - stride - G.GeglAbyssNone - liftIO $ SDL.unlockSurface surf - -- liftIO $ SDL.updateWindowSurface $ drawWindow ad + mrealRect <- liftIO $ G.gegl_rectangle_intersect + requestArea + (G.GeglRectangle 0 0 (fst $ drawDimensions ad) (snd $ drawDimensions ad)) + maybe (return()) (\realRect -> do + liftIO $ SDL.lockSurface surf + liftIO $ G.gegl_buffer_get + requestBuffer + (Just realRect) + 1 + (Just $ drawFormat ad) + (pixels `plusPtr` + (rectangleX realRect * cpp + rectangleY realRect * stride)) + stride + G.GeglAbyssNone + liftIO $ SDL.unlockSurface surf + -- liftIO $ SDL.updateWindowSurface $ drawWindow ad + ) mrealRect case requestPersist of Yes -> return Nothing @@ -103,19 +110,24 @@ invalidateDrawRequest -> Affection us () invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do ad <- get - let surf = drawSurface ad - liftIO $ clearArea requestBuffer requestArea - liftIO $ SDL.lockSurface surf - liftIO $ G.gegl_buffer_get - requestBuffer - (Just requestArea) - 1 - (Just $ drawFormat ad) - (pixels `plusPtr` - (rectangleX requestArea * cpp + rectangleY requestArea * stride)) - stride - G.GeglAbyssNone - liftIO $ SDL.unlockSurface surf + mrealRect <- liftIO $ G.gegl_rectangle_intersect + requestArea + (G.GeglRectangle 0 0 (fst $ drawDimensions ad) (snd $ drawDimensions ad)) + maybe (return()) (\realRect -> do + let surf = drawSurface ad + liftIO $ clearArea requestBuffer realRect + liftIO $ SDL.lockSurface surf + liftIO $ G.gegl_buffer_get + requestBuffer + (Just realRect) + 1 + (Just $ drawFormat ad) + (pixels `plusPtr` + (rectangleX realRect * cpp + rectangleY realRect * stride)) + stride + G.GeglAbyssNone + liftIO $ SDL.unlockSurface surf + ) mrealRect case requestPersist of Kill (Just victim) -> liftIO $ G.gegl_node_drop victim From 896a64a6fc64e6de44758a24e1ca4ba20091aff4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 24 Feb 2017 17:11:52 +0100 Subject: [PATCH 093/261] just some comment alignment --- src/Affection/Types.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 1bb4d24..ecfd039 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -82,7 +82,7 @@ data AffectionData us = AffectionData , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed , drawPixels :: Ptr () -- ^ Destination Pixel buffer - , drawDimensions :: (Int, Int) -- ^ Dimensions of target surface + , 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 From 46f51d13349ec119486ca8b6e3f89dacdcea6e6c Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 25 Feb 2017 17:23:28 +0100 Subject: [PATCH 094/261] found the particle leak! --- src/Affection/Particle.hs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 9c4b85a..8743234 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE BangPatterns #-} -- | This module introduces a simple particle system to Affection module Affection.Particle @@ -116,7 +117,7 @@ updateParticleSystem -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) -> Affection us ParticleSystem updateParticleSystem sys sec upd draw = do - x <- catMaybes <$> mapM (updateParticle sec upd) (partStorList $ partSysParts sys) + !x <- catMaybes <$> mapM (updateParticle sec upd) (partStorList $ partSysParts sys) -- liftIO $ traceIO $ show $ length x -- x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) if (not $ null x) @@ -125,17 +126,19 @@ updateParticleSystem sys sec upd draw = do -- liftIO $ traceIO "linking last node to output" liftIO $ G.gegl_node_link (particleStackCont $ head x) (partSysNode sys) mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x - return $ sys + return sys { partSysParts = (partSysParts sys) { partStorList = x - , partStorLatest = - if null x - then Nothing - else partStorLatest (partSysParts sys) } } - else + else do + _ <- liftIO $ G.gegl_node_disconnect (partSysNode sys) "input" return sys + { partSysParts = ParticleStorage + { partStorList = [] + , partStorLatest = Nothing + } + } -- | Function for inserting a new 'Particle' into its 'PartileSystem' insertParticle From 767643a1af3775cca61e1885ae3dd08b99ea8998 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 25 Feb 2017 17:23:54 +0100 Subject: [PATCH 095/261] fixing leak in partUpd --- examples/example03.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/example03.hs b/examples/example03.hs index 8e4f5ca..b6f2b17 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE BangPatterns #-} import Affection import qualified SDL @@ -173,23 +174,22 @@ update sec evs = do return () ) evs ud2 <- getAffection - nps <- updateParticleSystem (partsys ud2) sec partUpd partDraw + !nps <- updateParticleSystem (partsys ud2) sec partUpd partDraw putAffection $ ud2 { partsys = nps } clean :: UserData -> IO () clean _ = return () partUpd :: Double -> Particle -> Affection UserData Particle -partUpd sec p@Particle{..} = do - let newX = (fst particlePosition) + sec * (fromIntegral $ fst particleVelocity) - newY = (snd particlePosition) + sec * (fromIntegral $ snd particleVelocity) - liftIO $ G.gegl_node_set (particleNodeGraph M.! "rect") $ G.Operation "gegl:rectangle" +partUpd sec p = do + let !newX = (fst $ particlePosition p) + sec * (fromIntegral $ fst $ particleVelocity p) + !newY = (snd $ particlePosition p) + sec * (fromIntegral $ snd $ particleVelocity p) + liftIO $ G.gegl_node_set (particleNodeGraph p M.! "rect") $ G.Operation "gegl:rectangle" [ G.Property "x" $ G.PropertyDouble $ newX - 10 , G.Property "y" $ G.PropertyDouble $ newY - 10 ] - return p - { particlePosition = (newX, newY) - } + let !np = p {particlePosition = (newX, newY)} + return np partDraw :: G.GeglBuffer -> G.GeglNode -> Particle -> Affection UserData () partDraw buf node Particle{..} = do From 7a953b0a8b6990b4747e4fa777cbf1459fd443a2 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 25 Feb 2017 17:24:21 +0100 Subject: [PATCH 096/261] not needed for now Since I switched to texture drawing I don't need to pre-handle WindowMoved events, but who knows, what I might need to pre-handle in the future. --- src/Affection.hs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index c989a9b..0d174b9 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -154,15 +154,16 @@ getSurfaces window = do -- Prehandle SDL events in case any window events occur preHandleEvents :: [SDL.Event] -> Affection us [SDL.Event] preHandleEvents evs = - mapM handle evs - where - handle e = - case SDL.eventPayload e of - SDL.WindowMovedEvent _ -> do - liftIO $ traceIO "I was moved" - return e - _ -> - return e + -- mapM handle evs + -- where + -- handle e = + -- case SDL.eventPayload e of + -- SDL.WindowMovedEvent _ -> do + -- liftIO $ traceIO "I was moved" + -- return e + -- _ -> + -- return e + return evs -- | Return the userstate to the user getAffection :: Affection us us From 29183fb223a351bcb96dddf4d5de552fcff2ef32 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 25 Feb 2017 17:26:00 +0100 Subject: [PATCH 097/261] removing old code --- src/Affection/Particle.hs | 80 ++++++++++++--------------------------- 1 file changed, 25 insertions(+), 55 deletions(-) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 8743234..cafb298 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -36,61 +36,31 @@ updateParticle -- -> Affection us [Maybe Particle] -> Affection us (Maybe Particle) -- ^ resulting 'Particle' -updateParticle time funct pa = do - now <- elapsedTime <$> get - if particleCreation pa + particleTimeToLive pa < now - then do - mproducer <- liftIO $ G.gegl_node_get_producer - (particleStackCont pa) - "input" - case mproducer of - Just (producer, padname) -> do - consumers <- liftIO $ G.gegl_node_get_consumers - (particleStackCont pa) - "output" - liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to - producer - padname - node - inpad - ) consumers - Nothing -> return () - liftIO $ G.gegl_node_drop $ particleRootNode pa - return $ Nothing - else do - np <- Just <$> funct time pa - return $ np --- updateParticle time funct acc@[p] pa = do --- now <- elapsedTime <$> get --- if particleCreation pa + particleTimeToLive pa > now --- then do --- liftIO $ G.gegl_node_drop $ particleRootNode pa --- return $ Nothing : acc --- else do --- when (not $ isNothing p) $ do --- -- liftIO $ traceIO "linking second node in list" --- liftIO $ G.gegl_node_link --- (particleStackCont pa) --- (particleStackCont $ fromJust p) --- np <- Just <$> funct time pa --- return $ np : acc --- updateParticle time funct acc@(p:ps) pa = do --- now <- elapsedTime <$> get --- if particleCreation pa + particleTimeToLive pa > now --- then do --- liftIO $ G.gegl_node_drop $ particleRootNode pa --- return $ Nothing : acc --- else do --- when (isNothing p) $ do --- let mnl = nextLiving ps --- maybe --- (return ()) --- (\nl -> do --- -- liftIO $ traceIO "linking nth node on list" --- liftIO $ G.gegl_node_link (particleStackCont pa) (particleStackCont nl)) --- mnl --- np <- Just <$> funct time pa --- return $ np : acc +updateParticle time funct pa = + do + now <- elapsedTime <$> get + if particleCreation pa + particleTimeToLive pa < now + then do + mproducer <- liftIO $ G.gegl_node_get_producer + (particleStackCont pa) + "input" + case mproducer of + Just (producer, padname) -> do + consumers <- liftIO $ G.gegl_node_get_consumers + (particleStackCont pa) + "output" + liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to + producer + padname + node + inpad + ) consumers + Nothing -> return () + liftIO $ G.gegl_node_drop $ particleRootNode pa + return $ Nothing + else do + np <- Just <$> funct time pa + return $ np -- | Get the next living particle from a list nextLiving From 8c1d5a2664332bf546e0be4c00eb38c8aff56326 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 5 Mar 2017 16:39:37 +0100 Subject: [PATCH 098/261] new version with more features --- affection.cabal | 4 +++- src/Affection.hs | 2 ++ src/Affection/Draw.hs | 4 ++-- src/Affection/MouseInteractable.hs | 18 ++++++++++++++++++ src/Affection/StateMachine.hs | 14 ++++++++++++++ src/Affection/Types.hs | 2 +- 6 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/Affection/MouseInteractable.hs create mode 100644 src/Affection/StateMachine.hs diff --git a/affection.cabal b/affection.cabal index ba0d35d..cd3dd3f 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.4 +version: 0.0.0.5 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL and GEGL. @@ -39,6 +39,8 @@ library , Affection.Draw , Affection.Particle , Affection.Types + , Affection.StateMachine + , Affection.MouseInteractable default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/src/Affection.hs b/src/Affection.hs index 0d174b9..7683e76 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -33,6 +33,8 @@ import Debug.Trace import Affection.Types as A import Affection.Draw as A import Affection.Particle as A +import Affection.StateMachine as A +import Affection.MouseInteractable as A import qualified BABL as B diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index c22aa8d..7b50933 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -61,7 +61,7 @@ present -> Affection us () present rect buf kill = do ad <- get - let k = if not kill then Kill Nothing else Yes + let k = if not kill then Kill Nothing else Persist put ad { drawStack = (DrawRequest rect buf k) : drawStack ad } @@ -95,7 +95,7 @@ handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do -- liftIO $ SDL.updateWindowSurface $ drawWindow ad ) mrealRect case requestPersist of - Yes -> + Persist -> return Nothing Kill _ -> return $ Just dr diff --git a/src/Affection/MouseInteractable.hs b/src/Affection/MouseInteractable.hs new file mode 100644 index 0000000..ba0e3b1 --- /dev/null +++ b/src/Affection/MouseInteractable.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE MultiParamTypeClasses #-} + +module Affection.MouseInteractable where + +import Affection.Types + +-- 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 () diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs new file mode 100644 index 0000000..7ea9597 --- /dev/null +++ b/src/Affection/StateMachine.hs @@ -0,0 +1,14 @@ +{-# LANGUAGE MultiParamTypeClasses #-} + +module Affection.StateMachine where + +import Affection.Types + +import qualified SDL + +class StateMachine a us where + smLoad :: a -> Affection us () + smUpdate :: a -> Double -> Affection us () + smEvent :: a -> Double -> SDL.Event -> Affection us () + smDraw :: a -> Affection us () + smClean :: a -> Affection us () diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index ecfd039..bd0193c 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -96,7 +96,7 @@ data DrawRequest = DrawRequest } data RequestPersist - = Yes + = Persist | Kill (Maybe G.GeglNode) -- | A type for storing 'DrawRequest' results to be executed frequently. TODO From cb91cd77c4d64522d263800c086bb3e9f5c39bfa Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 5 Mar 2017 16:41:18 +0100 Subject: [PATCH 099/261] correction --- src/Affection/MouseInteractable.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Affection/MouseInteractable.hs b/src/Affection/MouseInteractable.hs index ba0e3b1..0a3454f 100644 --- a/src/Affection/MouseInteractable.hs +++ b/src/Affection/MouseInteractable.hs @@ -4,6 +4,8 @@ module Affection.MouseInteractable where import Affection.Types +import qualified SDL + -- class MouseHoverable a us where -- onHover :: a -> Affection us () From af271f85b2178ab7d90d409a7967bd8479af77dd Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 6 Mar 2017 17:48:12 +0100 Subject: [PATCH 100/261] adding handler function --- src/Affection/MouseInteractable.hs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Affection/MouseInteractable.hs b/src/Affection/MouseInteractable.hs index 0a3454f..9af9a64 100644 --- a/src/Affection/MouseInteractable.hs +++ b/src/Affection/MouseInteractable.hs @@ -18,3 +18,25 @@ class MouseClickable a us where -> 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.Event -- ^ Piped event in + -> t clickable -- ^ 'MouseClickable' elemt to be checked + -> Affection us SDL.Event -- ^ Unaltered event +handleMouseClicks e clickables = + case SDL.eventPayload 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 From 161ace0770e85cf3a1bfe023069c53c77d22a351 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 14 Mar 2017 20:10:47 +0100 Subject: [PATCH 101/261] recursive particle update --- src/Affection/Particle.hs | 87 ++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index cafb298..863e616 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -25,42 +25,48 @@ import Debug.Trace -- bother you. updateParticle :: Double - -- ^ Elapsed time in seconds - -> (Double -> Particle -> Affection us Particle) - -- ^ Update function for a single 'Particle' - -- This Function should take the elapsed time - -- in seconds and the initial particle as arguments. - -- -> [Maybe Particle] - -> Particle - -- ^ 'Particle' to be processed - -- -> Affection us [Maybe Particle] - -> Affection us (Maybe Particle) - -- ^ resulting 'Particle' -updateParticle time funct pa = - do - now <- elapsedTime <$> get - if particleCreation pa + particleTimeToLive pa < now - then do + -- ^ Elapsed time in seconds + -> (Double -> Particle -> Affection us Particle) + -- ^ Update function for each 'Particle' + -> [Particle] + -- ^ List of 'Particle's to be processed + -> Affection us [Particle] + -- ^ processed 'Particle's +updateParticle time func l = + updateParticle' time func l + where + updateParticle' _ _ [] = return [] + updateParticle' dt fun [p] = do + now <- elapsedTime <$> get + if particleCreation p + particleTimeToLive p < now + then + return [] + else + return . (: []) =<< (func time p) + updateParticle' dt fun (p:ps) = do + now <- elapsedTime <$> get + if particleCreation p + particleTimeToLive p < now + then do + dropParticle p + updateParticle' dt fun ps + else do + np <- fun dt p + return . (np :) =<< updateParticle' dt fun ps + dropParticle p = do mproducer <- liftIO $ G.gegl_node_get_producer - (particleStackCont pa) + (particleStackCont p) "input" - case mproducer of - Just (producer, padname) -> do - consumers <- liftIO $ G.gegl_node_get_consumers - (particleStackCont pa) - "output" - liftIO $ mapM_ (\(node, inpad)-> G.gegl_node_connect_to - producer - padname - node - inpad - ) consumers - Nothing -> return () - liftIO $ G.gegl_node_drop $ particleRootNode pa - return $ Nothing - else do - np <- Just <$> funct time pa - return $ np + maybe (return ()) (\(producer, padname) -> do + consumers <- liftIO $ G.gegl_node_get_consumers + (particleStackCont p) + "output" + liftIO $ mapM_ (\(node, inpad) -> G.gegl_node_connect_to + producer + padname + node + inpad + ) consumers + ) mproducer -- | Get the next living particle from a list nextLiving @@ -70,9 +76,6 @@ nextLiving [] = Nothing nextLiving acc = case catMaybes acc of [] -> Nothing ps -> Just $ head $ ps - -- if isNothing p - -- then nextLiving ps - -- else p drawParticles :: (Particle -> Affection us ()) @@ -87,13 +90,9 @@ updateParticleSystem -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) -> Affection us ParticleSystem updateParticleSystem sys sec upd draw = do - !x <- catMaybes <$> mapM (updateParticle sec upd) (partStorList $ partSysParts sys) - -- liftIO $ traceIO $ show $ length x - -- x <- catMaybes <$> foldM (updateParticle sec upd) [] (reverse $ psParts sys) + !x <- updateParticle sec upd (partStorList $ partSysParts sys) if (not $ null x) then do - -- liftIO $ G.gegl_node_link_many $ map particleStackCont (partStorList $ partSysParts sys) - -- liftIO $ traceIO "linking last node to output" liftIO $ G.gegl_node_link (particleStackCont $ head x) (partSysNode sys) mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x return sys @@ -119,10 +118,6 @@ insertParticle ps p = do now <- elapsedTime <$> get let newList = chronoInsert now (partStorList $ partSysParts ps) p liftIO $ G.gegl_node_link_many (reverse $ map particleStackCont newList) - -- when (not $ isNothing $ partStorLatest $ partSysParts ps) $ - -- liftIO $ G.gegl_node_link - -- (particleStackCont p) - -- (particleStackCont $ fromJust $ partStorLatest $ partSysParts ps) return $ ps { partSysParts = (partSysParts ps) { partStorLatest = Just p From 5d8c5d807c166cb428f87f8e1a740b8ed4b661f0 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 14 Mar 2017 22:18:39 +0100 Subject: [PATCH 102/261] a little lint --- src/Affection/Particle.hs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 863e616..4d9649d 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE BangPatterns #-} -- | This module introduces a simple particle system to Affection @@ -42,7 +41,7 @@ updateParticle time func l = then return [] else - return . (: []) =<< (func time p) + (: []) <$> func time p updateParticle' dt fun (p:ps) = do now <- elapsedTime <$> get if particleCreation p + particleTimeToLive p < now @@ -51,7 +50,7 @@ updateParticle time func l = updateParticle' dt fun ps else do np <- fun dt p - return . (np :) =<< updateParticle' dt fun ps + (np :) <$> updateParticle' dt fun ps dropParticle p = do mproducer <- liftIO $ G.gegl_node_get_producer (particleStackCont p) @@ -60,11 +59,9 @@ updateParticle time func l = consumers <- liftIO $ G.gegl_node_get_consumers (particleStackCont p) "output" - liftIO $ mapM_ (\(node, inpad) -> G.gegl_node_connect_to + liftIO $ mapM_ (uncurry $ G.gegl_node_connect_to producer padname - node - inpad ) consumers ) mproducer @@ -75,7 +72,7 @@ nextLiving nextLiving [] = Nothing nextLiving acc = case catMaybes acc of [] -> Nothing - ps -> Just $ head $ ps + ps -> Just $ head ps drawParticles :: (Particle -> Affection us ()) @@ -91,7 +88,7 @@ updateParticleSystem -> Affection us ParticleSystem updateParticleSystem sys sec upd draw = do !x <- updateParticle sec upd (partStorList $ partSysParts sys) - if (not $ null x) + if not $ null x then do liftIO $ G.gegl_node_link (particleStackCont $ head x) (partSysNode sys) mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x @@ -118,7 +115,7 @@ insertParticle ps p = do now <- elapsedTime <$> get let newList = chronoInsert now (partStorList $ partSysParts ps) p liftIO $ G.gegl_node_link_many (reverse $ map particleStackCont newList) - return $ ps + return ps { partSysParts = (partSysParts ps) { partStorLatest = Just p , partStorList = newList From 488235009532361d3b10c96d6bf72f905008fef2 Mon Sep 17 00:00:00 2001 From: Astro Date: Thu, 16 Mar 2017 20:12:41 +0100 Subject: [PATCH 103/261] split updateLoop into eventLoop/updateLoop, getTick, getDelta --- examples/example00.hs | 12 ++++++++--- examples/example01.hs | 26 +++++++++++++---------- examples/example02.hs | 47 +++++++++++++++++++++++------------------- examples/example03.hs | 40 ++++++++++++++++++----------------- src/Affection.hs | 26 ++++++++++++++++++++--- src/Affection/Types.hs | 7 +++++-- 6 files changed, 99 insertions(+), 59 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index 9d4808f..5e9f7a3 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -29,8 +29,9 @@ main = do , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow , preLoop = return () - , drawLoop = draw + , eventLoop = handle , updateLoop = update + , drawLoop = draw , loadState = load , cleanUp = clean } @@ -96,11 +97,16 @@ draw = do liftIO $ SDL.unlockSurface drawSurface liftIO $ SDL.updateWindowSurface drawWindow -update :: Double -> [SDL.Event] -> Affection UserData () -update sec _ = do +handle :: SDL.EventPayload -> Affection UserData () +handle = const $ return () + +update :: Affection UserData () +update = do traceM "updating" ad <- get ud@UserData{..} <- getAffection + + sec <- getTick traceM $ (show $ 1 / sec) ++ " FPS" when (elapsedTime ad > 5) $ put $ ad diff --git a/examples/example01.hs b/examples/example01.hs index 10351d9..398018a 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -29,8 +29,9 @@ main = do , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow , preLoop = return () - , drawLoop = draw + , eventLoop = const $ return () , updateLoop = update + , drawLoop = draw , loadState = load , cleanUp = clean } @@ -39,6 +40,7 @@ main = do data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer + , lastTick :: Double } load :: SDL.Surface -> IO UserData @@ -80,6 +82,7 @@ load _ = do return $ UserData { nodeGraph = myMap , foreground = buffer + , lastTick = 0 } draw :: Affection UserData () @@ -89,17 +92,18 @@ draw = do drawRect (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" -update :: Double -> [SDL.Event] -> Affection UserData () -update sec _ = do +update :: Affection UserData () +update = do traceM "updating" - -- liftIO $ delaySec 5 - ad <- get - ud@UserData{..} <- getAffection - traceM $ (show $ 1 / sec) ++ " FPS" - when (elapsedTime ad > 20) $ - put $ ad - { quitEvent = True - } + ud <- getAffection + let last = lastTick ud + tick <- getTick + putAffection $ ud { lastTick = tick } + let dt = tick - last + traceM $ (show $ 1 / dt) ++ " FPS" + + when (tick > 20) $ + quit clean :: UserData -> IO () clean _ = return () diff --git a/examples/example02.hs b/examples/example02.hs index a884b7f..8d29e3f 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -20,8 +20,9 @@ main = do , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow , preLoop = return () - , drawLoop = draw + , eventLoop = handle , updateLoop = update + , drawLoop = draw , loadState = load , cleanUp = clean } @@ -31,6 +32,7 @@ data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer , coordinates :: Maybe (Int, Int) + , lastTick :: Double } load :: SDL.Surface -> IO UserData @@ -73,6 +75,7 @@ load _ = do { nodeGraph = myMap , foreground = buffer , coordinates = Nothing + , lastTick = 0 } -- drawInit :: Affection UserData () @@ -98,28 +101,30 @@ draw = do ) coordinates liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" -update :: Double -> [SDL.Event] -> Affection UserData () -update sec evs = do +update :: Affection UserData () +update = do traceM "updating" - ad <- get + + tick <- getTick ud <- getAffection - traceM $ (show $ 1 / sec) ++ " FPS" - -- ev <- liftIO $ SDL.pollEvent - mapM_ (\e -> - case SDL.eventPayload e of - SDL.MouseMotionEvent dat -> do - let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat - putAffection $ ud - { coordinates = Just (fromIntegral x, fromIntegral y) - } - SDL.WindowClosedEvent _ -> do - traceM "seeya!" - put $ ad - { quitEvent = True - } - _ -> - return () - ) evs + putAffection $ ud { lastTick = tick } + + let dt = tick - lastTick ud + traceM $ (show $ 1 / dt) ++ " FPS" + +handle (SDL.MouseMotionEvent dat) = do + let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat + ud <- getAffection + putAffection $ ud + { coordinates = Just (fromIntegral x, fromIntegral y) + } + +handle (SDL.WindowClosedEvent _) = do + traceM "seeya!" + quit + +handle _ = + return () clean :: UserData -> IO () clean _ = return () diff --git a/examples/example03.hs b/examples/example03.hs index b6f2b17..ffa4ac4 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -23,8 +23,9 @@ main = do , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow , preLoop = drawInit - , drawLoop = draw + , eventLoop = handle , updateLoop = update + , drawLoop = draw , loadState = load , cleanUp = clean } @@ -104,20 +105,25 @@ draw = do -- (G.GeglRectangle (x - 10) (y - 10) 20 20) -- ) $ coordinates ud -update :: Double -> [SDL.Event] -> Affection UserData () -update sec evs = do +update :: Affection UserData () +update = do traceM "updating" ad <- get ud <- getAffection - -- let newPart = updateParticles sec partUpd $ particles ud + delta <- getDelta + -- let newPart = updateParticles delta partUpd $ particles ud -- putAffection $ ud { particles = newPart } - traceM $ (show $ 1 / sec) ++ " FPS" + traceM $ (show $ 1 / delta) ++ " FPS" -- ev <- liftIO $ SDL.pollEvents - mapM_ (\e -> - case SDL.eventPayload e of - SDL.MouseMotionEvent dat -> + ud2 <- getAffection + !nps <- updateParticleSystem (partsys ud2) delta partUpd partDraw + putAffection $ ud2 { partsys = nps } + +handle (SDL.MouseMotionEvent dat) = if SDL.ButtonLeft `elem` SDL.mouseMotionEventState dat then do + ad <- get + ud <- getAffection let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat vx <- liftIO $ randomRIO (-20, 20) vy <- liftIO $ randomRIO (-20, 20) @@ -165,17 +171,13 @@ update sec evs = do -- (particleStackCont $ head $ psParts $ partsys ud) else return () - SDL.WindowClosedEvent _ -> do - traceM "seeya!" - put $ ad - { quitEvent = True - } - _ -> - return () - ) evs - ud2 <- getAffection - !nps <- updateParticleSystem (partsys ud2) sec partUpd partDraw - putAffection $ ud2 { partsys = nps } + +handle (SDL.WindowClosedEvent _) = do + traceM "seeya!" + quit + +handle _ = + return () clean :: UserData -> IO () clean _ = return () diff --git a/src/Affection.hs b/src/Affection.hs index 7683e76..9ae35b7 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -9,6 +9,9 @@ module Affection , delaySec , get , put + , getTick + , getDelta + , quit , module A ) where @@ -93,6 +96,7 @@ withAffection AffectionConfig{..} = do , drawCPP = cpp , drawStack = [] , elapsedTime = 0 + , dt = 0 }) <$> loadState surface (_, nState) <- runStateT ( A.runState $ do preLoop @@ -116,11 +120,13 @@ withAffection AffectionConfig{..} = do put $ ad { drawStack = [] , elapsedTime = ne + , dt = dt } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents + forM evs $ eventLoop -- execute user defined update loop - updateLoop dt evs + updateLoop -- execute user defined draw loop drawLoop -- handle all new draw requests @@ -154,7 +160,7 @@ getSurfaces window = do return (oldSurf, surface) -- Prehandle SDL events in case any window events occur -preHandleEvents :: [SDL.Event] -> Affection us [SDL.Event] +preHandleEvents :: [SDL.Event] -> Affection us [SDL.EventPayload] preHandleEvents evs = -- mapM handle evs -- where @@ -165,7 +171,7 @@ preHandleEvents evs = -- return e -- _ -> -- return e - return evs + return $ map SDL.eventPayload evs -- | Return the userstate to the user getAffection :: Affection us us @@ -187,3 +193,17 @@ delaySec :: Int -- ^ Number of seconds -> IO () delaySec dur = SDL.delay (fromIntegral $ dur * 1000) + +-- | Get time since start but always the same in the current tick. +getTick :: Affection us Double +getTick = + elapsedTime <$> get + +getDelta :: Affection us Double +getDelta = + dt <$> get + +quit :: Affection us () +quit = do + ad <- get + put $ ad { quitEvent = True } diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index bd0193c..665237a 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -56,10 +56,12 @@ data AffectionConfig us = AffectionConfig -- ^ Window configuration , 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 :: Affection us () + -- ^ Main update function. Takes fractions of a second as input. , drawLoop :: Affection us () -- ^ Function for updating graphics. - , updateLoop :: Double -> [SDL.Event] -> Affection us () - -- ^ Main update function. Takes fractions of a second as input. , loadState :: SDL.Surface -> IO us -- ^ Provide your own load function to create this data. , cleanUp :: us -> IO () @@ -86,6 +88,7 @@ data AffectionData us = AffectionData , drawStride :: Int -- ^ Stride of target buffer , drawCPP :: Int -- ^ Number of components per pixel , elapsedTime :: Double -- ^ Elapsed time in seconds + , dt :: Double -- ^ Elapsed time in seconds since last tick } -- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated From a7c33a7413ec0fe136bf4875b9be8e9d248ded55 Mon Sep 17 00:00:00 2001 From: Astro Date: Thu, 16 Mar 2017 20:21:04 +0100 Subject: [PATCH 104/261] clear --- examples/example02.hs | 5 +---- src/Affection/Draw.hs | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 8d29e3f..ffa6bde 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -86,11 +86,8 @@ load _ = do draw :: Affection UserData () draw = do traceM "drawing" - ad <- get UserData{..} <- getAffection - SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions $ drawSurface ad - let (w, h) = (fromIntegral rw, fromIntegral rh) - liftIO $ clearArea foreground (GeglRectangle 0 0 w h) + clear foreground maybe (return ()) (\(x, y) -> drawRect (nodeGraph M.! "nop") diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 7b50933..6c6ffea 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -3,7 +3,7 @@ -- | Module for drawing primitives module Affection.Draw ( drawRect - -- , clear + , clear , handleDrawRequest , invalidateDrawRequest , present @@ -186,3 +186,11 @@ clearArea -> G.GeglRectangle -- ^ Area to clear -> IO () clearArea = G.gegl_buffer_clear + +-- | Clear the whole drawing area +clear :: G.GeglBuffer -> Affection us () +clear buffer = do + ad <- get + SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions $ drawSurface ad + let (w, h) = (fromIntegral rw, fromIntegral rh) + liftIO $ clearArea buffer (GeglRectangle 0 0 w h) From a32c8957bd8ea967d29e5262f44126e482ae5006 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 16 Mar 2017 20:52:45 +0100 Subject: [PATCH 105/261] adding process function --- examples/example01.hs | 2 +- examples/example02.hs | 4 +--- examples/example03.hs | 2 +- src/Affection/Draw.hs | 6 ++++++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/example01.hs b/examples/example01.hs index 398018a..0acefcc 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -90,7 +90,7 @@ draw = do traceM "drawing" UserData{..} <- getAffection drawRect (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground - liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" + process $ nodeGraph M.! "sink" update :: Affection UserData () update = do diff --git a/examples/example02.hs b/examples/example02.hs index ffa6bde..c41f862 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -85,9 +85,7 @@ load _ = do draw :: Affection UserData () draw = do - traceM "drawing" UserData{..} <- getAffection - clear foreground maybe (return ()) (\(x, y) -> drawRect (nodeGraph M.! "nop") @@ -96,7 +94,7 @@ draw = do (G.GeglRectangle (x - 10) (y - 10) 20 20) foreground ) coordinates - liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" + process (nodeGraph M.! "sink") update :: Affection UserData () update = do diff --git a/examples/example03.hs b/examples/example03.hs index ffa4ac4..9367bfb 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -89,7 +89,7 @@ draw :: Affection UserData () draw = do traceM "drawing" UserData{..} <- getAffection - liftIO $ G.gegl_node_process $ nodeGraph M.! "sink" + process $ nodeGraph M.! "sink" -- ad <- get -- ud <- getAffection -- drawParticles partDraw $ particles ud diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 6c6ffea..b016761 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -7,6 +7,7 @@ module Affection.Draw , handleDrawRequest , invalidateDrawRequest , present + , process , clearArea ) where @@ -66,6 +67,11 @@ present rect buf kill = do { drawStack = (DrawRequest rect buf k) : drawStack ad } +process + :: G.GeglNode + -> Affection us () +process = liftIO . G.gegl_node_process + -- | function for handling 'DrawRequest's and updating the output handleDrawRequest :: Ptr a -- ^ Pixel buffer to blit to From 7c51fbf3ac7aea45fddac560a292f1b2ccb182b5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 16 Mar 2017 23:24:34 +0100 Subject: [PATCH 106/261] rebuild example02 --- examples/example02.hs | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index c41f862..15c76e2 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -3,14 +3,9 @@ import Affection import qualified SDL import qualified GEGL as G -import qualified GEGL.FFI.Buffer as G import qualified BABL as B import qualified Data.Map.Strict as M -import Foreign.C.Types -import Foreign.Marshal.Utils (new) -import Foreign.Ptr (castPtr) - import Debug.Trace main :: IO () @@ -31,7 +26,6 @@ main = do data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer - , coordinates :: Maybe (Int, Int) , lastTick :: Double } @@ -53,48 +47,46 @@ load _ = do [ G.Property "buffer" $ G.PropertyBuffer buffer ] traceM "buffer-sink" - nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] - traceM "nop" + rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" + [ G.Property "x" $ G.PropertyDouble 0 + , G.Property "y" $ G.PropertyDouble 0 + , G.Property "width" $ G.PropertyDouble 20 + , G.Property "height" $ G.PropertyDouble 20 + , G.Property "color" $ G.PropertyColor $ G.RGBA 1 0 0 0.5 + ] + traceM "rect" crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" [ G.Property "width" $ G.PropertyDouble 800 , G.Property "height" $ G.PropertyDouble 600 ] G.gegl_node_link_many [checkerboard, over, crop, sink] - G.gegl_node_connect_to nop "output" over "aux" + _ <- G.gegl_node_connect_to rect "output" over "aux" traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) , ("over" , over) , ("background" , checkerboard) , ("sink" , sink) - , ("nop" , nop) + , ("rect" , rect) , ("crop" , crop) ] traceM "loading complete" return $ UserData { nodeGraph = myMap , foreground = buffer - , coordinates = Nothing , lastTick = 0 } -- drawInit :: Affection UserData () -- drawInit = do -- UserData{..} <- getAffection --- present (nodeGraph M.! "over") foreground (GeglRectangle 0 0 800 600) True +-- present (GeglRectangle 0 0 800 600) foreground True draw :: Affection UserData () draw = do UserData{..} <- getAffection - maybe (return ()) (\(x, y) -> - drawRect - (nodeGraph M.! "nop") - (G.RGBA 1 0 0 0.5) - (Fill) - (G.GeglRectangle (x - 10) (y - 10) 20 20) - foreground - ) coordinates process (nodeGraph M.! "sink") + present (GeglRectangle 0 0 800 600) foreground True update :: Affection UserData () update = do @@ -105,14 +97,17 @@ update = do putAffection $ ud { lastTick = tick } let dt = tick - lastTick ud - traceM $ (show $ 1 / dt) ++ " FPS" + return () + -- traceM $ (show $ 1 / dt) ++ " FPS" handle (SDL.MouseMotionEvent dat) = do let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat ud <- getAffection - putAffection $ ud - { coordinates = Just (fromIntegral x, fromIntegral y) - } + liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" + [ G.Property "x" $ G.PropertyDouble $ fromIntegral (x - 10) + , G.Property "y" $ G.PropertyDouble $ fromIntegral (y - 10) + ] + liftIO $ traceIO $ show x ++ " " ++ show y handle (SDL.WindowClosedEvent _) = do traceM "seeya!" From ba454d341d3d13bcd6d515f3a02909a9927df736 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 16 Mar 2017 23:26:23 +0100 Subject: [PATCH 107/261] little fixes --- examples/example02.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 15c76e2..c04b50a 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -98,7 +98,7 @@ update = do let dt = tick - lastTick ud return () - -- traceM $ (show $ 1 / dt) ++ " FPS" + traceM $ (show $ 1 / dt) ++ " FPS" handle (SDL.MouseMotionEvent dat) = do let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat @@ -107,7 +107,6 @@ handle (SDL.MouseMotionEvent dat) = do [ G.Property "x" $ G.PropertyDouble $ fromIntegral (x - 10) , G.Property "y" $ G.PropertyDouble $ fromIntegral (y - 10) ] - liftIO $ traceIO $ show x ++ " " ++ show y handle (SDL.WindowClosedEvent _) = do traceM "seeya!" From a737e2863c3414e3e599117fbbb66ffdb97cc1dd Mon Sep 17 00:00:00 2001 From: Astro Date: Thu, 16 Mar 2017 23:51:41 +0100 Subject: [PATCH 108/261] implement Affection.Property dsl --- affection.cabal | 1 + examples/example00.hs | 18 ++++++++--------- examples/example01.hs | 22 ++++++++++---------- src/Affection.hs | 2 ++ src/Affection/Property.hs | 42 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 src/Affection/Property.hs diff --git a/affection.cabal b/affection.cabal index cd3dd3f..49f955e 100644 --- a/affection.cabal +++ b/affection.cabal @@ -41,6 +41,7 @@ library , Affection.Types , Affection.StateMachine , Affection.MouseInteractable + , Affection.Property default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/examples/example00.hs b/examples/example00.hs index 5e9f7a3..551c881 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -46,18 +46,18 @@ load _ = do traceM "loading" root <- G.gegl_node_new traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation - [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 - , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 - ] + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ + props $ do + prop "color1" $ G.RGBA 0.4 0.4 0.4 1 + prop "color2" $ G.RGBA 0.6 0.6 0.6 1 traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - text <- G.gegl_node_new_child root $ G.textOperation - [ G.Property "string" $ G.PropertyString "Hello world!" - , G.Property "color" $ G.PropertyColor $ G.RGBA 0 0 1 0.5 - , G.Property "size" $ G.PropertyDouble 40 - ] + text <- G.gegl_node_new_child root $ G.textOperation $ + props $ do + prop "string" ("Hello world!"::String) + prop "color" $ G.RGBA 0 0 1 0.5 + prop "size" (40::Int) traceM "text" G.gegl_node_link checkerboard over G.gegl_node_connect_to text "output" over "aux" diff --git a/examples/example01.hs b/examples/example01.hs index 0acefcc..3c64519 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -48,25 +48,25 @@ load _ = do traceM "loading" root <- G.gegl_node_new traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation - [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 - , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 - ] + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ + props $ do + prop "color1" $ G.RGBA 0.4 0.4 0.4 1 + prop "color2" $ G.RGBA 0.6 0.6 0.6 1 traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" - [ G.Property "buffer" $ G.PropertyBuffer buffer - ] + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ + props $ + prop "buffer" buffer traceM "buffer-source" nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] traceM "nop" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" - [ G.Property "width" $ G.PropertyDouble 800 - , G.Property "height" $ G.PropertyDouble 600 - ] + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ + props $ do + prop "width" (800::Double) + prop "height" (600.0::Double) G.gegl_node_link_many [checkerboard, over, crop, sink] G.gegl_node_connect_to nop "output" over "aux" traceM "connections made" diff --git a/src/Affection.hs b/src/Affection.hs index 9ae35b7..f42d519 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -13,6 +13,7 @@ module Affection , getDelta , quit , module A + , module Affection.Property ) where import qualified SDL @@ -38,6 +39,7 @@ import Affection.Draw as A import Affection.Particle as A import Affection.StateMachine as A import Affection.MouseInteractable as A +import Affection.Property import qualified BABL as B diff --git a/src/Affection/Property.hs b/src/Affection/Property.hs new file mode 100644 index 0000000..73a5bb8 --- /dev/null +++ b/src/Affection/Property.hs @@ -0,0 +1,42 @@ +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE FlexibleInstances #-} +module Affection.Property where + +import Control.Monad.State.Lazy +import qualified GEGL as G +import qualified BABL as B +import GHC.Ptr (Ptr) + +type Props a = State [G.Property] a + +props :: Props a -> [G.Property] +props = flip execState [] + +prop :: IsPropertyValue v => String -> v -> Props () +prop k v = do + ps <- get + put $ (G.Property k (toPropertyValue v)) : ps + +class IsPropertyValue v where + toPropertyValue :: v -> G.PropertyValue + +instance IsPropertyValue Int where + toPropertyValue = G.PropertyInt + +instance IsPropertyValue String where + toPropertyValue = G.PropertyString + +instance IsPropertyValue Double where + toPropertyValue = G.PropertyDouble + +instance IsPropertyValue G.Color where + toPropertyValue = G.PropertyColor + +instance IsPropertyValue B.PixelFormat where + toPropertyValue = G.PropertyFormat + +instance IsPropertyValue G.GeglBuffer where + toPropertyValue = G.PropertyBuffer + +instance IsPropertyValue (Ptr ()) where + toPropertyValue = G.PropertyPointer From ac9a9e3bdc8ccaa31814ffd8c668cedd5b839589 Mon Sep 17 00:00:00 2001 From: Astro Date: Fri, 17 Mar 2017 00:20:00 +0100 Subject: [PATCH 109/261] port example02 to props --- examples/example02.hs | 44 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index c04b50a..934d812 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -34,31 +34,31 @@ load _ = do traceM "loading" root <- G.gegl_node_new traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation - [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 - , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 - ] + checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ + props $ do + prop "color1" $ G.RGBA 0.4 0.4 0.4 1 + prop "color2" $ G.RGBA 0.6 0.6 0.6 1 traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" - [ G.Property "buffer" $ G.PropertyBuffer buffer - ] + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ + props $ + prop "buffer" buffer traceM "buffer-sink" - rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" - [ G.Property "x" $ G.PropertyDouble 0 - , G.Property "y" $ G.PropertyDouble 0 - , G.Property "width" $ G.PropertyDouble 20 - , G.Property "height" $ G.PropertyDouble 20 - , G.Property "color" $ G.PropertyColor $ G.RGBA 1 0 0 0.5 - ] + rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ + props $ do + prop "x" (0::Double) + prop "y" (0::Double) + prop "width" (20::Double) + prop "height" (20::Double) + prop "color" $ G.RGBA 1 0 0 0.5 traceM "rect" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" - [ G.Property "width" $ G.PropertyDouble 800 - , G.Property "height" $ G.PropertyDouble 600 - ] + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ + props $ do + prop "width" (800::Double) + prop "height" (600::Double) G.gegl_node_link_many [checkerboard, over, crop, sink] _ <- G.gegl_node_connect_to rect "output" over "aux" traceM "connections made" @@ -103,10 +103,10 @@ update = do handle (SDL.MouseMotionEvent dat) = do let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat ud <- getAffection - liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" - [ G.Property "x" $ G.PropertyDouble $ fromIntegral (x - 10) - , G.Property "y" $ G.PropertyDouble $ fromIntegral (y - 10) - ] + liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" $ + props $ do + prop "x" (fromIntegral (x - 10) :: Double) + prop "y" $ (fromIntegral (y - 10) :: Double) handle (SDL.WindowClosedEvent _) = do traceM "seeya!" From c3fdf7b41069415d42b5c2638139e5bf0ab67784 Mon Sep 17 00:00:00 2001 From: Astro Date: Fri, 17 Mar 2017 00:22:26 +0100 Subject: [PATCH 110/261] Affection.Property: import switcheroo --- src/Affection/Property.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Affection/Property.hs b/src/Affection/Property.hs index 73a5bb8..dfebc79 100644 --- a/src/Affection/Property.hs +++ b/src/Affection/Property.hs @@ -5,7 +5,7 @@ module Affection.Property where import Control.Monad.State.Lazy import qualified GEGL as G import qualified BABL as B -import GHC.Ptr (Ptr) +import Foreign.Ptr (Ptr) type Props a = State [G.Property] a From ec4c0fccc84a404f920f6745a0532a66b559c75c Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 18 Mar 2017 13:50:34 +0100 Subject: [PATCH 111/261] indent uniformity --- examples/example02.hs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 934d812..31df9ee 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -36,8 +36,8 @@ load _ = do traceM "new root node" checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ props $ do - prop "color1" $ G.RGBA 0.4 0.4 0.4 1 - prop "color2" $ G.RGBA 0.6 0.6 0.6 1 + prop "color1" $ G.RGBA 0.4 0.4 0.4 1 + prop "color2" $ G.RGBA 0.6 0.6 0.6 1 traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" @@ -49,16 +49,16 @@ load _ = do traceM "buffer-sink" rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ props $ do - prop "x" (0::Double) - prop "y" (0::Double) - prop "width" (20::Double) - prop "height" (20::Double) - prop "color" $ G.RGBA 1 0 0 0.5 + prop "x" (0::Double) + prop "y" (0::Double) + prop "width" (20::Double) + prop "height" (20::Double) + prop "color" $ G.RGBA 1 0 0 0.5 traceM "rect" crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ props $ do - prop "width" (800::Double) - prop "height" (600::Double) + prop "width" (800::Double) + prop "height" (600::Double) G.gegl_node_link_many [checkerboard, over, crop, sink] _ <- G.gegl_node_connect_to rect "output" over "aux" traceM "connections made" From a5273ff79b3059cdc91bd6e97b1695cb2bdeb0fc Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 18 Mar 2017 14:12:42 +0100 Subject: [PATCH 112/261] type signature --- examples/example02.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/example02.hs b/examples/example02.hs index 31df9ee..5ae1aaa 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -100,6 +100,7 @@ update = do return () traceM $ (show $ 1 / dt) ++ " FPS" +handle :: SDL.EventPayload -> Affection UserData () handle (SDL.MouseMotionEvent dat) = do let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat ud <- getAffection From 6eccf8ed5b78f64c4d164b530dc2254acc993f69 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 18 Mar 2017 17:38:26 +0100 Subject: [PATCH 113/261] introducing actors --- affection.cabal | 1 + examples/example02.hs | 31 ++++++++++++++++++++++++++----- src/Affection.hs | 4 ++-- src/Affection/Actor.hs | 12 ++++++++++++ src/Affection/Property.hs | 10 +++++++--- 5 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 src/Affection/Actor.hs diff --git a/affection.cabal b/affection.cabal index 49f955e..46d0d1e 100644 --- a/affection.cabal +++ b/affection.cabal @@ -42,6 +42,7 @@ library , Affection.StateMachine , Affection.MouseInteractable , Affection.Property + , Affection.Actor default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/examples/example02.hs b/examples/example02.hs index 5ae1aaa..4d58f06 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -25,6 +25,7 @@ main = do data UserData = UserData { nodeGraph :: M.Map String G.GeglNode + , actors :: M.Map String Actor , foreground :: G.GeglBuffer , lastTick :: Double } @@ -47,13 +48,14 @@ load _ = do props $ prop "buffer" buffer traceM "buffer-sink" - rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ + rectProps <- return $ props $ do prop "x" (0::Double) prop "y" (0::Double) prop "width" (20::Double) prop "height" (20::Double) prop "color" $ G.RGBA 1 0 0 0.5 + rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" rectProps traceM "rect" crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ props $ do @@ -61,6 +63,7 @@ load _ = do prop "height" (600::Double) G.gegl_node_link_many [checkerboard, over, crop, sink] _ <- G.gegl_node_connect_to rect "output" over "aux" + let rectActor = Actor rectProps rect traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) @@ -71,8 +74,12 @@ load _ = do , ("crop" , crop) ] traceM "loading complete" + actorMap <- return $ M.fromList + [ ("rect", rectActor) + ] return $ UserData { nodeGraph = myMap + , actors = actorMap , foreground = buffer , lastTick = 0 } @@ -85,6 +92,7 @@ load _ = do draw :: Affection UserData () draw = do UserData{..} <- getAffection + mapM_ (\(Actor ps node) -> liftIO $ G.gegl_node_set node $ G.Operation "" ps) actors process (nodeGraph M.! "sink") present (GeglRectangle 0 0 800 600) foreground True @@ -104,10 +112,23 @@ handle :: SDL.EventPayload -> Affection UserData () handle (SDL.MouseMotionEvent dat) = do let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat ud <- getAffection - liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" $ - props $ do - prop "x" (fromIntegral (x - 10) :: Double) - prop "y" $ (fromIntegral (y - 10) :: Double) + + nmap <- return $ M.adjust + (\a -> Actor (props $ do + prop "y" (fromIntegral (y - 10) :: Double) + prop "x" (fromIntegral (x - 10) :: Double) + ) + (actorNode a) + ) + "rect" + (actors ud) + putAffection ud + { actors = nmap + } + -- liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" $ + -- props $ do + -- prop "x" (fromIntegral (x - 10) :: Double) + -- prop "y" $ (fromIntegral (y - 10) :: Double) handle (SDL.WindowClosedEvent _) = do traceM "seeya!" diff --git a/src/Affection.hs b/src/Affection.hs index f42d519..968c1e0 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -13,7 +13,6 @@ module Affection , getDelta , quit , module A - , module Affection.Property ) where import qualified SDL @@ -39,7 +38,8 @@ import Affection.Draw as A import Affection.Particle as A import Affection.StateMachine as A import Affection.MouseInteractable as A -import Affection.Property +import Affection.Property as A +import Affection.Actor as A import qualified BABL as B diff --git a/src/Affection/Actor.hs b/src/Affection/Actor.hs new file mode 100644 index 0000000..2e2a5bf --- /dev/null +++ b/src/Affection/Actor.hs @@ -0,0 +1,12 @@ +-- | This module implements the Actor, a Datastructure binding a 'G.GeglNode' +-- to a game asset +module Affection.Actor + ( Actor(..) + ) where + +import qualified GEGL as G + +data Actor = Actor + { actorProperties :: [G.Property] + , actorNode :: G.GeglNode + } diff --git a/src/Affection/Property.hs b/src/Affection/Property.hs index dfebc79..1caa42c 100644 --- a/src/Affection/Property.hs +++ b/src/Affection/Property.hs @@ -1,10 +1,15 @@ {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} -module Affection.Property where +module Affection.Property + ( Props(..) + , prop + , props + ) where -import Control.Monad.State.Lazy import qualified GEGL as G import qualified BABL as B + +import Control.Monad.State.Lazy import Foreign.Ptr (Ptr) type Props a = State [G.Property] a @@ -34,7 +39,6 @@ instance IsPropertyValue G.Color where instance IsPropertyValue B.PixelFormat where toPropertyValue = G.PropertyFormat - instance IsPropertyValue G.GeglBuffer where toPropertyValue = G.PropertyBuffer From f4a96f03c4013f3d5bf30b8bdd39b7966566d778 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Mar 2017 05:24:02 +0100 Subject: [PATCH 114/261] split particle update ind update and draw. Adding some concurrency --- affection.cabal | 1 + examples/example03.hs | 25 +++++++++++++------------ src/Affection.hs | 3 ++- src/Affection/Draw.hs | 21 +++++++++++++++++++++ src/Affection/Particle.hs | 27 +++++++++++++++++++++------ src/Affection/Types.hs | 3 +++ 6 files changed, 61 insertions(+), 19 deletions(-) diff --git a/affection.cabal b/affection.cabal index 46d0d1e..c1bc844 100644 --- a/affection.cabal +++ b/affection.cabal @@ -65,6 +65,7 @@ library , gegl , babl , monad-loops + , monad-parallel , containers , clock , glib diff --git a/examples/example03.hs b/examples/example03.hs index 9367bfb..75dcccd 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -194,15 +194,16 @@ partUpd sec p = do return np partDraw :: G.GeglBuffer -> G.GeglNode -> Particle -> Affection UserData () -partDraw buf node Particle{..} = do - present - (G.GeglRectangle (floor $ fst particlePosition - 10) (floor $ snd particlePosition - 10) 20 20) - buf - False - -- ud <- getAffection - -- drawRect' - -- particleDrawFlange - -- (G.RGBA 1 0 0 0.5) - -- (Fill) - -- (G.GeglRectangle ((floor $ fst particlePosition) - 10) ((floor $ snd particlePosition) -10) 20 20) - -- buf +partDraw _ _ _ = return () +-- partDraw buf node Particle{..} = do +-- present +-- (G.GeglRectangle (floor $ fst particlePosition - 10) (floor $ snd particlePosition - 10) 20 20) +-- buf +-- False +-- -- ud <- getAffection +-- -- drawRect' +-- -- particleDrawFlange +-- -- (G.RGBA 1 0 0 0.5) +-- -- (Fill) +-- -- (G.GeglRectangle ((floor $ fst particlePosition) - 10) ((floor $ snd particlePosition) -10) 20 20) +-- -- buf diff --git a/src/Affection.hs b/src/Affection.hs index 968c1e0..b18bd48 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -26,6 +26,7 @@ import Data.IORef import System.Clock import Control.Monad.Loops +import qualified Control.Monad.Parallel as MP import Control.Monad.State import Foreign.C.Types (CInt(..)) @@ -126,7 +127,7 @@ withAffection AffectionConfig{..} = do } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents - forM evs $ eventLoop + MP.mapM_ eventLoop evs -- execute user defined update loop updateLoop -- execute user defined draw loop diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index b016761..7618d14 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -72,6 +72,27 @@ process -> Affection us () process = liftIO . G.gegl_node_process +putToSurface + :: Ptr a + -> G.GeglRectangle + -> Int + -> Int + -> DrawRequest + -> Affection us () +putToSurface pixels realRect stride cpp DrawRequest{..} = do + ad <- get + liftIO $ SDL.lockSurface $ drawSurface ad + liftIO $ G.gegl_buffer_get + requestBuffer + (Just realRect) + 1 + (Just $ drawFormat ad) + (pixels `plusPtr` + (rectangleX realRect * cpp + rectangleY realRect * stride)) + stride + G.GeglAbyssNone + liftIO $ SDL.unlockSurface $ drawSurface ad + -- | function for handling 'DrawRequest's and updating the output handleDrawRequest :: Ptr a -- ^ Pixel buffer to blit to diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 4d9649d..0623a51 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -5,6 +5,7 @@ module Affection.Particle ( updateParticle , drawParticles , updateParticleSystem + , drawParticleSystem , insertParticle ) where @@ -12,6 +13,7 @@ import Affection.Types import Control.Monad import Control.Monad.State (get) +import qualified Control.Monad.Parallel as MP import Data.Maybe @@ -38,7 +40,8 @@ updateParticle time func l = updateParticle' dt fun [p] = do now <- elapsedTime <$> get if particleCreation p + particleTimeToLive p < now - then + then do + dropParticle p return [] else (: []) <$> func time p @@ -64,6 +67,7 @@ updateParticle time func l = padname ) consumers ) mproducer + liftIO $ G.gegl_node_drop $ particleRootNode p -- | Get the next living particle from a list nextLiving @@ -84,21 +88,17 @@ updateParticleSystem :: ParticleSystem -> Double -> (Double -> Particle -> Affection us Particle) - -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) -> Affection us ParticleSystem -updateParticleSystem sys sec upd draw = do +updateParticleSystem sys sec upd = do !x <- updateParticle sec upd (partStorList $ partSysParts sys) if not $ null x then do - liftIO $ G.gegl_node_link (particleStackCont $ head x) (partSysNode sys) - mapM_ (draw (partSysBuffer sys) (partSysNode sys)) x return sys { partSysParts = (partSysParts sys) { partStorList = x } } else do - _ <- liftIO $ G.gegl_node_disconnect (partSysNode sys) "input" return sys { partSysParts = ParticleStorage { partStorList = [] @@ -106,6 +106,21 @@ updateParticleSystem sys sec upd draw = do } } +drawParticleSystem + :: ParticleSystem + -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) + -> Affection us () +drawParticleSystem sys draw = + if not (null parts) + then do + liftIO $ G.gegl_node_link (particleStackCont $ head parts) (partSysNode sys) + MP.mapM_ (draw (partSysBuffer sys) (partSysNode sys)) parts + else do + _ <- liftIO $ G.gegl_node_disconnect (partSysNode sys) "input" + return () + where + parts = partStorList (partSysParts sys) + -- | Function for inserting a new 'Particle' into its 'PartileSystem' insertParticle :: ParticleSystem -- ^ 'ParticleSystem' to insert into diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 665237a..a0fad1d 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -40,6 +40,7 @@ import qualified BABL as B import Control.Monad.IO.Class import Control.Monad.State +import qualified Control.Monad.Parallel as MP -- import Control.Monad.Reader -- import Control.Concurrent.MVar @@ -114,6 +115,8 @@ newtype AffectionState us m a = AffectionState { runState :: AffectionStateInner us m a } deriving (Functor, Applicative, Monad, MonadIO, MonadState us) +instance MP.MonadParallel m => MP.MonadParallel (AffectionState us m) + type Affection us a = AffectionState (AffectionData us) IO a -- -- | Inner 'StateT' monad of Affection From 7bf2962d074c837017b9acef72bceb9980da76c1 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Mar 2017 05:24:30 +0100 Subject: [PATCH 115/261] some linting --- examples/example00.hs | 2 +- examples/example01.hs | 16 ++-- examples/example02.hs | 12 +-- examples/example03.hs | 149 ++++++++++++++++++-------------------- src/Affection.hs | 18 ++--- src/Affection/Draw.hs | 54 ++++---------- src/Affection/Property.hs | 4 +- 7 files changed, 112 insertions(+), 143 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index 551c881..844d545 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -106,7 +106,7 @@ update = do ad <- get ud@UserData{..} <- getAffection - sec <- getTick + sec <- getDelta traceM $ (show $ 1 / sec) ++ " FPS" when (elapsedTime ad > 5) $ put $ ad diff --git a/examples/example01.hs b/examples/example01.hs index 3c64519..b2e03a2 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -40,7 +40,7 @@ main = do data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer - , lastTick :: Double + -- , lastTick :: Double } load :: SDL.Surface -> IO UserData @@ -82,7 +82,7 @@ load _ = do return $ UserData { nodeGraph = myMap , foreground = buffer - , lastTick = 0 + -- , lastTick = 0 } draw :: Affection UserData () @@ -96,13 +96,13 @@ update :: Affection UserData () update = do traceM "updating" ud <- getAffection - let last = lastTick ud - tick <- getTick - putAffection $ ud { lastTick = tick } - let dt = tick - last + -- let last = lastTick ud + -- tick <- getTick + -- putAffection $ ud { lastTick = tick } + dt <- getDelta traceM $ (show $ 1 / dt) ++ " FPS" - - when (tick > 20) $ + elapsed <- getElapsedTime + when (elapsed > 20) $ quit clean :: UserData -> IO () diff --git a/examples/example02.hs b/examples/example02.hs index 4d58f06..2870f78 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -10,7 +10,7 @@ import Debug.Trace main :: IO () main = do - conf <- return $ AffectionConfig + conf <- return AffectionConfig { initComponents = All , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow @@ -77,7 +77,7 @@ load _ = do actorMap <- return $ M.fromList [ ("rect", rectActor) ] - return $ UserData + return UserData { nodeGraph = myMap , actors = actorMap , foreground = buffer @@ -100,13 +100,13 @@ update :: Affection UserData () update = do traceM "updating" - tick <- getTick + tick <- getElapsedTime ud <- getAffection putAffection $ ud { lastTick = tick } let dt = tick - lastTick ud return () - traceM $ (show $ 1 / dt) ++ " FPS" + traceM $ show (1 / dt) ++ " FPS" handle :: SDL.EventPayload -> Affection UserData () handle (SDL.MouseMotionEvent dat) = do @@ -114,11 +114,11 @@ handle (SDL.MouseMotionEvent dat) = do ud <- getAffection nmap <- return $ M.adjust - (\a -> Actor (props $ do + (Actor (props $ do prop "y" (fromIntegral (y - 10) :: Double) prop "x" (fromIntegral (x - 10) :: Double) ) - (actorNode a) + . actorNode ) "rect" (actors ud) diff --git a/examples/example03.hs b/examples/example03.hs index 75dcccd..2c633fb 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -42,10 +42,14 @@ load _ = do traceM "loading" root <- G.gegl_node_new traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation - [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 - , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 - ] + rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ + props $ do + prop "x" (0 :: Double) + prop "y" (0 :: Double) + prop "width" (800 :: Double) + prop "height" (600 :: Double) + prop "color" $ G.RGB 0 0 0 + traceM "rect" traceM "checkerboard" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" @@ -62,13 +66,12 @@ load _ = do , G.Property "height" $ G.PropertyDouble 600 ] traceM "crop" - G.gegl_node_link_many [checkerboard, over, crop, sink] + G.gegl_node_link_many [rect, over, crop, sink] G.gegl_node_connect_to nop "output" over "aux" traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) , ("over" , over) - , ("background" , checkerboard) , ("sink" , sink) , ("nop" , nop) , ("crop" , crop) @@ -81,29 +84,21 @@ load _ = do } drawInit :: Affection UserData () -drawInit = do - UserData{..} <- getAffection - present (GeglRectangle 0 0 800 600) foreground True +drawInit = return () +-- drawInit = do +-- UserData{..} <- getAffection +-- present (GeglRectangle 0 0 800 600) foreground True draw :: Affection UserData () draw = do traceM "drawing" UserData{..} <- getAffection + drawParticleSystem partsys partDraw process $ nodeGraph M.! "sink" - -- ad <- get - -- ud <- getAffection - -- drawParticles partDraw $ particles ud - -- SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions $ drawSurface ad - -- let (w, h) = (fromIntegral rw, fromIntegral rh) - -- liftIO $ clearArea (foreground ud) (GeglRectangle 0 0 w h) - -- maybe (return ()) (\(x, y) -> - -- drawRect - -- (foreground ud) - -- (nodeGraph ud M.! "over") - -- (G.RGBA 1 0 0 0.5) - -- (Line 7) - -- (G.GeglRectangle (x - 10) (y - 10) 20 20) - -- ) $ coordinates ud + present + (G.GeglRectangle 0 0 800 600) + foreground + True update :: Affection UserData () update = do @@ -111,66 +106,61 @@ update = do ad <- get ud <- getAffection delta <- getDelta - -- let newPart = updateParticles delta partUpd $ particles ud - -- putAffection $ ud { particles = newPart } traceM $ (show $ 1 / delta) ++ " FPS" - -- ev <- liftIO $ SDL.pollEvents ud2 <- getAffection - !nps <- updateParticleSystem (partsys ud2) delta partUpd partDraw + !nps <- updateParticleSystem (partsys ud2) delta partUpd putAffection $ ud2 { partsys = nps } handle (SDL.MouseMotionEvent dat) = - if SDL.ButtonLeft `elem` SDL.mouseMotionEventState dat - then do - ad <- get - ud <- getAffection - let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat - vx <- liftIO $ randomRIO (-20, 20) - vy <- liftIO $ randomRIO (-20, 20) - life <- liftIO $ randomRIO (1, 5) - r <- liftIO $ randomRIO (0,1) - g <- liftIO $ randomRIO (0,1) - b <- liftIO $ randomRIO (0,1) - tempRoot <- liftIO $ G.gegl_node_new - tempOver <- liftIO $ G.gegl_node_new_child tempRoot - G.defaultOverOperation - tempRect <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation - "gegl:rectangle" - [ G.Property "x" $ G.PropertyDouble $ fromIntegral x - 10 - , G.Property "y" $ G.PropertyDouble $ fromIntegral y - 10 - , G.Property "width" $ G.PropertyDouble 20 - , G.Property "height" $ G.PropertyDouble 20 - , G.Property "color" $ G.PropertyColor $ (G.RGBA r g b 0.5) - ] - liftIO $ G.gegl_node_connect_to tempRect "output" tempOver "aux" - -- traceM $ "position is: " ++ show x ++ " " ++ show y - -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy - ips <- insertParticle (partsys ud) $ - Particle - { particleTimeToLive = life - , particleCreation = elapsedTime ad - , particlePosition = (fromIntegral x, fromIntegral y) - , particleRotation = Rad 0 - , particleVelocity = (vx, vy) - , particlePitchRate = Rad 0 - , particleRootNode = tempRoot - , particleNodeGraph = M.fromList - [ ("root", tempRoot) - , ("over", tempOver) - , ("rect", tempRect) - ] - , particleStackCont = tempOver - , particleDrawFlange = tempOver - } - putAffection $ ud - { partsys = ips - } - -- when (not $ null $ psParts $ partsys ud) $ - -- liftIO $ G.gegl_node_link - -- tempOver - -- (particleStackCont $ head $ psParts $ partsys ud) - else - return () + when (SDL.ButtonLeft `elem` SDL.mouseMotionEventState dat) + $ do + ad <- get + ud <- getAffection + let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat + vx <- liftIO $ randomRIO (-20, 20) + vy <- liftIO $ randomRIO (-20, 20) + life <- liftIO $ randomRIO (1, 5) + r <- liftIO $ randomRIO (0,1) + g <- liftIO $ randomRIO (0,1) + b <- liftIO $ randomRIO (0,1) + tempRoot <- liftIO $ G.gegl_node_new + tempOver <- liftIO $ G.gegl_node_new_child tempRoot + G.defaultOverOperation + tempRect <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation + "gegl:rectangle" + [ G.Property "x" $ G.PropertyDouble $ fromIntegral x - 10 + , G.Property "y" $ G.PropertyDouble $ fromIntegral y - 10 + , G.Property "width" $ G.PropertyDouble 20 + , G.Property "height" $ G.PropertyDouble 20 + , G.Property "color" $ G.PropertyColor $ (G.RGBA r g b 0.5) + ] + liftIO $ G.gegl_node_connect_to tempRect "output" tempOver "aux" + -- traceM $ "position is: " ++ show x ++ " " ++ show y + -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy + ips <- insertParticle (partsys ud) $ + Particle + { particleTimeToLive = life + , particleCreation = elapsedTime ad + , particlePosition = (fromIntegral x, fromIntegral y) + , particleRotation = Rad 0 + , particleVelocity = (vx, vy) + , particlePitchRate = Rad 0 + , particleRootNode = tempRoot + , particleNodeGraph = M.fromList + [ ("root", tempRoot) + , ("over", tempOver) + , ("rect", tempRect) + ] + , particleStackCont = tempOver + , particleDrawFlange = tempOver + } + putAffection $ ud + { partsys = ips + } + -- when (not $ null $ psParts $ partsys ud) $ + -- liftIO $ G.gegl_node_link + -- tempOver + -- (particleStackCont $ head $ psParts $ partsys ud) handle (SDL.WindowClosedEvent _) = do traceM "seeya!" @@ -180,7 +170,8 @@ handle _ = return () clean :: UserData -> IO () -clean _ = return () +clean ud = do + G.gegl_node_drop (nodeGraph ud M.! "root") partUpd :: Double -> Particle -> Affection UserData Particle partUpd sec p = do diff --git a/src/Affection.hs b/src/Affection.hs index b18bd48..dc82f06 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -9,7 +9,7 @@ module Affection , delaySec , get , put - , getTick + , getElapsedTime , getDelta , quit , module A @@ -81,8 +81,8 @@ withAffection AffectionConfig{..} = do pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface let (w, h) = (fromIntegral rw, fromIntegral rh) - stride = (fromIntegral $ Raw.pixelFormatBytesPerPixel pixelFormat) * w - pixels <- SDL.surfacePixels $ surface + stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w + pixels <- SDL.surfacePixels surface let bablFormat = B.PixelFormat B.RGBA B.CFu8 cpp = B.babl_components_per_pixel bablFormat format <- B.babl_format bablFormat @@ -116,9 +116,9 @@ withAffection AffectionConfig{..} = do -- clean draw requests from last run mapM_ (invalidateDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time - SDL.clear renderer + -- SDL.clear renderer -- compute dt and update elapsedTime - let !dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9) + let !dt = fromIntegral (toNanoSecs $ diffTimeSpec lastTime now) / (10 ^ 9) !ne = elapsedTime ad + dt put $ ad { drawStack = [] @@ -147,7 +147,7 @@ withAffection AffectionConfig{..} = do -- clean the texture SDL.destroyTexture texture -- save new time - liftIO $ writeIORef execTime $ now + liftIO $ writeIORef execTime now ) ) initContainer G.gegl_exit @@ -159,7 +159,7 @@ getSurfaces :: SDL.Window -> IO (SDL.Surface, SDL.Surface) getSurfaces window = do oldSurf@(SDL.Surface ptr _) <- SDL.getWindowSurface window rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 - let surface = (flip SDL.Surface Nothing) rawSurfacePtr + let surface = SDL.Surface rawSurfacePtr Nothing return (oldSurf, surface) -- Prehandle SDL events in case any window events occur @@ -198,8 +198,8 @@ delaySec delaySec dur = SDL.delay (fromIntegral $ dur * 1000) -- | Get time since start but always the same in the current tick. -getTick :: Affection us Double -getTick = +getElapsedTime :: Affection us Double +getElapsedTime = elapsedTime <$> get getDelta :: Affection us Double diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 7618d14..20620b6 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE RecordWildCards, BangPatterns #-} +{-# LANGUAGE RecordWildCards #-} -- | Module for drawing primitives module Affection.Draw @@ -40,7 +40,7 @@ drawRect -> Affection us () drawRect node color Fill rect@GeglRectangle{..} buf = do ad <- get - tempRoot <- liftIO $ G.gegl_node_new + tempRoot <- liftIO G.gegl_node_new opNode <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation "gegl:rectangle" [ G.Property "x" $ G.PropertyDouble $ fromIntegral rectangleX , G.Property "y" $ G.PropertyDouble $ fromIntegral rectangleY @@ -51,7 +51,7 @@ drawRect node color Fill rect@GeglRectangle{..} buf = do diw <- liftIO $ G.gegl_node_connect_to opNode "output" node "input" unless diw $ error "Affection.Draw.drawRect: connect failed" put $ ad - { drawStack = (DrawRequest rect buf (Kill (Just tempRoot))) : drawStack ad + { drawStack = DrawRequest rect buf (Kill (Just tempRoot)) : drawStack ad } -- | Force update of a specific region on screen @@ -64,7 +64,7 @@ present rect buf kill = do ad <- get let k = if not kill then Kill Nothing else Persist put ad - { drawStack = (DrawRequest rect buf k) : drawStack ad + { drawStack = DrawRequest rect buf k : drawStack ad } process @@ -106,20 +106,9 @@ handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do let surf = drawSurface ad mrealRect <- liftIO $ G.gegl_rectangle_intersect requestArea - (G.GeglRectangle 0 0 (fst $ drawDimensions ad) (snd $ drawDimensions ad)) - maybe (return()) (\realRect -> do - liftIO $ SDL.lockSurface surf - liftIO $ G.gegl_buffer_get - requestBuffer - (Just realRect) - 1 - (Just $ drawFormat ad) - (pixels `plusPtr` - (rectangleX realRect * cpp + rectangleY realRect * stride)) - stride - G.GeglAbyssNone - liftIO $ SDL.unlockSurface surf - -- liftIO $ SDL.updateWindowSurface $ drawWindow ad + (uncurry (G.GeglRectangle 0 0) (drawDimensions ad)) + maybe (return()) (\realRect -> + putToSurface pixels realRect stride cpp dr ) mrealRect case requestPersist of Persist -> @@ -139,21 +128,10 @@ invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do ad <- get mrealRect <- liftIO $ G.gegl_rectangle_intersect requestArea - (G.GeglRectangle 0 0 (fst $ drawDimensions ad) (snd $ drawDimensions ad)) + (uncurry (G.GeglRectangle 0 0) (drawDimensions ad)) maybe (return()) (\realRect -> do - let surf = drawSurface ad liftIO $ clearArea requestBuffer realRect - liftIO $ SDL.lockSurface surf - liftIO $ G.gegl_buffer_get - requestBuffer - (Just realRect) - 1 - (Just $ drawFormat ad) - (pixels `plusPtr` - (rectangleX realRect * cpp + rectangleY realRect * stride)) - stride - G.GeglAbyssNone - liftIO $ SDL.unlockSurface surf + putToSurface pixels realRect stride cpp dr ) mrealRect case requestPersist of Kill (Just victim) -> @@ -177,7 +155,7 @@ colorize (rr, rg, rb, ra) col = G.RGB r g b -> (r, g, b) ca = case col of G.RGBA _ _ _ a -> a - G.RGB _ _ _ -> 1 + G.RGB{} -> 1 alpha = ca dst_a = ba da = alpha + dst_a * (1 - alpha) @@ -189,7 +167,7 @@ colorize (rr, rg, rb, ra) col = ( G.CVdouble $ CDouble $ red / da , G.CVdouble $ CDouble $ gre / da , G.CVdouble $ CDouble $ blu / da - , G.CVdouble $ CDouble $ ca + , G.CVdouble $ CDouble ca ) unsafeColorize col = @@ -199,12 +177,12 @@ unsafeColorize col = G.RGB cr cg cb -> (cr, cg, cb) a = case col of G.RGBA _ _ _ ca -> ca - G.RGB _ _ _ -> 1 + G.RGB{} -> 1 in - ( G.CVdouble $ CDouble $ r - , G.CVdouble $ CDouble $ g - , G.CVdouble $ CDouble $ b - , G.CVdouble $ CDouble $ a + ( G.CVdouble $ CDouble r + , G.CVdouble $ CDouble g + , G.CVdouble $ CDouble b + , G.CVdouble $ CDouble a ) -- | Clear a specified area of a buffer from all data diff --git a/src/Affection/Property.hs b/src/Affection/Property.hs index 1caa42c..78e209f 100644 --- a/src/Affection/Property.hs +++ b/src/Affection/Property.hs @@ -1,7 +1,7 @@ {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} module Affection.Property - ( Props(..) + ( Props , prop , props ) where @@ -20,7 +20,7 @@ props = flip execState [] prop :: IsPropertyValue v => String -> v -> Props () prop k v = do ps <- get - put $ (G.Property k (toPropertyValue v)) : ps + put $ G.Property k (toPropertyValue v) : ps class IsPropertyValue v where toPropertyValue :: v -> G.PropertyValue From 9744ebcf992e5f3ac29d510c284ba2f1f3dde8b8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Mar 2017 05:26:07 +0100 Subject: [PATCH 116/261] version bump --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index c1bc844..7a0a3c2 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.5 +version: 0.0.0.6 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL and GEGL. From 33ca2220ea0df725d82dcca47cbbe05ea0f1c3d6 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 20 Mar 2017 05:26:24 +0100 Subject: [PATCH 117/261] on how not to do it --- affection.cabal | 17 ++++ examples/example02.1.hs | 169 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 examples/example02.1.hs diff --git a/affection.cabal b/affection.cabal index 7a0a3c2..a63cb4d 100644 --- a/affection.cabal +++ b/affection.cabal @@ -122,6 +122,23 @@ executable example02 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 diff --git a/examples/example02.1.hs b/examples/example02.1.hs new file mode 100644 index 0000000..aa2d443 --- /dev/null +++ b/examples/example02.1.hs @@ -0,0 +1,169 @@ +{-# LANGUAGE RecordWildCards #-} + +import Affection +import qualified SDL +import qualified GEGL as G +import qualified BABL as B +import qualified Data.Map.Strict as M + +import Debug.Trace + +main :: IO () +main = do + conf <- return $ AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + , preLoop = return () + , eventLoop = handle + , updateLoop = update + , drawLoop = draw + , loadState = load + , cleanUp = clean + } + withAffection conf + +data UserData = UserData + -- { nodeGraph :: M.Map String G.GeglNode + -- , foreground :: G.GeglBuffer + -- , lastTick :: Double + -- } + { coordinates :: (Double, Double) + , lastTick :: Double + } + +load :: SDL.Surface -> IO UserData +load _ = do + -- traceM "loading" + -- root <- G.gegl_node_new + -- traceM "new root node" + -- checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ + -- props $ do + -- prop "color1" $ G.RGBA 0.4 0.4 0.4 1 + -- prop "color2" $ G.RGBA 0.6 0.6 0.6 1 + -- traceM "checkerboard" + -- over <- G.gegl_node_new_child root G.defaultOverOperation + -- traceM "over" + -- buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< + -- B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + -- sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ + -- props $ + -- prop "buffer" buffer + -- traceM "buffer-sink" + -- rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ + -- props $ do + -- prop "x" (0::Double) + -- prop "y" (0::Double) + -- prop "width" (20::Double) + -- prop "height" (20::Double) + -- prop "color" $ G.RGBA 1 0 0 0.5 + -- traceM "rect" + -- crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ + -- props $ do + -- prop "width" (800::Double) + -- prop "height" (600::Double) + -- G.gegl_node_link_many [checkerboard, over, crop, sink] + -- _ <- G.gegl_node_connect_to rect "output" over "aux" + -- traceM "connections made" + -- myMap <- return $ M.fromList + -- [ ("root" , root) + -- , ("over" , over) + -- , ("background" , checkerboard) + -- , ("sink" , sink) + -- , ("rect" , rect) + -- , ("crop" , crop) + -- ] + -- traceM "loading complete" + -- return $ UserData + -- { nodeGraph = myMap + -- , foreground = buffer + -- , lastTick = 0 + -- } + return $ UserData + { coordinates = (400, 300) + , lastTick = 0 + } + +-- drawInit :: Affection UserData () +-- drawInit = do +-- UserData{..} <- getAffection +-- present (GeglRectangle 0 0 800 600) foreground True + +draw :: Affection UserData () +draw = do + UserData{..} <- getAffection + traceM "loading" + root <- liftIO $ G.gegl_node_new + traceM "new root node" + checkerboard <- liftIO $ G.gegl_node_new_child root $ G.checkerboardOperation $ + props $ do + prop "color1" $ G.RGBA 0.4 0.4 0.4 1 + prop "color2" $ G.RGBA 0.6 0.6 0.6 1 + traceM "checkerboard" + over <- liftIO $ G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + buffer <- liftIO $ G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< + B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + sink <- liftIO $ G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ + props $ + prop "buffer" buffer + traceM "buffer-sink" + rect <- liftIO $ G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ + props $ do + prop "x" $ fst coordinates + prop "y" $ snd coordinates + prop "width" (20::Double) + prop "height" (20::Double) + prop "color" $ G.RGBA 1 0 0 0.5 + traceM "rect" + crop <- liftIO $ G.gegl_node_new_child root $ G.Operation "gegl:crop" $ + props $ do + prop "width" (800::Double) + prop "height" (600::Double) + liftIO $ G.gegl_node_link_many [checkerboard, over, crop, sink] + _ <- liftIO $ G.gegl_node_connect_to rect "output" over "aux" + traceM "connections made" + myMap <- return $ M.fromList + [ ("root" , root) + , ("over" , over) + , ("background" , checkerboard) + , ("sink" , sink) + , ("rect" , rect) + , ("crop" , crop) + ] + traceM "loading complete" + process (myMap M.! "sink") + present (GeglRectangle 0 0 800 600) buffer True + +update :: Affection UserData () +update = do + traceM "updating" + + tick <- getElapsedTime + ud <- getAffection + putAffection $ ud { lastTick = tick } + + let dt = tick - lastTick ud + return () + traceM $ (show $ 1 / dt) ++ " FPS" + +handle (SDL.MouseMotionEvent dat) = do + let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat + ud <- getAffection + putAffection ud + { coordinates = (fromIntegral (x - 10), fromIntegral (y - 10)) + } + -- liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" $ + -- props $ do + -- prop "x" (fromIntegral (x - 10) :: Double) + -- prop "y" $ (fromIntegral (y - 10) :: Double) + +handle (SDL.WindowClosedEvent _) = do + traceM "seeya!" + quit + +handle _ = + return () + +clean :: UserData -> IO () +clean _ = return () From 85c6288f5050bb6094c461b67f8c2fc194f6898a Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 21 Mar 2017 12:04:56 +0100 Subject: [PATCH 118/261] better actors --- affection.cabal | 1 + examples/example02.hs | 44 +++++++++++++++++++++++--------------- src/Affection/Actor.hs | 48 +++++++++++++++++++++++++++++++++++++++--- src/Affection/Types.hs | 2 +- 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/affection.cabal b/affection.cabal index a63cb4d..1ec0293 100644 --- a/affection.cabal +++ b/affection.cabal @@ -119,6 +119,7 @@ executable example02 , babl , containers , mtl + , monad-parallel else buildable: False diff --git a/examples/example02.hs b/examples/example02.hs index 2870f78..633ff55 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -5,6 +5,7 @@ import qualified SDL import qualified GEGL as G import qualified BABL as B import qualified Data.Map.Strict as M +import qualified Control.Monad.Parallel as MP import Debug.Trace @@ -24,10 +25,11 @@ main = do withAffection conf data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - , actors :: M.Map String Actor - , foreground :: G.GeglBuffer - , lastTick :: Double + { nodeGraph :: M.Map String G.GeglNode + , actors :: M.Map String (Actor String) + , foreground :: G.GeglBuffer + , lastTick :: Double + , updateActors :: [Actor String] } load :: SDL.Surface -> IO UserData @@ -63,7 +65,13 @@ load _ = do prop "height" (600::Double) G.gegl_node_link_many [checkerboard, over, crop, sink] _ <- G.gegl_node_connect_to rect "output" over "aux" - let rectActor = Actor rectProps rect + rectActor <- return $ Actor (map + (\p -> ActorProperty + (G.propertyName p) + (G.propertyValue p) + (Just "rect") + )rectProps + ) (M.singleton "rect" rect) traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) @@ -78,10 +86,11 @@ load _ = do [ ("rect", rectActor) ] return UserData - { nodeGraph = myMap - , actors = actorMap - , foreground = buffer - , lastTick = 0 + { nodeGraph = myMap + , actors = actorMap + , foreground = buffer + , lastTick = 0 + , updateActors = [] } -- drawInit :: Affection UserData () @@ -91,8 +100,11 @@ load _ = do draw :: Affection UserData () draw = do - UserData{..} <- getAffection - mapM_ (\(Actor ps node) -> liftIO $ G.gegl_node_set node $ G.Operation "" ps) actors + ud@UserData{..} <- getAffection + MP.mapM_ applyProperties updateActors + putAffection ud + { updateActors = [] + } process (nodeGraph M.! "sink") present (GeglRectangle 0 0 800 600) foreground True @@ -114,16 +126,14 @@ handle (SDL.MouseMotionEvent dat) = do ud <- getAffection nmap <- return $ M.adjust - (Actor (props $ do - prop "y" (fromIntegral (y - 10) :: Double) - prop "x" (fromIntegral (x - 10) :: Double) - ) - . actorNode + (updateProperty ("y", G.PropertyDouble (fromIntegral (y - 10))) . + updateProperty ("x", G.PropertyDouble (fromIntegral (x - 10))) ) "rect" (actors ud) putAffection ud - { actors = nmap + { actors = nmap + , updateActors = (actors ud M.! "rect") : [] } -- liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" $ -- props $ do diff --git a/src/Affection/Actor.hs b/src/Affection/Actor.hs index 2e2a5bf..cd773c6 100644 --- a/src/Affection/Actor.hs +++ b/src/Affection/Actor.hs @@ -1,12 +1,54 @@ +{-# LANGUAGE RecordWildCards #-} + -- | This module implements the Actor, a Datastructure binding a 'G.GeglNode' -- to a game asset module Affection.Actor ( Actor(..) + , ActorProperty(..) + , updateProperties + , applyProperties ) where import qualified GEGL as G -data Actor = Actor - { actorProperties :: [G.Property] - , actorNode :: G.GeglNode +import qualified Data.Map.Strict as M +import Data.List +import qualified Control.Monad.Parallel as MP + +import Affection.Types + +data Ord a => Actor a = Actor + { actorProperties :: [ActorProperty a] + , actorNodes :: M.Map a G.GeglNode } + +data Ord a => ActorProperty a = ActorProperty + { apName :: String + , apValue :: G.PropertyValue + , apMapping :: Maybe a + } + +updateProperties :: Ord a => [G.Property] -> Actor a -> Actor a +updateProperties ps act@Actor{..} = + act + { actorProperties = + foldl (\acc (G.Property name val) -> newProp name val acc) actorProperties ps + } + where + newProp name val acc = nubBy (\a b -> apName a == apName b) $ (getProp name) + { apValue = val + } : acc + getProp n = getProp' n actorProperties + getProp' n [] = error $ "no ActorProperty found: " ++ n + getProp' n (p:ps) + | apName p == n = p + | otherwise = getProp' n ps + +applyProperties :: Ord a => Actor a -> Affection us () +applyProperties Actor{..} = + MP.mapM_ (\(ActorProperty{..}) -> + maybe (return ()) (\m -> + liftIO $ G.gegl_node_set (actorNodes M.! m) $ G.Operation "" $ + (G.Property apName apValue) : [] + ) apMapping + ) actorProperties diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index a0fad1d..d659cb4 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -33,7 +33,7 @@ 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 +import Data.Map.Strict as M import qualified GEGL as G import qualified BABL as B From efbfe14fe8e86dde7a9ccb43a737b823c90a9a39 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 21 Mar 2017 12:12:46 +0100 Subject: [PATCH 119/261] incorporate in example02 --- examples/example02.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index 633ff55..013ab86 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -126,8 +126,9 @@ handle (SDL.MouseMotionEvent dat) = do ud <- getAffection nmap <- return $ M.adjust - (updateProperty ("y", G.PropertyDouble (fromIntegral (y - 10))) . - updateProperty ("x", G.PropertyDouble (fromIntegral (x - 10))) + (updateProperties $ props $ do + prop "x" (fromIntegral (x - 10) :: Double) + prop "y" (fromIntegral (y - 10) :: Double) ) "rect" (actors ud) From 22877d239bf16b72b7f7a898ef5e7e4a75d746c5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 21 Mar 2017 18:01:58 +0100 Subject: [PATCH 120/261] example04 (side scrolling example) finished --- affection.cabal | 35 ++--- examples/example04.hs | 259 +++++++++++++++++++++++--------- examples/example04/panorama.jpg | Bin 0 -> 409609 bytes 3 files changed, 204 insertions(+), 90 deletions(-) create mode 100644 examples/example04/panorama.jpg diff --git a/affection.cabal b/affection.cabal index 1ec0293..8b3959c 100644 --- a/affection.cabal +++ b/affection.cabal @@ -158,20 +158,21 @@ executable example03 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 --- 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 diff --git a/examples/example04.hs b/examples/example04.hs index a720487..dcd1335 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -4,117 +4,230 @@ import Affection import qualified SDL import qualified GEGL as G import qualified BABL as B -import qualified BABL.FFI.Format as B import qualified Data.Map.Strict as M - -import Foreign.C.Types -import Foreign.Ptr (castPtr, nullPtr) -import Foreign.Marshal.Utils (new) - -import System.Random (randomRIO) - -import Control.Monad (unless) +import Data.List as L +import Data.Maybe (fromJust) +import Control.Monad (when) +import qualified Control.Monad.Parallel as MP import Debug.Trace main :: IO () main = do - conf <- return $ AffectionConfig + conf <- return AffectionConfig { initComponents = All , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow , preLoop = return () - , drawLoop = draw + , eventLoop = handle , updateLoop = update + , drawLoop = draw , loadState = load , cleanUp = clean } withAffection conf data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - , coordinates :: Maybe (Int, Int) + { nodeGraph :: M.Map String G.GeglNode + , actors :: M.Map String (Actor String) + , foreground :: G.GeglBuffer + , lastTick :: Double + , updateActors :: [Actor String] } load :: SDL.Surface -> IO UserData -load surface = do +load _ = do traceM "loading" root <- G.gegl_node_new traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation - [ G.Property "color1" $ G.PropertyColor $ G.RGBA 0.4 0.4 0.4 1 - , G.Property "color2" $ G.PropertyColor $ G.RGBA 0.6 0.6 0.6 1 - ] - traceM "checkerboard" + let bgProps = props $ prop "path" ("examples/example04/panorama.jpg" :: String) + bg <- G.gegl_node_new_child root $ G.Operation "gegl:jpg-load" bgProps + bgScaleProps <- return $ props $ do + prop "y" (600 :: Double) + prop "sampler" (fromEnum G.GeglSamplerCubic) + bgScale <- G.gegl_node_new_child root $ + G.Operation "gegl:scale-size-keepaspect" bgScaleProps + bgTransProps <- return $ props $ do + prop "x" (0 :: Double) + prop "y" (0 :: Double) + prop "sampler" (fromEnum G.GeglSamplerCubic) + bgTrans <- G.gegl_node_new_child root $ G.Operation "gegl:translate" bgTransProps + traceM "background" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] - traceM "nop" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" - [ G.Property "x" $ G.PropertyDouble 0 - , G.Property "y" $ G.PropertyDouble 0 - , G.Property "width" $ G.PropertyDouble 800 - , G.Property "height" $ G.PropertyDouble 600 - ] - traceM "crop" - format <- B.babl_format $ B.PixelFormat B.RGBA B.CFu8 - pixels <- liftIO $ new =<< SDL.surfacePixels surface - sink <- G.gegl_node_new_child root $ G.Operation "gegl:buffer-sink" - [ G.Property "buffer" $ G.PropertyPointer $ castPtr $ pixels - , G.Property "format" $ G.PropertyPointer $ nullPtr - ] - traceM "sink" - G.gegl_node_link_many [checkerboard, over, crop, sink] - diw <- G.gegl_node_connect_to nop "output" over "aux" - unless diw (error "connect failed") + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< + B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ + props $ + prop "buffer" buffer + traceM "buffer-sink" + rectProps <- return $ props $ do + prop "x" (50 :: Double) + prop "y" (290 :: Double) + prop "width" (20::Double) + prop "height" (20::Double) + prop "color" $ G.RGBA 1 0 0 0.5 + rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" rectProps + traceM "rect" + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ + props $ do + prop "width" (800::Double) + prop "height" (600::Double) + G.gegl_node_link_many [bg, bgTrans, bgScale, over, crop, sink] + _ <- G.gegl_node_connect_to rect "output" over "aux" + rectActor <- return $ Actor (map + (\p -> ActorProperty + (G.propertyName p) + (G.propertyValue p) + (Just "rect") + )rectProps + ) (M.singleton "rect" rect) + bgActor <- return $ Actor (map + (\p -> ActorProperty + (G.propertyName p) + (G.propertyValue p) + (Just "bg") + )bgProps ++ + map (\p -> ActorProperty + (G.propertyName p) + (G.propertyValue p) + (Just "scale") + ) bgScaleProps ++ + map (\p -> ActorProperty + (G.propertyName p) + (G.propertyValue p) + (Just "trans") + ) bgTransProps + ) (M.fromList + [ ("bg", bg) + , ("trans", bgTrans) + , ("scale", bgScale) + ]) traceM "connections made" myMap <- return $ M.fromList [ ("root" , root) , ("over" , over) - , ("background" , checkerboard) - , ("nop" , nop) - , ("crop" , crop) + , ("background" , bg) , ("sink" , sink) + , ("rect" , rect) + , ("crop" , crop) ] traceM "loading complete" - return $ UserData - { nodeGraph = myMap - , coordinates = Nothing + actorMap <- return $ M.fromList + [ ("rect", rectActor) + , ("background", bgActor) + ] + return UserData + { nodeGraph = myMap + , actors = actorMap + , foreground = buffer + , lastTick = 0 + , updateActors = [] } -update :: Double -> AffectionState (AffectionData UserData) IO () -update sec = do - traceM "updating" - ad <- get - ud <- getAffection - traceM $ (show $ 1 / sec) ++ " FPS" - ev <- liftIO $ SDL.pollEvents - mapM_ (\e -> - case SDL.eventPayload e of - SDL.MouseButtonEvent dat -> do - let (SDL.P (SDL.V2 x y)) = SDL.mouseButtonEventPos dat - drawRect - (nodeGraph ud M.! "nop") - (G.RGBA 1 0 0 0.5) - Fill - (G.GeglRectangle (fromIntegral x - 10) (fromIntegral y - 10) 20 20) - traceM $ "drewn a rectangle at " ++ show x ++ " " ++ show y - SDL.WindowClosedEvent _ -> do - traceM "seeya!" - put $ ad - { quitEvent = True - } - _ -> - return () - ) ev +-- drawInit :: Affection UserData () +-- drawInit = do +-- UserData{..} <- getAffection +-- present (GeglRectangle 0 0 800 600) foreground True draw :: Affection UserData () draw = do - ad <- get + ud@UserData{..} <- getAffection + MP.mapM_ applyProperties updateActors + putAffection ud + { updateActors = [] + } + process (nodeGraph M.! "sink") + present (GeglRectangle 0 0 800 600) foreground True + +update :: Affection UserData () +update = do + traceM "updating" + + tick <- getElapsedTime ud <- getAffection - liftIO $ SDL.lockSurface $ drawSurface ad - liftIO $ G.gegl_node_process (nodeGraph ud M.! "sink") - liftIO $ SDL.unlockSurface $ drawSurface ad + putAffection $ ud { lastTick = tick } + + let dt = tick - lastTick ud + return () + traceM $ show (1 / dt) ++ " FPS" + +handle :: SDL.EventPayload -> Affection UserData () +handle (SDL.KeyboardEvent dat) = + when (SDL.keyboardEventKeyMotion dat == SDL.Pressed) $ do + ud <- getAffection + (G.PropertyDouble xpos) <- + return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ + actorProperties $ actors ud M.! "rect" + if xpos >= 40 && xpos <= 760 + then do + nmap <- return $ M.adjust + (updateProperties $ props $ prop "x" $ + case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of + SDL.KeycodeLeft -> xpos - 5 + SDL.KeycodeRight -> xpos + 5 + _ -> xpos + ) + "rect" + (actors ud) + putAffection ud + { actors = nmap + , updateActors = (nmap M.! "rect") : updateActors ud + } + else do + (G.PropertyDouble bgPos) <- + return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ + actorProperties $ actors ud M.! "background" + nmap <- return $ M.adjust + (updateProperties $ props $ prop "x" $ + if xpos < 40 + then + case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of + SDL.KeycodeLeft -> + if bgPos <= 0 then bgPos + 5 else bgPos + SDL.KeycodeRight -> + bgPos + _ -> bgPos + else + case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of + SDL.KeycodeLeft -> + bgPos + SDL.KeycodeRight -> + if True {- TODO: CHANGE-} then bgPos - 5 else bgPos + _ -> bgPos + ) + "background" + (actors ud) + nmap2 <- return $ M.adjust + (updateProperties $ props $ prop "x" $ + case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of + SDL.KeycodeLeft -> + if xpos < 40 + then + xpos + else + xpos - 5 + SDL.KeycodeRight -> + if xpos < 40 + then + xpos + 5 + else + xpos + _ -> xpos + ) + "rect" + nmap + putAffection ud + { actors = nmap2 + , updateActors = (nmap2 M.! "background") : (nmap2 M.! "rect") : updateActors ud + } + +handle (SDL.WindowClosedEvent _) = do + traceM "seeya!" + quit + +handle _ = + return () clean :: UserData -> IO () clean _ = return () diff --git a/examples/example04/panorama.jpg b/examples/example04/panorama.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9ed5014681f58d92a88bf97cb655adca19a187df GIT binary patch literal 409609 zcmd422Ut_v(l8tl6i^TlI7%}jC>`k%NC2fHy@o2iCrUsd6cr0ist~02-lT@kQJT^s zU5ap!5<)0K0!Y(u>pjPN@B7~O|L$MD=f6AI$tp9mX00`QX02JXzkmGx2Z&KiO;Zg- zK|ul106w7a3ly!Y{%+17kd6*W5Cj6zfX-2z2T=m4)4-ic!T2+M9!Td?u!E?9e+uBP zqu}_JZl&M{o%&f0U{Q$wgg*!5yC@`nrdfdW2MYP0=^wZt$_szO1G&&we=iSn15p9h zKVX)^`Tj~%|9ZHGW2{`L!2)iVOi-ngOiqx?r7eq#LmSJ%|h2TO?F6cxV-lDHu* z3%MZ)kq`$1327i93j&>q18x9=>V|~mjkr^P@karRqxzLLjHmb;30~=ZLg-5_Ld&iTT^z6B8G`AqG-V^uK54;Nj#0ws&%ILqM-@wRBtu zyE#Iyn@Q{3(7AWp$<<8*?d@cQ)-`rOdpO8BURQ+CDfmPDz3zEA`PhN|y*v>ph(GlD zPvQ_D{i9gyIsoMD=nOGbQTr1F$U(3F$%~(#pQxXtDALY7!e5Yo z2Q)#r`M^wppY9YyX9d{7HNf5u%#UzFBRX@uI&;9TNJj(`7;$ldKU@AUsJ}=WA)S5v z?7W>|8ot1kf`3S!sD$VZQG2JGj&?UBB*f)pZv08&7w9i!6+0g%V>d4+*uN0iKaqZc z{6bRp_Hje`fW`hk=J4D0x4rd|D7PPj<%V#9ncVY6qJXYBIsQrIzd`(>b{lywz}wBm z)dyzT-PW1oi0Evy2J@>SeUL8RcFqD|5%52jt2(;*Aie(@lfUEsBBu(-fT@!=>c_18 zNP{0Zd83^GEg<H(a%A?!T=jq&&e`rnAC05p8u&`ys329>|V{vvZ5 z>2(i@a6i<*)=$xOU z^bbl0P(*(#1C$w{*2I1(KOpl`L1pBG@rsHjh$ zp`it;Iet|Ay#A^M{aO8e8g%Xy1&EUI6eB?5K>t_$e`JEz!G--}Pu+IKowYqgjI@+F zX2Si&#Tzdo`+PH6$FwYy$_tv*;X|{yIS=#y$PWCwOi@%t`^;j8cji@bmdE=oIE%)S zQW`1OPd}KfecW{ z92cbB8d51gGL)+|P9M*h3o3n*qr}2^cSOnw{DD%LD6Fd^7=GJ0@_|Vy=0?l_;?-N? z3+|6^i8!%<|FjJ}6+s>@nMd4Q9xVD&k`?^1MyW(xV6slR zn<@)PNm-eaQR@m-E(H~b1|R~VG~i(-5#`g5o@k952$??%?HgoD8P9Tp+#3=6eWO2R z3zxFhhpGABH2fb@P2ne-x7_|D^DsnyLyq_w-;z?d-cw3feF-gz)>X&CEUREfl@fkb zhiCEVqZp_ui#`R2ih`0kTuX_WQJE^A@d}knJrz4RJTh^ZiaCzzX-$c4*~hPS@T7F+nzM@K@bb!G3j>t1^bvn}HVq{xfhX}%1Pvv9JryGrqid+)Em{pJWq~UK zOsrJ5nDYco-NUZvT4!cTqaDI4h>aj#XE~WJ(us; z?e<)@@#nf9F-iX~d89NaG?Lf+ch~&Mt6#}Am|U_4yDzmppOF^4$di^p`VR8z+p*PS z%Qs;hR^^0PUcw%1%_~?RTDjT169m!FJqpbZrzw6EDhOyMUR5m(Wk3*fH7ur8RF#>i z0MP_S*iN_4*V<*Unq}?zvL#zQ`)eyw@WGBv<|XKjf63DS!9ohOyu#dM2Cx0p#UeLOgw1Ii~MTs8ze6<(s60agO!oi@i zCq}0#<>rnL+FK7YXAg#Ei68zOyI!Pu2Rw1&3vpzdz*~8}W-N94wRJ6ft)^2*?OQ(_ zCE4W5$x-ui1i3?gXpi)g~xm(gulrXD4uo=4}Fw#mxqOtPu*(YCa5SRr~?7~!UNy^6VsYp>fCLy zX;`Pu${r~?$a`%=PmbHd-R!oyVjYU2Te#;N7>peHE}^pyk3N6CbSo>rk-%ZJVLQ28 zw!YIjnx(u0`xF!WN*=P&a@@rD2=J~kDubwuRdSj1m8q&rX#l-IBXv4LqePPynD02i z+A;w(r=x*K3Qo|S>@M+0lohZ#_zuGM52EG%iM2m7lrL9iF%!P_!p<{EBaLU_#p;ee zGRS(e{f zI`Up>*%_MsJ74s|9^(q!F`Z!^DIe(aF&5f{2DcPbebGxN;>+zZHg5B*(KuP_b}mmc z4jO#%q>+3a%+(P)D&fP~+|vk~&xj&!>(7Qr4+nh*6%W~jBOC|GLmhH&=qN#tz%<}T zJmHvJaHv-Jv=XB-D-$cz6{>uS)8WNDe7rL8jH-Y(p<*fQgo#8=E&L)tmK%lRlVy3rY>eKpl?Hy^4i3Xr{jhg_CLJQwj*Xb*Y2LNu_Jt2eDVlAlgkusAnT@QFsHXJ1|%tFnobK8t~! zlEEEVN*KJ0@B)_p1+th$z+ElW2Z4MPYDLmN+ygNhvzb-bJ^C-y|D7!L$JN@3*-z=8 zA|9X+{q<=AXlwpghOSz7-<93b@PMqu6qW2uTu&Y!Lmx-0ovUt=xg;bBj~tkAF&LDD zcsASWy=6Jhd|{hL&M`HzmrixALilrFQAKARRoIh8m=Zsz6h>2klo+)s3{;up1ng7` zSh)ouNt{=VjN-QXW60i1fjbq&Ac~@Br1#vG$d>oqJapZ`TZ^8JGv+cVN+h)!T?TiR z%@QN;kYJ6Iqz8nc%$TadxnKmmsWJoMXJZpcz>lItmMur$>T*XNw-9&c9=y(wbE-@h z9WcHu+;0s%RWxLw45k19)4~X7s~nAD9-bRY6f8gNFHp$DUZMex44>9;rO#I}W|wju z)3UE~rI(bAr9I5N#248Uq4W>qYB@NCA9cX&9&9;e?jCGzy2m6LjD<$>=bY81yGdD9NON}*~(p=6+XF^R(~WYK6YHchje0 zq?>TrG^>?0l85)rdHbcqUX_%;DamLx9#i^vCh#W=rR$n_Wf{lXHoDcLq^CKEm^)Or zprp^lsKv^3I$DKYooQ6H+VGUDOiz(wXNTU}j!e(F_$Pdxl57g@3TwcJkTR;HSMmp2 zSk!nD>12D9HKCrbsjMlESda)AFrgkWRd+O@k7tT543E%Y3B5%vNSQ0hV|Z3+$>2k- zKFxXVSWb1Tst~K#5Hk{*+Zw_E6?HDojWtnc2-Bb`U=k?SGdYuDeIF0YRQL{p`n2n* ztFLV36A_z!n(M-m*-fGNDDw6cq1X2P`nDc2t9ofGC^I#lb2@j-*WNi@RrcaU@a19B z8!#OeqXreDrs40(z(QX6be;-`37ADKKrNnT%6qI20z4kf32GxWfYqO2mvYbLsjIqc zc+qmGc*@M6!DK9&yUs7wvYR_)xn(D&OPwh?A4J(B+tMG%xj%8AP|#m6OHgIGmzx{I zV(|9XGyd1Pk^H%lWzZtkiZc-bsf!qAqkfhLQDY5eC})|B$pFepbNN~c1x=<`*f z&h__Sjx2RduZG`mW;YpC@l}hP?aY6m?r@s-fx1Dy&-C5L_B0?+q)Zp1Hj|2pvA!yc zfSo#nrpdbnH`PZ5>O%2daZ5o82L1Pfl`s7E!W!Ros5{e1oWM^{V4-6~7x=Tpf_cH0 z`2k}ilBmJ9^eTre>h*2UZL%M?qUm=KtO}EnUStdP$M#o+ zctUvu=_#o48C_|j8Lx;a0XthIU@;92rB{n)2k{m2@#vR8MXlTntcAt)tt!}L zWcsC$?{D~%$=qC=H$eusSd?<Eqxi^oQym%m z+(>oJ-hwGl=K>d>!StD^0-PVpnXl%SdjNQQYzc05ZM7y>DpJcL#nfmlr+jn*wa>ri zguEm_q)UouRR%=mpD)1W=bPTV8-(@^u{pVIajAxxH~xyaoSQJF{I#Crd+CHRjGPIG zEsjdT^&Br$Kk8kCkQeiKa=FljX0(Mx&RIGW1sa+1_!C;r%#Sc}o`vFaJ zv%{<@6O&wC2f^s{69vBy2WhbLUh|5DTig?0w{rKt@(`kDEu>}uOKb8w@bs&OCK}vj z8;@6IF>y-Tk4#&6wrO25)qi-?s42|6%saez3~r@;L8sY$Y2rB=*E@>;hM5RJY|jYy zRlbyM?GEDSiOdhJ0MmsQhtmsw1k_62>5+4S6=%~w-Dy)OnqSCk70m9jf_daEPGU*> zlRf1wrXsZ|JbhX?A zl_5EV%#s~tUHu2KU?CiAVHYRs7AI>LCLK6jXU7IzG`bwC!}&koa(tDlE$!NfS#Nkb zC9Un4xTMdVM$bsu6E4G?=5R;Xa)R(wjTS|p$TNE>aYw_wTWa9r%I7p`VYI)*w2iIm zWyNgMewK>(rdbdAxaZH~;ivP|@=hC@RDb<_r z^72$!jm?b>)am$N8$M!b_AZ!A5=|%gENfRmTDoo0vnN!9jKV^;mV(O}TE8l~bd_)M zG#jVVI;j?wMVNcn;$DJu}gE(;!VkihA7B7yLHDOp;O{mS43P7r?8dTXl z4f#6_SBjrqDL?FQdPB&%c6b@ux2#;=X3d{0S&8poT|FQ-Wek~Pf~MT<%@fQF-<+#q z7Jr?$;ZvL6pm8VW8gKm)25J+V^lrPe#KDO9=K&6z+_LK@R z#Kkp@{@Y7&JLZY6buo^^(vkV`_it6F+Hxu1h6XUe`sHj!)xPw$XpT4)weECiuNV<} z&vONHES(64q&w&!g6j^*-4m$#p@!-wlNsFUpQHn=_M6C)FD%QqKd&zqZ@avxCf+~ZMh{aMAZ2c1~@#f zM``PHPiBaz)7B1)@@^l>Bfg=Vhc>0vx&+s#XU#{P4BkcxpSimA#bs~tc9kHPe?xDX zVGzQtfHz6yQh5VT?Q53bnIc8`y<H@G4Dd#z9B9&9=Tk3#e4itTZxh zPX?TDxo}V4{1jEjj7yAL)pA!a#eB3qn+GwOpVIH;pl2M66y#10$@nY| zo0uZ*voWs1&-nA6g~djkDSOWYzs7exdaT2-+Lez==+g4`#92W2Ho1@XFHN;xE(+SN*rF8nZ7!klv>xy|9r{Ik8Ip;h2}HS=nT_vNDo^p9 z4Q=K+yGw8I!%>+ZrqlzhHmI0WBPq?tw9j(n!L6M|oHZ|vrU{!mjA>(tGFNXyMuzHv zLriNc%cikD0;ASXb;iOj1+06i*ws|MP*2II_Zc^!nf#kt=0XiSXvn-GQgJWf^^td6 zTW_$~;Gt>tNR`!xd2YY?Mc{O>HQ21yvTV*9Z!tJvVRGqrbnAq_$U!Z`b=2w}|LeTc z!tL1k!Oj!KG`}p3U10~4{parLD46dxU!J%@=b-6~8&-#UZ;!qPi{oKi(exwC5>s_> zrfU^0X}w*trA_;D$TGnFv-3%OEFjR*xVr_1Rn5$B?E125phSsLHN>cbjFt5b-qIMB z>uA+;PVmkqU;OknpsMBVu8E2Yy5$Osxytx*`wWga+fzQ+%xu1CQgZ2a17D_D!t{=s zt#Fheq(Jk`=;uE3hEEpSCQicRiK$t->V`3_EEWbz)S#ylzCuYa#SG&O`>vPYUp-V+ zzA~PYHn`}{NRdO^uWt3b5|}0*#2a2L<;G!9;Z5^cBj9dbG6Fe;?gu8-hZL3#>Tcc7)rD@Bj8wH7|8RX6S=P4OCoAI}A4z^v_Nm+V7qpnb2l$AtS zD?m@9rh8K?3eX6b_rcu*q1pW4Ds`c-&V{9@D^dvw?~8t8S~;}1$dkY`!C(03kMi~1 z<;EkVLxE_kf(tQ7(eg{Md=Qj<)T-fx1w~v6&XTlEPIGxbpGrPJ+gP^EhZ#hB%;KEb z2f&o7wHL~1ZGc@9cdbf(eq8Br7zRH&xcFhZtkq)iQ)2iG#fNiaJ~CNi2wJ+oGG;ArVqo(Bj0URvl4{FBTWry4fTBP)6CLI3e+)yUb%M<^<*h)3N*X z#@J^YFUndKZ6oE`erLe;udcP8_=_RaIIu#qyCYwNo4Fs_oG%(ISs~*O6~1zBu_V6K z?`z2%CIz31nkDtSM8mlZh3ti08p-Hc{@hpGS+W$n?U4GwWb^uzTIK%JuRpgR^cEL` z*s7~^zuIPZ5wc|WgDkMM)YmZA>b8q+M~}>SB`U7yIEO}$u~+Z z5dtYO8LA1L7QWB?hIN9U&A8s_NXqkr^D@RWwbzhG$gV+Iklys1ISy7?Y6-9A%*yJ} zoKM$Lm8KfML(Y@`yf`(8U%iX}luq{JIN;ux*-poCe;7(6jnwLwId0?hh$!DxM)pqW zL9B>VGOhO*Q!?GCbT=0KWTCk`*jqb-M>Rg`Wv0pI3bW{irY-qqaXzzKQhrPaZ?joYM8r;TZ<;%*l2ME8~HTv^)c~2_Sw@I2aSE$r%t5 z9Mlu|43UEEY<+ObH-S~T0djSWsR8Dyb#mh{>uBR<)9K`ckwCWzcsHeOR`k0}bX`E6<$M~KPl zB|b^+xIVM9uMkp!;5w*!7$t^fQNenIwZp44NcUA<#>l<6Uod{V(v938nSs2}v9)2K zZrF1sZxG+%u$a9dtgO#j>!R<$z(U2TlY!pCe+S)uw~zll5GJ#Kxn%GG-a}TK<98ws zOM38*Am%(;lUYOL64UFi%oK5RudrU88fz-S79H#h-_&PgWiiob0a3}(dz=L}CN2Z= zP_d*BDca!F&tkf6ty4wXrulvb4`J@7j_=DNGx(f;TuojaSfmFi($F{wShwGh9~ z%pt8{g-0u~yUl z2oCR=O9Ays^-N4y@MHUPts~3j1>vD{L&%{Auim#+%8fNwW7>nrRUyeR0mlTmX6_qr zofp0EyV#T~_Vvuhj-a!~#jbfJcdQ;P-b8=QG}`^9V{;sMK5cz)PCi6_O*XR_o5UXO z2{(yMvhs=%?$as_HIJjoHhf0|q6C&8+zzp}BAK7Z2~nJZELRQsR<$CRvQ^G#)ib;v zYAwINzO4dcbmEa|8EU_SQ%^%C>k4tLqSgmBq1?P1c%r}WEWX)Sw`|JYatdo7prU-b zOzi~^xS^ocMKh`Ja&0eL4fu@S3fY%Cq!pK!4y?P!h-LoGR#yA9INP9vcfThfD|UiK ziD7It-^~Az?Y;2H%OO!#_hs(EAz8G&CS6$A9v$=zwFiTkXP?7F@HSh?ulSLkJUT#>?S+)O2~iRg6MhFiIVI~MV%nNHeSchya(rEyebZdO z;2A456?;IHZYoNiAt-b6#eBARznY1{T16B6{j}Dhg7md55j|PkD$Xpk*z3c~F)Pm;QUCMCosl$%m8Z;iS z*@4#t^6{21Yx#dJuj3m`9U9C5wBU=u7ZG+s+BzOW+5!&-<)i5uF`oWUL2A|g>?Bsh zPu$jDipruFHadx$Z-Xy#C73Y``XAUpFDFiN$?7G|*$#~?PN;GA=WkaP6gT?|xjXHg zs3o5-tZ7(ikXcSngClAt40~qt_+=J^RkYb7?5`pWyHr$~R8+K?D9&&MeAafhm-!?u z+{U1Ac!BFQDuB;4t1)lIn`|v zE*svwU5l&DQFm_05E70uuoJE=-_2;XzkiY3l59M-F1xZin*%Nkuk92}N?flpt3~#s zuJ=80m!NE`_gXkCoh8k6@LK*(cU|Xi66aTiOKV+6k(=*?q&wqoLsLfkZOy&HZAd5b z-)u_=@B^Vrk6vyhVcD|sv@NUOXypN_b&KfB|GGMAbzx=RWfAhbiotMM=dfS0wJz5- z)W*10zJsRz5k+tqX+CG=aB--ugIjZM)X)FJ>*=7;~^+#pNYsUt?}|krb13jGpaTU0*fVA>qG;4Gc}v zNI7Rm(ArDXYN5lTW+uEhY9>mYxhvI$hTjS@vS>wFh2~vmqm7`wvN+sTT$e3p+6_qJ zYUx5}xq&JO^o&v4l8J?Hwl*0*S~dsCJZ@?aG%#S1TSu~)uSxJ+$&Y_9$U7#TGO1%J z>LJN&9KKC{H4;#U|Na1wqrBk{aQJtu6|V1Z)vOQIiWQ@ z_|Bk@qvdj6`?o04$eoSO-p=t4J8)*7RmgFNa)b^Wm9rz@4CE^ zOuA$BQa&L+A^t7@V-vT+YCeU}yK+SN5?Q{&@ET^8F0GLbjnP?qdTnGYV_nDTy*sI9<4J}_xUy|88lM?~+wh3HzN(^a( zt7}0g0q1Qm1?*SgCbc`Bt2HQGexT+JOU4tIWrBYT>aYm0cIvg+$G$b=Ta1Nm^(5!L zXdG{U@J$vuD34UH_&m%Y9(Zs(xgO+azNWlgyS!_Ec>Hn^t;esbD5(|YS`BX%~Mf2scFDt=Jgdd;S<3Zsdws{DGwZa%iQdL}kvrQjJCoroag zvN5YFMFC_%ww2)+ih4gSq%hL~>t`nfOq`|6uS@QiE@|>5m-U|}zC>XB9N5o2L_SH~ zJ09ck?h?pNmYdHFx~yk*X0460BJrw)uT}}dpr*$RRYnze#Kys6k@$LHr;XPvFF`dS zQ&UepuOgmXj@1e{p`;9_er))drZ1pb#(vTAI($A^GFmW{>Xl@G`AmG>@SgCE$d3uq zkvvRD_V+bIpN7T<`>$VicrS1j<{>641}P9W?H2JaA6Lzu&>{4}!WxJxTiXjA1wwhR5#_-=iDfbgoWLIHt~R2MZv?gR>+ms~?7a$cioW zbg!~fZbhpc0Iz{8CQfEeS1RFSqGb3G5$RWdt&hYvP4QpH97#GvteF9++&02b;a0 zx8%TF_I%9{b*V<^*)tXka|0!By5jL1o8ZxWYlXRr<+UQYrUA4ZS~jih%34ig_s;vt z%H_(eDmGQ$Wv+l1K8}U(PtIqsE8}GU1Pxyf2W{pPWfCkS1?AOQO5}?2Tzc0a=NPZ@ z)QrL4GV^(EP2MO09>7y<^qyAOIJlBKhD-MuTM`R}29s(%NF&Cg`)N*wwM|tK%tN}= zT&vJVHaYr~SByr~$&KNJo*V9uZVS<9V5DBV7j_Sgc+Sq%_@usfr}ZY4r<8kc*40ns z+}fgD-OucR7152bdWr|(FPkNhPH2vfFJc!EZQL#!yJBLswabIBEQ78T0xa-7+x|{v zYg|Ey?E}yK;dU{ljjw~k-bGp3ca39QTHkD~6pe;SPfi+T&9?P^x`r;E!OYfUW@VNe zt_s-G^wCckFmc%PSmjEeWqy2#O6_AfkG_;P(`DI-fZ()`%LU>eZIdJ~@L^Cnd!NZY zBFW;u)eoXZNXf24-CcD{HRdewA|kKuy-`x8x>y~~I2?ta!Hkvwmee(phn5;dNiFmcpXgU5%%}cnmkTTwgfx8=yp*Lc`k>(}u?a4!1Mg zS*pn*-us9E>~FTbm&o0fzAXWw-hf}Zut-O>eC6<)Y`OogsQwOq2>-%Ne0_#pCpm{W zyP$sd64%#&qN%S#`MiVVhtYd={<}tThqm&v9rUrXc}a)dXRY=FV?7mZlir1u9gDSa z`z_htUUc@D=xg2SPtiSO|5z!k3-Kr6iDSJ1c_TreuuYq8R@%aO1*yDAZ{KJ^RO#R* zEc&L2LW75E8@Qr_K9i~&yZ6akvL?J{Z%4#ZQDi?!_BOffZu_x*3{KY`DdQ#@?VOy~ zi3?4$8=)g@SMpjD-B?qrwxE&P747r&J2!)CG!ASHyPn2V&Ahz{Pb77Gt2{vFvM!B?()@O!<71$OWzyC@m;4}BL8uY}56|?4c#neefi(b91 zE-Go}D}e)*tyde%fo&h~k2hm(%?>S}17T>)kuiE-6%N+L*X(rH2AV@!=x~NsXLoBH9+JLO>uEe^QVC;m&{!f@lH{qEg>OzpHXJlP(7T!-F%60ocHY@Z`0 zQw}Z|%M*7m3-xx5^b6&<88{i60LRVX z>}Ntjixnfu!%cQ?xlyaj9~Lb?jxPHzbir$;>%xUJmK_ZN^Ih6F7n@b@Rg1ol4~ZSf z{6avDergR_i|-Od%0zUt_%Eyy0oR2l-@V-g^++ibL-VdVZ}&|^u&ki8m2jGRcZwG# zN@Y?&`C={w6K6G3*EKKuEmq>3+?I_9d~~-r?I4(Ksk*hl@!6f}SEHWs!b15WvA~(q zl)ioMxr@~*9v7$VFWv~9j<9)>Js}PMAoY1AKkl061j{^XGU*{~O`B6(|1Au0$PGh| zcQ%j?{U7Q9j-&dL#ohP)Tkz#wJT7-q(WD8Y=tm094EC14M{57LYx|P8Tz%M%>*!oX zk@CD=t?Sf^+Ky(}K&2bq3%CEc>@qxlaDqZ@y$rSo_AP)Jf*^70CKy;p8dqG=z2jdN_Jp>qNnwkXp`JmQ*nzaNm_8txYi}J zLaUC3FT5faieFo_uXB+hmq)FQ5N`+=bH*W-v7EkL-zOn7B;lp+_xu~ ztz)}=LbO3vcsn>`^>z1T6Z(1{jBV{NO~*TFGczX`zh-9CHegh~64`&H6oWds-?xS2 zf3ToebhBjfLJsK=Ql+SCgmwR|l(efC4MV_rhe~}(c=R{uz^4#v-vFOBIUnB_TT`vHEQTQ53Pdv#s446?m|*SZSMwt6BlXwYBbWCk--A zIut*#F=p94AF;QzJo)Zh*gM>$*O`P2GK5XYR%MiZ_V~VsuUJ84rLmqi$(MM1lID=K zxS)x0v}nd%Sz9Q;1zKSXq6JHZ>2h;RS%romGZ$w7{jCU zqcxmcXR<65n#qUwew&mBK9J>4TU-p)yejDr$^{U{>K1unJfmS(M11l}&e@3OW0W)A zbI8qBTWW2uSU2sznv^X;_xHQDunC&KhVqv1XZuE0YfQoEQ=drZ2dIl^NGlKvp-*J< z#%eNbt?mO0X&3FM2a0#-@q_NHkAjo??>A~E?yUDCuJdx~nMMP?o%>v`yHtcIY|5C% zcx6m#M!eEW%3pFJb?<$dL-<|%80-k&{tlWv$X#kwr-+8EN^w1QiXwr(_ ztLKAL^-uzh0kNk<1J3Yv#A87iGxo;Sh~<|W75R;ngK0*);9 zs`z?SNnR&AJvN?=Ug5E=zRlkrEXE3T_+DBYT^K$f90MEQvsP&L51*G~wf?BhJZ1OYElzFH1LgWO9%DzNlChE-=}}JQ|&CM^+W% zO9b9h^T^mL_7)}e7UXkDB4Ytf9L2nn!8=CxRc00Y8#bVm6F(?Rq{v zc@rx6o@_dd(Y$w1sC6V8K(x(l%u=g9bV`=FKe2xvQ3Q1xV)I*^SZ;c$=$h=8I%qIb zDd)WptPHMg*88?@HWjpzThRq(W9j9%l;Do=V&jB8MM(mDsHw^?GSkjrU3Dz;5geol7kpexP zLOjgaj%%FX0Vf?HHV$M3UzC@&TuHQbPm7|gSGzSEC#8t{ACNn`*s0?pFx{Az$-Tp1 z;m8j(RK>+FY~>dTQv<7zF9n>6n#0Zyjv#z|a=hL>;_RklgJT7#0@3clba+9%Xq2ga z%56w!FWl}Lcg8 zS+uo;m%SdTTe}?GJbfh76j-r1>H&q6f4w0+d9@=nIq&o8*Y6;08K{TUDGj;k#RkI{B~e5o}^YQ;Tn2 zIc;iM1Z{dedb9b}^w9M?OYTO)OsXcKZ`mHJnC8Z3PLYCx^`LsX*-Gs9I8PJ`p){|z z*baP|Ap&-%4b2J4!qkk;)wet7xw)g~&4&8-kB>bjJruSM0@s1flk=G}&hwK8)3)-v zM)&u~3hgq^zP3ejq{8cf@RvUoE{@Q+?TlEdf>P1~FP;oHsOu+5-+%y-$3gYdsr4T6 zqxiC^1}9P#GF3jK9qM}pmjvVkwbTVx?YK=t002Nyd&N#1_*V`WU zesEsHno};p)l4X-Ul?Bz%O!&5X&bLN7Xa1n-LdaaW&;xfzY z5WA5+J*yWfyAh80)&65W^|_TPd$-TmXeAh`3oWJCIwrIRznmycceyiN+fEFN$u&pi zo1MQ_6DXoB^Li=GP|K5X;A3sEQoH?6;bbNXx=t`OmS`%Y7N3#v@k=(3S)p-lq( zp{%p@rXLE@BFMzVIL4-Q8=lKY8r|>D!QYef^(qf9sInC~(aS1<*pLDb&g(_Sd_@KX zLqZ-Je|u;2!`Mqweelu8{Pe8L$8JIH*@GcyoHpVJQ#1yA0z@X>P2~(=_pu?5BJP* z%cu9z#%;N-)lANh@|{l4_Es9qNw);O?XtENYi1+vw#9~RpcNI<#)=!sM;Z!6NyK)WG&XbIq|RZrO3~T@GERO}VgJq_2-MM_ z#k~>-cCD)7Z189E1a57S7w#*vDE2H!tl#>lh*|6vZc*5+#|0{2oBGlIBZ61e#vD~3 zag3u0ZD!Oy!ezWqlD;gcvc%j(9F!4BZr;RI828AGw)ZA7<0uG6+HHw8C4X5Bnk!c{ z%amsquo;R<)`{UDU2fUtd`83m?V(b_p!>lQes7s!J9(9qogD+czIYPY{!snpQ9Hs+ z+j?BZ?0nMOPK=uG!Pg`;g2#AKtXOFPth}Zb19ytM|L(AL&W#-66EN$)A*gb@{1EqK zYWg6s$%N%rM!U%>-mM+~ByUhZ|1q8ap?^a&g=>{uetneWg@Z?zwjzD9#J@faP`4`DART6Aof6%vS~v;WSRnU)JifN6 zFjVdPTk9S3n?7mg2ru|ZyRURN=f<9ki}}~N%c7aSy>0aHfm34ndF8`n!5Iu@_QxKB zZqCJTqoqYN+MCE1$IrPzdlb ztsZY;#niC4qrgnPp4P%oV!Jf}U}H{mxz8YjQzU-I8w5GSnMpEZ|GOK-08dM500 zB9=@H2{be4Ea;u{7e*rj+LS&yhlTuD8QI`cGS;`n@Ae|L7EaFD`X#$`u62_eAG(aB zCD-OHZr?A`!1e{XVbHK+G^5sId!@!U)aNYqf!bS=a3yW=j{d+_?1%e=3f$c{rx}f_ zZEEX6)z2p_=5&UE=%o6ILxBI7Z9v;gq;eV0op~Da4ueL0BBABJgKS?F(n$=SBNpvE zAc?ho>9R0ZR!^4m{jfNBesYEBcaR+Px20e&G1;=d;Cb1r`BmtdbPt%Y>y4~2j2jAw(aO0{hbV>MN z=P2tU;rS$2MqZN#2|{6XBK)^XuDF3KlIk_^HEm+nt^It>DgL?f5zc#evJKAAK@7NI zEdKc;Hxt4W%GZ1XaTi9R$ssl!L=q^Q=aGRn6^lMNn~7U!pBcQ|)V$zxF!?!HOkr&I zX{~gTwHe~!5AlCxyRm&j{e>vqbI{?&7I-xI;n=0@2u5j-k#D+DhTm{00#^V5kCR%4 z>xXidgh3yTovpOZ?JU_U0ubnsCd?g(4cOW$E8-$ETWC4+qP@Wv5oeqq!sS=-+N-yYlVzm-F}2U)y~g#}{e zP!^RyoP`c9ZL6qh`Lef7JI7_CKl(-HtdZNqlvUCsVF+ej_swObtxCc4?M!}zL{WQY zuuqG(SD8$0c6npWcEvoNSrFI~eE6i=KGQ1e!FfWBCk88u;au*VDVt~wmR%+xUR4Xb zBm^F(wW5L7uY5RnGfJXb57mSj&yM@CHHD>U)o{hz=e&}h= zlC>7K#1Yd)KSmY7YQwZ8ifgpfE=ESNVz(Q#7I8~NF5O-Nvx>MRx1n! z1M1eIsLHfKAv1dAFx9+Po98_P%O7o-$Q!!>ZNapOxlc^O7Z8lKuxG?>|i+E{@n2amf zoSjZaui)>9MBAL{CVbJm-aKbu`L7^wv+h7 z3vbpw)_UVz0-hx6$pI(rdQrHk2j8SGw|zO@=bjEYgcuEmjbgn>TO%1EwvP_4U+rC2 z$Bk4Wa|6Da%&U9a%7)_eNZ61wGs8z6khYDMlwLRdw+)SX)s-!VY0g6)zYfK^mkP-( zE6YCV-$4kg?;uaT)a{Yoc0|i%`A_Ob@7jUb(lasp!(UH2`VQeCu~67-?a`Oo=no!x zE5R4{FJzTA91KeTFPhFfEb0CI|JKRVI%!KAu2L&=o^qpTW@hfa6;GL(lN3!sz>W<| zQ*)A&%$%sCrsTp?rlf?VD7ZNeKr|(N;{eC`^7&oY@8Y`n5B|Deuh%`E&&Q1$fR$ui zn-{(zGQ}f1%*1nP6_pr9?$R%!tV8!ODR=cZ3}vZKo*?a&qLLCs2^HPj9XTAq!5 zU3^d>4O8-(&(ez_gS8+=Suav~NEqILPmD$oOWU$t9r+1>OVIgmW~xYLpKYo6bS zIe|V`hKoagP+I{Kr1b>77f?D*Tdqjnwz^ypu8(B2TlPw&_qzZ7i&p#hzp2Q{i1b6$E+YRE+X?9EfD zQT(mCn>1cEW8qfrZ02s!{Q9{U=8RI!aKri2K$2#T?kVXbW;euTMJ9mmOv|h+^J>+= z=3swD#zEYVSkAIM^Xh}gAH+|WW?CF~+?VMX;1Z^@#V@Q^Zt&nJWbzH#>scjlKXz7@ zWjx+WyxV{un1yQ4L4R<*5i8{!Qn&5b1jIaH;tMO=!nW-qEd~6|HNr&h725$n^%%+} ztcy$i8K;1>wM_xg#}i(e0NMs7-`(!TR-T=fn7+&AS%*b7dUHSGwKP%zY?=HBn>%zK zA+m4ZHHIb+uZE+kVG0V+#XVDU^Kvq_3jsIWMLcTYZ1QqvXZElPU}nAll}?oLBYBgI z?#%{N;!&4@LQH*$k!yS17>03{NZrp%n}jA%$J!{hVYO>HKX6Kh{;b(2RT_`s`wpVMN@NSR?)Is1c#QoD4-+# z9kfU0aLgx8x8A2wB?rH5Y#kN%a`!RhB~aJHEo%bU0muyZxXR>r30o_`gc7AKX!7lN zHLSGQ^G`yqgEPR3DUgjl1?C?w%8xNd98#59yw_0d=CmkQRrBGk z%DcxB>fMVK7#LoC6JD0*1r6|HVk^*1QtB37Q`e3aPJ$jXVKa5_q~c0`t}QfUC(@?< z!j?`fX*3kPI5gKONOX9&nt-gF`nQ^xAlr$7#XC62)2Q_C!&d#9oMtNAhiXQSyp*Sk zM<+tNxhrc;c6Mq#9`X*$YqJ}y^VM$3O{OkPba1=J`4}+RP&j;Y1q!sF@&o|TzKi;1LvS{v}`SLzfu~W7@ zTe@~hxCT)CrsgJ1k~43dxas03?R@m;9ThQ-U2p%SH=NKmqwV3sVdnDVWtUo8mk9Y$ z_3eYyZ=X;<(~#%@f~oPwT*De4xBSa$PB`W8ZP%af?;;RnSrcEhI0KJ9bZ-`%T^jiH zb5|n2xnpsibMDe#zqmkf#sX^J(ytAO0^Y#>_#+k5B_FxtRk%bihXNw zgnXXdcxT7)IqoSJ2tp{xVJVp)2z`3p0pq^^q&G^I!vvT%P20i z`9zZ}Px9%m`3+H=4MGd54s7u~f!*|E`NZ3t*)qX0XV>0{*ba-#00DL6hyUYA#gOiD zer|Duvvf!)hx;)rCEmXY^IG_U<>}U5G@e!Q-n+13+zPBcZ!$}`7bfe`aZsGrpY5zI!l*Iho%OE{qCCEC#qWSyxJPwX8;5J5 z{8;LS)0cl#-l~+p$r>*x7-d|gokg`yiwx#5Lm7G-f}We_tE+G$@b#DLL?a97r{c$5 zEwc4`V~yW8Qp|`b|Ktgg;F;*Pyc)3nubR)ncYVsbmC~0_M!KIM)cpj0enM#$<}pvI z&GgHg@z(Cge06tiRK}V-{j^CfUL4|>{i9R+$-B9^gj^hdM8$KaoBJhQw#HC;K~_E0 zwomP(DkhK{-WWP0s}mUp3h51qy?WsE!6T}M_n*mTnbU8%c^8m_eI_sk^o`Y(|LIdb zTgwkwhpNB5IjSmlMESIZJy?OJ)wi7q5KjP|)3GJR#nh;YLcvlk9|qJbK>s1)zu@33 z9~s0MnT*Q}(r)l*36=srN4Dol_sc^zVh18-KXNI*c$e3f1IHi4bXWP>%Ls53-bVVK z>1{r|Mlwy58iCM|5+`66&!PJ@RHAW%^KUfK?gP6<#VgGh(`f`X?~;)22_Q~koXhDa zVo5Q?$X$I513Q6HBNkm@Y!xT=?_M(Gmo0Oi$ZCU7mXPaEQZ-&Kt&I7HKK44I9^f&4 ztp*}KxeY(@&5SjCR0#TOa=kM4M?B%nu7cF77}pzenpn|%b67AKR?&jo>||Scw_71Um;T{|YHKI*@|8ZNJ08k;-O;Qo_}NjrclvrwcSq13-i0KE+nk((EUt zT#pv*>hJs~<{Zvrz~Kq9@yo$z4@L>JQa+!(#F^3#(+&T(lZZ|vn0gDQJO6+L)a$IG zOsTXG<0Yr>(8+3L$?R+SRKiq5rA%G+L?5rqw5}u9-QxWTM`U{WJ~KDzl19(6 zUW1!e|siduTa4DbI7wmsI)k^ z58&`xY6Q{rAVDavdq~Vr?3t*}xOnyMg{sxs=>!`0J0;vaEuM!NVRaYF^7D0;*6 zVrEE^e{Ka!c4jW`aI|;=b*-TFX>^`RFxP~@6L_r#aO$)Yi>EGWnR+!B%3b?i|5{6YT1Wc(lY{^&|P;G>m!iUzHesHC<}*WQJ4 zx@oN8R($uumnk`WY;vVhh58OJScczm1rk|Mj?jy%U6yGaD3Im3%>z*;|B0!~=sq7! zsx+Tl{)W<$eY1o&uB2^k!Xtb26EN0tTc0~zI1{CU>3)yFgkpEKFjrx~K|%=!Gd-b# z*l}OIH+)wd6OVBBP)V7nalEYY)JRB0Jp7J2?ldSPU1ZFO+d4%3|guBz%u zs?2KSgkWmN2MUsETd97s_Xv@E@C{9NNpV4J|G9ZObEYppW}^;^?9FMQo+6a zJ@cxd-IPY{?|@*HW|(DklnmYwMy%}8j93r=*wJ#lya$f`02oSDL1}PU3+HF|4sJ(> zIstJdY;@5gutbesDo$HO7-+lLc%`Je6bpj8`JE13SOyqkJ=QozSYRn=z_lprtbq4h zKQV(eVTXQmQLX>)1$6STdTAk37!Mg{#Uo6d*3cu`>qAE$Rc7G1f@6}FnAb%<@qiy} zWJjw3n)j=d7>UJWpvc*EpgsMQkb_Cz!TU<&w84f2rXDFm-{|dFx%*Tv*oa_3Km;hW z{-&lTLYV#_=ZUYFR2&jhxGA{8VL;+%gk2}Xy`WJ`4us>$)TIS<@K6(jRKWi_w3J>C z>)HHX1kQu=2iCWm4e4&*hif%7S9zI>YX)x446Vpb>_lBUq?_b@CWS|}EbiGVUfCV+ zRS0()W^jU(nmqKYIPr4*pQiUTlUXe>nUy4OQ*4{gE2hH!L`;~>gst< zw)yvr&3$Ut1_fuXKV0dUQXY=ADG)xMZx2Z4QJ(kejtM$tgKc?h(*tMnJR3BsN+1DX z0UDC$>ivCd@xznHgAAjI`JB8%bA_A*(JTWhIwU5^*dNsD)iGMGhK=(_+?|xFbM1*r z%k+ZO%B!&I<_rsXt=ZK(k5hYu@$lsKC!E`j+_yJv`H`*fE>W9zsP5@HH8W48?cKGP zO9N)4#BWu2nS@rUs@Cq$n+=24k4~{_3>o`)YcaB*b+o?)t|YAG%0Od#bhh4xZU89D z{?sxu^}0==@o;NXu;_FIRSeVy5km)b)fC&gA!kunO9}$->F<~g>I`z*ueHni@-&UJs z|5tM%%x<`7_-av6A;QUaGPLEz{*)AuY<+UN!pW1ak40x7s;eV@(`uAwYL3!66@JH# zb=dIduxCU@Z#Mihi#`+R769SkAdBf7R5yA~;c5hW+^W&0I=CM;?5g7O%iJv0Fi1 zq+cGu@qP#0TV02+7hq&dxwC&#xzwzs32lU@7X)-dId4EmLw@pf^s{s{c{&mIV~)CF z{7KvQVI0+kvd~-t?*#tg7X_DLyf{l`>etO{Et*#nvat@jLU{)}0BT7dr+Lbkn0PGD zMQhiOX9W8cqyxtaM%&Jcgror)_C8&O4Wu%43fF+=k8PhBTC}del6TZ#uNCOBRKY<@ z+Pn18`Y>4DMs#PP>S$^-MN7M+gKFw>r#WB1KYiTU50(^?(~s2ac|OinnufIt_{Mur zgy+5Caxy++#76;yB>!AV$At9xKB0F{V)AdyV@2}HvF6Kx1 zH=OE4*m9yaqqJDvwfRwG7)I5Hx@}cEq-EXCCmM3y!i~m@7=5|`$>z1|HLzRp9 zwTm2ULq7>i)f9D=BPwszrPb?CJ2w1qRKKJmA!pzGZQV7x#voRuu_jMnSABkdu%IpC zeQ%nJ;S1XEcWc21@5d=Msvvm-B)(y+@fO`=ST;{}Jalr4Y6+uZ2fHkL&2BgywGpj) zI4+iz>CJ%E_SAn^AHdiONXfKWBJ_4|WyLxNs$aVNeHxum%<8eNvlvqiTyiiqTi~>K zgfeW6>qzfkk!ydFl1molLcEC64R>A(d36ms5BNkdeIH`pzBYYiOSy=r9ZY zte+2W+I&9w7T3Tbnf?SEs5pH735KRcgVVQQ7mMorbJ5_efrQHNf z{bic4PFbC;xK6$a#39l_>2~P7n1MV^2*7av&UhTOebtokmc_F^MD^zqyMj$-?3 z9-m`?T7PKNXNu5Rr#;2x4qL0A{xIX@=49~}`KPs0|3~4&d8grphcn^sdHbZQ`}#^X z*2h}&d^VgQ(He_EWw5HM8&dyf$f?bv>NDpXREb|b^3*>TSv>R;2y?MEy9y`ni3-~a zo2r}RS#^)coQ_^^7@``bRV-a+6^jH9#Rt(LvXYp=S%|~!XVl7#-ly%m@M$hJ9f-qg z7<<#qGAKNG-qGz|O3>_R(Q&6RV)sJ4%*zQli69@+bw<@~bEB zqd=@p-E~Z;duubyg|eLfl`a)IjecU_a4gsHebOgMe@T;#XeVXl!i$;#OWYWomx9KGcA5ACp5A=2QF`ua zsc&V;S?0M5J7ZZs5M`_Cdm2}cLQgR>YP1HsJUA)$im$%|20LnM2vV{8w0#a&nv&}C ze3$&tpEyxVSb8}0x-V;uOOEEwq5W+MCUuPB#R+a|AqfiyO9j6D(BB|0zq`H{i$;)u zm?S@cOR!v!rgsLYi5Romrs34fp4Kk!?D*kt8UsN5v|%^I9rx`Me<--G?sK|%Xr0oB zY6KR9_wW*3|7%~gCY#U*9G2GT1aI{N9QDt}G&5sE!LmEdQZPR_0qi24_L+ld!H3ang^V-v5eb9&q(Ie(5F%hpWSJCtOj ztLI{;*S`_Wrz|vOCt+3Y#`M_T=~J|-!#zCoRMDrxe?c4@7|K|^i(^{|DeOBy1W3Eb)*t@D!Sxn{GsZW=Isd$vP z@kaUgK8@;%WbplAX(M^M6BVU_gp|hd!{0E<}&Q;GP zi=xyu`pKY$>+PZv@863UU+ z6v6byuuyw!&UWyY)1}z7B-ed+rKB#L)A`OVHk@5t3iTdN_qrSpk?TmHETnY$RDzG$ zb#grLTGv()tpX&W9(6RsCfXC=jS#V^kAiGFI)^ma{pM-r>V)%WnU?21O_h8qJ@2QD zB^28i$?FyuUloTd8qvC5IFB*e}JP>-RETe(JsU9GFGRt&6r z6>8;G`d8glz!G@xHaY9^4zOt`z)R| zTS$A>bYhxrNcpCFq$*aasvNoSK3Ka2VuVmABmEwiNv$+x_it7MIUL8#>sWAudO^=X9K)i#%;Xw2J;(j`)J0d*ugnbjo_8w zyOhtt-`f;`n3j@CK&(JvjB{~p$ct;DWjSiXH2IjUwyI^aXUYTgvi3PdtZfMAvb8g; zgtM<&;@-5ZF(69kdj`s~v6K7El*PqQ{7!2MXy^xSgR!9X^rActTk_0;Hn67KozV?~BSQ1rN=Q}qZu@x4gyzV$X^a~V~P31+@EpZ^I-uuNzla-Iv4&llR z>|LAM%o0`n>CHN2`LI@lcRw z^GmU=dK5dJBBpY|i%n#jDlGid1mnNJN$k)KxBZI=zPQ-cgyjAW2NmNN&nr_#u+<_` zs`zOsCyQJsw;Rnl2-T%~r29I~uMAcutD$_rzi!rTVh}r3gahKZ|&1MVWI>vIUT{L=7FLNnc$QUBWAbG z*FCLxk#d3Sy*~r2fy%Btr-_bRLlsSf03 zlbI1q+>-=v3N&Ec1%IRERbq+pW}-Z_l2d__Hr!Uw^5V=$eEN5Nd^$>F8mtL@mhd2K zn5+YvD_;cksmn1#e4tA%ZwB}=6;%hixsqL`KR-U74Zg^bk=>2CGP2T&tdFD3EwqA{ z*c&b~&49nZmhm#RZOi`=(#c8vUSEOT!$f~h3{fk%7=_Q&3}Z#ShMps0RmDHab(5<;pR(OGY zBw9D1QSEZ><5xA13=PQSeUdezVA{cjU3lXKSsYn)y*J|p0rRflj2u1w`^lt3NrK9D zN}tfs!0gs>v6yk+1bpA-{&dBvtb484@@uzF+;Tka=IZ9@WTt-G!K$=aBtbj&+UdrJ zC&@=8_T3Oa?etf9j+AUo%G-Bo+Hu9c4@4!w=@Cd^v4g!Wy4CO+vtm~kO~&Euny>*#s88qxHrD;? zfNcd2?Dw6{J-v1Kl2W-jO#of?x4goCncRmpWdNALwZ^O?E*%&Paelc z?<4*4d~9xqS=D2M*GOGQc*2!eidBE>e%fTRI@2HhYyo1lBPceqBTPf`Fizy_!MMo< zT)larQO@#<_^s?RbzH|h0y%DyXuCC9WsvfksiwNY-)JV;;aILK1rHb>%WPQ?LaHY%vz~xj{w)x=iMTo_| z)(By^o^7|9y?K0>=77%XY>GkrOwS7u=(FeLrwgPUeFTNfeUc2!R)mI+<9=>*E z*my@|sr1zgm}Ig`+TA0SK=jHnBM!S3%QQ*lX4b{KpIncTGSsNrSpzeHv%nbJhX z$QVT{nD_Zq+-3akd#FVpeaAT*QD?aLi>Kal*(*F#obsJ+Q#2vam{;lCi z+rT4MNNp#XAg7=aEnnhPk!(_6-0n+7Ks`Rd{SHu=4t-cqb$W@@0;gwX)AZ@J z1s@j0rx7Woc++hKRVzPLWuMOzRPX+Teu@2T{ig0ZJB}$HY#TO=PQ;b5>h)^@)6)6g zWs3ME9Uu}$fAl)^u%cI@N`2&$-)7hX1VQfc-mn&5*L9H7v!~2y zL)Lj7jcV$5QeB+Y_QNSVpFF0uQU9A-OXRM#g*a>6l6EvZ0XRYntL==)EhNL7eHT?6 zu5H3UB;0idVRVO|-g4JzE&TbPnD+P)kYdFtDQi%!x|idP)8eAL)hW=d?;>U>S{|=o zkNi(8H23F0QTC#8RLoIbT}-6rsCoBz<_2XtrlH(bO!jS(>L2H;%0U-ntUiVYsb5o0 zw>o+E+u(s)XRpaP$zpsfO&a6YR!d6cu$Xow?G{aTyPPgeH@kA+S+-N-r!xbqw0?93 zIU>tOz3CQNy``elfx_js#j|^G7Ra2nyrPPys z0ev_XtYh@!0*$JEx+*f8Pg2QIIPnAhJoqb^lfp7R-~fD*Tj3n2CX6UZ`y z`??&yecqp^59jfinhS8@D7WZPYX!k}RfdQvUY*?d;e7nSdAG)r+5WOpo6)yw1J6)^ zZ!Od~QPMJ(3N40r-Sb=K&CUVaLlishAc1mEJA*}^Td#)+>WJiccH6IM4k>sFuqjf1 zl-gnxS>la0DG6ERd>3|n6rgq|)^>jbXwNo_W-crj&Bxd-uupBwi4vE6Y5?4nexbjUU%xhgiz40v2 z6;pK|PVOS$#TufT49!_rX?xv?P8s}5k$%R>&eDWo%*hq|{cq7B&8J;+OL(JdbYb{a zdT%x!hh%#HFu71M+~9RJ#JMMT|7*u*Qf8Kl)sl)0Y6=7}+Gnh{Sj9jDy4?4d^lMMi z_dd}UkN$Um=XY=eZm6h;R#Mz{4U=n-s^i3QIkAi3KLwcJCXUT@&wLTmKULi~`vWp> zv^PLW>7rp|Yh+wG@sA1%BLl6t95Jmq{~KH2$f`x}cpAA0$ykU#-+$q_V$yc8L~Q*C+2{^Zs98JE$wKhJHMCdJVF* za*AtOHme;HJ@GR$w=EmqM7suzKoIQ0X?pMgE{Ig^X*(`GO+V#Xrejkot&ytTi$r&5 zCh$bG1fJdU%y9syRIuW+-=q&2!=+KQ`1n#;6Bra>3$sy1sf`o z+1k>EGLa9~>SO>r?B^z$>5U8BOVqbB^kj|jL2j1Ufp%a*4Ul$fdlNkfe;-R>3@+c{XXHV z45vCZ)F>Ua@aKGUSg6Zz+T9PQU%ZyOb+-2Qkc6~yj!cBjx!Ng_ifv`ZomJrD6sG3| z?_(&)jfC+|1(YHjXwAu>Q!P`g)lvOvd$^A)(_ABmxJLtGiJm>UP%de-0b3;dr)SSO4dS4XBiL2MBG!#}4 z-E_5VY~qJ{S#n1u9Us0;s(SZ$@P^|dF~@yYtQ?EbASlRl=#b*$s_SJJs#H7O3EGiy zc-y+sgq^?wS1+s9E0!E6slGR^l1jh}I%@B%1l-9}{4JmRW|7^hS7vps{x|0Q38}+n z3yFox%`HLp^~u(juOLYM_HVD9uHQ*O=)MzCYx`QKw(Q74>TYFogrHY0n zpQbqVn7nxf`)6KG?q;hxjWvDbIM&5zALoJWyrX9f@_rS=H?E8zI9S92r|@A)G+}`@ zmL}Z*|0t7zDSb1snN@byLwQ}mlrFEXrK*KoU(Gm!FJLiY`RIJKI00o6)k?f#aHOLiP?vI=obVf2bEe{pU!+oE`G#}9%`l)ErdCoegu;@&V-(W;4h234$4FQGotNT zVj`HH2Jlcu0wn^>sEXqB(Bq{2j~^$c>`(W+T}JQB;n%0CI`2Q3V|GWUyjtSD_;6Xr zH?!J44LSCT#`i_K&N}j9U7B}pV6@5~r`^oVM0}Q12d}Y#kFbE;3J2STIXk6uH?ywO zU2jQ=o4J1cKH0;C#ZF5w&-ccVHG=qcHIGCsuNrDhFF_G~Gn&X!nJao{zdyPj1ojX6l^}$mTJ4KI zuF@(r(=))@h}jj{M6{?6!viKr=^k0utG@(+)gsrle_yL4=Jfoaqt&ks&EI}fBa$c8})a!y@Z+_eW{oNX$IIszmZHwDATm;(X ziGBn^AF!D`_H9cT;0v1Bvm4!*<5EjGtu$F47Oe()Sgf7qr|H@7i~;)IPkhrJPwYy2 z51|{%Ej=69>B6{|xjx>10=b*DkS*kunnEqgeenF&Yj1Z#boSl<94clHt! zY0QjoETDrr!Ad>T#-b5ft=GE1EbhR@Lq8Z_L2ii0n(sV6vuoVeL=p#@~>{lHLwL1>56|5|bYisER)q_bln@ zIQ1v<_sGX%dQH*D;9)@{TR2&gmV4@}K0E@zF-i#F2!Qc6I>u13@oGqrZ%w;Y&RLb? z8d9%9&FoI+xgBS<+o@CP5xCkVVDf3!H)|(f0WFXEP?Qa))wlUDy)Fkde`piirpg8A$zZ)=)r!2>AdlMDGlMS~ z+THKe)M&k<-y(F?xGrAJYI$k}=QGDD(-r}S9ft=gR(CEp`Bu|XE?OL{L!JD_eMNbc zivMo*Md`DvNJY}*r*8r&oLMK`4{>q9@Vdh=-o4V9e0JC$w=eS~CV7#!Bkb6oz7xJb zx0q2g>oLff@s#3VwdFYihnpZn$UI~)(>d)YawnGvz;iaBSrp_!EGG9-L;gq@NNuO_&(QW%!uk-XpYMeB*m))5=+DczgzJ9jL z?=KfDAV@Lh_gmZ#2cPsflD=P^c~F2g&*sp&==u+T@Yhy4iBO3(VSHC%8&QqCz0>YX zOxgst@xyC?(`@UMOmzOtuI$CZ9E&8UYHml|x+!MRO_Wiz7~+tG49};xtHnkTf8ITt z^5pQ$mkbY5^%c7b>`^KblhC(ivIIxtp^0bI6rkhSG9D`%aCc;H!_NHTH&z6arV6Z>k=rMXiCI2po3-9he&rM}Brn9!|F zv|3P!CQ~D>h-aA_SWz!?|Mv56rNd$tN#0VY^~x?bNcVe|xJPex3IissxfQ?IYf zx5bH-Jkg@G>fIe(>H2nP&l6h{o$aP2VG0FJ*gzF6_%i2u! ztbcdM0;b~Eg@tFc-#^8*Yv~|-m(^FUyYBZ3e4u#NRzvKCEqbYLoj?OE>ru7pM&Z2us>d&81{K3fOyf zOAo=oQz}Ts(M!%JWx_6YTOPaU3kDi=%=6gcA7@sT+b1`IDAoVOwD}o~a@MtiNEvbO zgJ%UCJZT0(z0^N3N2?aqQXZo)qKwFn7nS`ir>dDVJL~@yZAIvD8!0&A!Jx6#LYofHo%*$XLN(pcyOH^PW6E zSoYOGRB;Bj^5`O~G?j#SnB=8%z3li=W1-L7wC#d|TDeW9OxJs3G*n}KwM#3Jv#elSR?rEVs-O1dVnHL zlsbIKzn^-NoRdWqv9tCcag_ErKAMB`QVYE)+EATz=#oyv3!TaiRvZiRJ+h-<|KqZk zV+x|-%7FXUhv=~9P|k7Z+Fag$}qJ~ zJ=E(WO`mW$>XUwoUp!G%PRoA+BQ8DBG&}#TCmp40yjxDagEvlu=IQy^7etT&F}H#f z;vs7ex?yrCtVT$29j2Y^b^{eJ|v~jHW9XGc)Z|2!)=*tOk!Gz*gIQ0UEp0wy^UBV ze3KT%k7i(VW0($rN!DkT+6M8I_I#8es>_Ep`e{kP3E8w8-5B@$CTarQc^ZNCK4@d8m?`dpFZ=&a!62l569%x*w-A3ORg8qY!oymTj0>vwGB}!=-fbxD?2_JkrEew;FWT3okvd9B9_ zZ1|j{SuiPdZzR4-pX=kIjk3$nE%h#^ohh{9RMyApzo~uBCe|Y6$%>6>kB4o1mE+_c zJbc)Er`k?VK3B=zdZ-Jc?b~w7NmDDDTR7++kAAfO0!HmrKv~m51`-#u*7;mq-Kt>W zwCDQsp~pKuXE%lVlP~`9fTr+kM%6&AUcg*>ggvz*8_ z8(e$Fm^UcMPau5&7>wd;Usrk0*nHR|V*HnIibHo@&Zd1HLVgK@Top147ykgfMr7jN z(;H>r=f*Mp!shk*fH69;b$A1L3@T}ldk$;|C=d-K%Q*Kyeh{CjxsHMSFFeY()e3@q zi+0G0^Yrm_if3 z?@|Z95+ymk*s<_yNw@7o_@{|pas>}lwhwq3MMx883tC^r`ECR!=W;Yx~Q|yL90=XUo z?o&6v`teIeL)t`+ci&0XEMSTKoy zy_=Ek;JXsExVA(fHQT}FcSV=fhfm;#c^5>6;h9xMic)E;yQNle+HUqN|8rVP1yk=> zjVDdb+%rSprOWUm#JiOyop3LLbqHsQc~^N^Z)a-{`ZjMBg)G{XpHl6vY}q!RJSZ_8 z_%C0ZJ?H;(ZB4;ln-QpM6FYqQ++1}UGkY!$YYQowv3!NM73@u!Od3Q^?kY09Sd3&2 zt+@BcoxtN`egUf0mhtuJ)EhU=-%=hUJ!okg@d+8&m7gLx155ZP$`~O-#TF|Kb2^zI z%_5-$7(L4ubfotD{0Bh%x#vH~?bvOTw9LLT#o-uxFQ9cn-xBR+{v{@3Mf*JBl(Wq# z%tZFCxcbFys0UO0NYz9#Xu3ofmmz%KIi2-M>k3pXJ4F88IJ^38oq?%L8S40*kK zKOT|aYZ~j-J-QARkH1lOVRVW%Z1W;Z1`~SXyv5L)$%KPXr~X+Ao|#&>Z2KgTe!R8q z-eCi=LXBg7&YI=sJkoY8Uuze<6IfHX+3+Sa45Wrn%WQxlCS=ARR5QKA@49G|RRRU~ z*i^uguf@X3H8kFHl;c=o2 zMO7h=c~7G5ATnGttqBFF<4XWYjV9Nji$lD89%CQLGCVU4Fya>wReR+h@mU* z8vGzeYmd(EzOimn=-00AnjOqx*m)3shj7rjt+xr&+G;^uF14`)aj*o_YDx2}3P%jTh z0ivBh7IA>EJGq`lLGNJPvup))Fs<9Cwe?Ob7a=o}Ei6%a4wi%KrJuvmH`c-8rh4^R z{Wq}gUXri(8}R6g6W^cPcg}&mgLW=tE)+o?g@;Gm6IaLk^bE| zd>&C;J~e`5V6Ha7YoL|rN2aPJ-@pQ$VeYNz)UPhcuZNA@Q!BEq!eB+H(?Z76EiQW@3+HNPph!_K)vPDX-9$v$7h~6BNIsbN zBi#xV>~6*?G^J+jN$&hR3p0OM9_RGxo$K+lY7R`F3=FTa0}T01j;G=wF#nU=pxly| z!1mL5a8z+AyN=aloQY8E^@-0@^Zaz$aqdAT_NoMeqw};_0anz8_w+#y^+L^3hFDj# z@MYb6y5Tsx^S;q~d(u2Uf97(dNd22e{T<`wY7Fauqeai)ETykQi7sUi!kZ6bZSatm zD@m!r3|YB+{;ex_alJ`o7kIK`)Yq1xwW*UFeq&fu`r}3M(dA3l1MayE&Ew?q`P_Nv z+|RiRm>x+ZEGX81NY#*3xNYPPTSh0VFapIp`asTs9d}#^%Jw&lkGY0Z2?~t zI*o6t;B{#HV{;7F_LYHrnz59WXlP>ivDF45jVl--^iKIM-qV|8?}O#hpYH00D{P!U z9T^X`y4JBeq+Gi;=jmyYfZjd1TE9k*36hDNG65OQMLcue!;cUNIep@uU_D=0_iYJ8Xd=msS#G=q2#dE;>=b8@uVjcX8z^^cGAHU1i8@}$| z0{D(;upK7s42}>zEvgRlZl0$Q@q`vwr&QXlG@H=5<@nTBs5vvC2%u8e1IvBkA&-RJ zq*MiBIooT|`#s!6a;O+MGAPxp87Fm>IrAUslv*6D4`f$@;x1uZTlBY6Vx2bU`gn%$ z3^}i6`65Mni$j4#6N{fqFZpM_UV(>wOk5NDt~SEEyI_Qu0seX!w`#=3H~vHjH^WP+ zByiPAe7?!Fe&ya1S5Q1}s&g&oC07bP?)sH#*g2T*f>qm;s-lq+8OnPfHdd+4bb zulIFM?eqNuXrF?1=T;ehDZ`$FJ+fb(j9DdR8XvVAFJAkWEYXGFE)YipcmP^vqes^+ zF*l=??eQ-10nm}7p9bt#8 zn(&Rm4!Fmm5JLt~hKd`3jg2h3zM@xp^~mqzZA2VrJ}kZ@a_4L--KBXv6ks3V;WUvt zJ?!c%TSfGKJeInOdTy_B4I8mc(o1BsethuwYr%Sy@$Tcp$^@JMKH}cf<@HX@6ur!O zV>IO+lw;bIUp%T4f#X!P3XGROr+SA^yLTSH_F!n8uCiTyEkxb>wMrp0#%lYFT zvJPX9?FWw!%O4nd;Fe$n%Q>fwO0EJ@-HC}%H+j-9Nbd+%0VLZyhCB*d?&}Y0RVJrL z_vZ~Y``m<@&bQM=$qU9IHY=CgB%Il}Z|*todD`$Tv9+w;Jbb9_Ca#~Tmu1%R2yOc1 zn&4~N)qFIOo73^w_{sWi(#fQ@=EyK>?y11nx^gA%06Oe1Agk4vyzt>wR?)0`lJ)YK zCMZ}ms4GNP4h@YOg-23Cf??5VK^z)1#&$ldN182@2=*v{;*m9^G6_yh$lVk({+;pw2E7@bpX!5M4UGPUV_T& z{OlU%?%;qwlyo2w_=|6txkK~+qy-i?rC3`SC4o1BHP6Bn!PZ- zZ6ob0igZ`ofUW}qU8d()R)E%`BsQHc@=8>ugV2rL7h|NW$<&z3+ z%Z*XNwx{FKu#!q_PTNYr%#Qp9&_DzLcpi?X9Os%sH|_g0zsn#1P$kk8J~L*d%^AbX z!EyK-K){v;&6?E?Sa2-f#`3och0-@c{&oN*1gR7m2>|}`(5O%^Mg1Pns=2g{_}W`h z-}1y<3pTKxl(pjur}=uEL0pC9euWEW=6el-FG@IoS1T^UXFX=~b3>6=-`@BzL6J5`6 z@Z^Q3qTq)jYyRbjED_VkAhW7(&sjgt;ju6-YL|E4o}>E3ArJVjo8oylZlHrhZ&+=U zGDGQ4c0XJH9>Pr*kk?cco4MQJ_H#YyVWV|{9kI@rJP7oyZ_uSYkT3dWB(3MV?^j4O z7#=@$SESYe_JRst!=A}3-caJO;0l?`Rv~3X?t3doS{m{;dt$ktj%iH>bT1Kg-G17G zSJRwo@JprRWLAa-iK{uS72cUThKCWhfPL18_CfMREXKd_#*Yrc=UPI|)0!Sbz#tY> zc!Ulgnk84V-M}sMyZ82iV5jCq0xXRt)28Hly}ho0)^w`UeZy(5$TYyaL`ux=sPhdsoPo}f zS(8CC^)^kI4W!#ix;hsQ#}q5Q6ENXX(0qzklR*y9(j@}#9L{P{9~(aby~AmEvb82V zxP98r|M20{!NmC*|LipxkKpjqB!wA`(8F4-^F!wdv+~<+u(kKznP}a8AiUryv(g&A z6RR)}c9I6G6x%k!o|D_;wK++a6(6fm#!XZ!mB_p;5VknY=D8V^s?-2T2OPxaDYFvF z$eW|^QpeO+(5S1jbU1=RH zN{D-c79XyW2yrYLQp$*vqFbhp6G=9HCMHWXXv&mnz{5!OkA2U%g=T&(k|{l=^GEWG zXD&Y-${AOLH*ND_pAt50C~`tHuwSV&&6VIUxV7!|o5o!BbjPqw3JvS8X_0j#0k7P@ zx({#h*-}xDR~LQsm>wj(2B3W@oxK{z#RN)6r9qe7&aaI(hsBC`e4nGNF7|?f5n^Vw zkh^DV4x5qQ{o}gJp0Xg5-J4%n{SM>9VZX5k_j`KBMy@Z8o;j9_`)?N8`{#d~F$B-U zd^zNk_+YX7!uD?<%A)R*N0(<^=a3B*;;+`R#zq=Dm*39|U5puJbjZdGo4kUYFAMaV zr{l)W;Ptv<#)Dk?TeN3Upy768G3NSfB=pXHhKo96yoRQ!CT|1fLF6`3_M|tUr9vCg z1HG(YA`f}fCa~wEkB6`Whxl-tjHfN{;y?=A`Hy8ejn&Z70>*9CZKgY^#=8glCS7eFOHrwcTgQey<7vCRf zSCr_lz5T#9w&7DA%AAR)5V{a5l}LlFjz)=%lpEwQJn*po^r*kzjcLLTJ9s*M*l@C% zmr`WbA zY(4^-3^IqAP*D)ylqUK|e^3cj3qr_j}is!BXm{^EctNn*A13ne<7G zBmLA4YW$iL&_UHHlYF?%b@SRaK-lZ~oaHO*0SmHc+ZOm@@7k}I{YxKHfZG1Az|yT9 z^O#rV@QQUZ&12`josvCX0yow!5x+$4{U%Q~SdZmN<_8ytfRpfwyhap&?*zEz`7Z6# zCir`v96$AcgKtQMW{$_+&y!SXXnVoaJqpjq+Dh%r&oq_O<`F{mF=;cUsG>`&W_bk6 z`^^C*Xb1qJ2aiumJN0EA@cJeNx|iM$&NZr2c?IJMowgYfnAy$KwL<;5z@_*nIP&;Y z=O|7rl4^EcSFN?5$U{&OYYwaa{#VkLtg?>r-82+G2bQJPVR0j%K_f&#$hRpl7zIxI zm;S@2MMaY8N(z$wFm)+W8My`j?-+}Y4jF-CChyXrl0QG&CePrXh;(~_ojxE*$dv2P zBGOPd#)eXBqEJov`DkxiPey5yqytDPKQjVCQC{_m9+-FH1K^BBse!G*gjj}+BR^g+ zFG$Aq1tFYyc@iGtJXfWquPh;>PRj%UAmXE{CKX=t=8}w;IC^-jLDOg(5z9IZs<=(C zkGKtzDsY!9;j?hQxmZ>?-O^TWJ^uLNx2SmBfzeQ`V(03HzoW9zYr zUPE3;0I%bA^I(>CI@W1j82qS~7j`4ETBv5N7R>WZF$)WJxx#ck>0#K~4=jW{nx%$< zyroGOhP!;=6*5bsm^f-(cy2_}3<5lHzY4)v5>#x=v}QYQPGI%<2fT%`dH|dkSyjU- zzT!pQKC#8IgZ4F5N~!(R^YXBu^_`!zk|G9%jfDZG>sN)*$~Od%}ZFN ztPqqii8(gSZkoZfBN61Q-h}p#bJBvs4gDuRFn&P*M4b7z7L*{7DOCH?HcJ=o@;16y ze^Fs<8k2MDez38p?$o9qS_e&)!C%11G$E_Txgj{lOV-g3F*V4rdE85I zRV;qtbk@dT-2;5>02DoX_y%W0qK9+o>0>XCLtS-S_-=INSX97Vo~wm^D}(q!e};s< zMoR;Vfa}6T=jr8YENuy2GEv>IdN^q2B{iU+6?H7au zY>B#h;jg(3Er|H=7g{C)8s(}a0J+@hy%<;@5%5x)#G|el)kGQJGx7j{7(vPS`7;0^ z@6CduvI4u+)CNmM*`$sJwk2tOZsN#IrURd#y<+8w^R93h$5fV(2L_GehdeR0((=7K zRm;a72djgwo-Q-JNX#zIZZUHbDov|bJ9V`>3+0tyly#gL?FSYKillQ}S^Kc5({RA|ox{0z(NBtZM;`ncPPf`u! zr6IGF2O3NzZ#(nfFJ(RaQlG5R!IW+%cW);nbtvCHm}vImt4n>KF$Pb1OkQDkOkKOV zymg^)+!PIbIp0szE;hBlkHrB*7oM3sfYtpFP~!-KZ9os(qc3~;YhP@h8B%EVW4Z+d zdkfZTFdmBpmqv(6zzO}kq~M?=KJU_r1{ZFReL6vGS>qK!XKG~M`(IRtee)49?|!-ey^dbEzlS`_ zy3@`IAC97o62VKgNn&oivdF_N7%WfQI(DfeMWR3#o+@v(dtRQ(~*z#sy&v)(TN~uPvV`8-nL2IsXf8=L)G6IWt;;D7z7XaV|sxqnqesVenwxq^4 z_wEK1Iz}m-+QXSKTiVodUL$=gzzb?5w7k8%~bL7Qldm7ioRRJ-J9Gm&f+-Osa{3W!NEb`zB9YdiE!%;Y_gvK`C#2L(d zS&(FUUD$Q(AZ+Z{;>1hLHI_HNnE0@`Xo&DRjFI2xY|7!YI`>^N;DyEuf;P%orcy`S zFgv9-c%_bi8Y^STH~gj&K@Il} z)wPan8y#5huyW4N;T83UWx()E+e&UQUip&P7KnYkoh{f5OMI%w^v)%E8clz~(zW@V7j736Rd zo>%vOuf34{{OKUYf^~4z(K=@i+km#wULezOb>b=%gQ}^g4F7u;lf-}g(+k>W3%7kY z8%-Nc6=5L*ZAm(uK5UP@RpmVXg8M!HOWSn3vA$iKBg%tJU4s?hKw_WOZ(pKk8EIbN z3V&$)fUN|lX?;GNrXqe{7M%F9wKvI2EIoHa)e$i8XND>wBFaAxoF@Or3I83ce^w_z z(wKE&vM%6NEkF^o2bwZxcHB8PJwXV)tOkU4#_@|pUuhG}jmH8E$4M+?K6Z}7=Jv(D znwk|aJ-}GqgNnq3k1oSJtm0`JV>je#`DsfSsVmu7tCPspSVDnu^hIJ>c#F@$?Uh*! zg)knkhTkeo)){M4Eq=(Qkv$~jh4k4Qocn7|xpOYM8E@tJMjXH3)LK}52>F|;ApNRZ z5)HSqv=}n7GG)-J?Oukyq(Yi@^5~(~Yf< z2nX(gC1+~ry$L3a_SVP1X@zkN!UxG^8JV`pz`7qv-i?rYP|Okwg)r zq6BB2)t<*n-W+Fn_5a~1^)-;-zra=k=%<>z_Bia?tk2qegomgV{}{aXP0dZHh#oUs zRigSJ`^5vFrFbB9Oi@*IC8%{S5GwKSnO-thlw=ATz=7m5$}B$ON1-!+UPjaoQMc3+ z7Y___cKB~e`0w%miw+R%;6CoUEe2yOn=EkiDw+X7c{xz&f!07i zljZE8e&08!8< zB$W|;S=elvZ8BlYQvUq+`d^}1;Z_w|SgH7~xJ}@Z4sf!(;B@T!^7pI%zZ=${0e}!T z;059f1jLufh%XQkUcP`g0K!W|BplpV*mzXKM zJmbZhZTqx76UD>dpwC2-!I7HsVRpa^D-)CNo+;bJA;Mulnbt{vQ}prmdB5I82qHhR zH@y1(2LOUJ5;-%k<7E`+-MR}$1lzReFAIp{A%x+{HC|siqgO%}?2j5fvpXkDblFRF zf3(7c-mDiW5iwZk7bN+pRtan6-ZFg*^>|`K)y`*88B@j9{M5aO>dH?gLs<0NwJxdE zRrg0g*}Scu8V&pST6=yPlr4MyF@8%EBTO@}+krkypOW5ZzY9!L00^M> zndO_H4>AYf+IFOiwx(FXtRAvhP%|{r<)1u)$T1WF&#w*HJ6OIxdIuIJEzqz>W#=V+#)TFU3RSHD_AT*Z86rmdwVG;8R5$am_BLo|CxuI4den zx^U#qDFBZ15vc$oMTlc)r>OMJ^Qy(Q8vOz2XAdhJ8PlY7R{au-XU-Q)C@bqmRDAFP zQCMruv@<8Y(Xt&{U>SlO{sDXzU6^VASk_k{Q=dNzBA~%@L0sbmJAfD#Vh)#gA7OIs zhf{-$tX22cUl*PP?de!?C4z6vdzeq#mVR^%M)sfVGf>+!>oKom#aG@Bl3JK$oAa|x zb#IyXC1DR$Xhrh!KDI18StUf6b0v&J{s8*z6C;GWoK?$kK&%b}%EV4HdE6AECl2!m z?|_huIIsN8Z<_K7X4MZWpzQ2iI@xI)g! z&Su@Mt%pl?XCWsQsPw%7%d8{4Z!-m?qx!Hg5N9DJhY)5KJz2Vn+30S^-4X<$JGXYYMOcxl z#G~9u(Ghr$lnx^f@lo++|IoC$F=6r67Kr3jvCk9~j``v5(=OiY3n7>g}m&jRwu zs~T;KOKBE7%oB)S#WcBcdOEFZY!9`uSzMfcJ$?16^BjMdqsT`_mgNGq**n0g4!Z_E zua&wtm{bNu<-^_6edNxQka3;4UQw9}%zH&>d}RrSDqyNr4$~l}P<@P~Myl?)FA2=6P5>aZmZNk_GYIjcOpB-pC>Z|&R z>X^GoUJ|@_QVO^>Qcuzz;pMbg*H-5u;O+RKHY%pc30t8jPNx}hg%a1$3>mPDPI0)= zI?Zb(6=biNg@q09lf71)L7Ij51lFbO5azx3VUo!5y1}yP2dZ^cnpn|OfA&%pQ)qN1 z{u&iBGV+qe{Bd`5;lut}`yW7@Bd#bvk?Uax%b9fK6&)tw2+lKwXK<8>*>*e8z0=?` z0oNY@dJZ{ey6_VhJayo8XKz0{=wfZ;qEO~#|J!#3{SSn|9p^@|mPWV^BBYyajo6-V zi@Kou<=`CWP0edSh&`ru@69#>CZgUojhppbZ)Y2K`_HZp>Pe*GNq7TxnAUM1&^&7X zoM9-QpmIht z^l?ae6{!l6m$s42IsI;%Rf+1G9G(G&o=LtEjL|(0HH<(v-B*B9GYdRhUl{v~A!qJq z;mBjQ-~a18 zev-cP{sg6%nGD!Is1tS+9;R+#Giue8UyMahu*ge~2a#1@8rC;5j>IkAbUPbmLaQNg zhN;u7`R~UBACJ3jcE(k<*ZDe(O`%1!I)rBH^bq&zh$bd%@z!y0Zl8@=Y#WDdP`rBo zKHx1tyTh&Et;ar)4gXW8tZp{CySXEn6BN#Zz?Vr{m7msIih zZ-WG;so3`Kjov0_`LFfgnip|~d5QYxLNaBu-KmD7s}_7S4$C(wa~w;Z`LhcZUs;%= zIE~~fHg@pV*^+&m3Eo%0#Hw7PUiqNeTf_1Eig|9Tit`v1pK%CvnadAlTD!` z9B#XKmsvFIRU#G^nMJ>=(RA4a^QrPuq))b@D>VEWrbei51=AS)-?&9BPf&3$KU4Dm z0qp93D81DSwJ-k|)13`hceK=W?yq4*Ts z{F){=Fnt?>_I_xt`&Yo$1zTX5KBOh0CY0SygNfz(LoDFVL&C^oS3{Nn{Fg+3r!<^G zIv_T9_kYk!r+U4;zGS`Cu!i7c%bGEbs+B`{y;KE=&Bph`&{`AKw(?bF2V-T2T)Z_Y z-5Wd_M+@0j+mtJGOSL`eJ#>p5>mkO=$BvkL{;Fxi*Y2-7_Bl zV39F0ZK0`O&bt)-jmqQUM}JHr{`g8xHRA=|dKXXmoAvpYI;<<|DxM>D@^0q1a45AH zUOl1@8fWFE(hLw7Qq$fS{fRiA^&8eFi@snvLWt850}kwzt2c=-wbN@ zY6^V=XOGm&5Ud1w7Ata_#aQVxKh%3x|ABmtc)zr8*FnYa5d3`dney$bpA0YT#8G`? zuJ@Dv0BQ!&_XACFCN_9}XYV@+0ULj*@pdQ8*oUnIV-3_?SYtNZTQJQ$JU;M1X+wF* z<(MpwoJFK-nEaBGHTHjBwFF_YVh|g3P=)5TI&Wy$GnS5o8@APnhx{ds8^pf-FMYr+ zMvG^fa-%se5tS>N7gnF|1z7E{(0}fz&xN+K9}U^0$=*(8GpPR<4>yBgNXUsi-<(p+4g zdg{Gv3&dO;S%Og6ZOrnHLMTnoBaSx+@Uvs7{3DrEx^$RBW-}%i)VxPwav4&J0Z!+25rn6||8` zAK$R*ch7p{pjr`}jQ8%_J@xS#vTr*Z!VA+(IP_P8BieXmYqN!N>~UU|G8z?7Sprxg zE2TwwukveT!P6M7*)k;f2=d=!WBBr26})Y?T9Mbi*Pg9<2OhSbdKm1OhYHUl)+)y2 zWV31Suj1FdBd`wJ=l2y!tQ?&^RAK46P-U-KmrFhU7jY#0(rhlL4-08 zQurD9u!2$Oa4SEa0^@N%E-@k++b8*G3bf&W0Nc#w&YL>-cuem=C#Rnmf1H?DFV-MY zEt;sef4QcUvSg)SR%r-7cpqM=5H{nt-U z)0r-ntwLAVK>r}w9m`_Nl(07PfRzm2i!t?s3($YMe*~yeCVZkgOUd@?fU;zeMe+2F zEh-UGP*-JG7un8~HgXynsmW;o zhfcWwH7$Rtb*%5xTOT7RCexKE@ZP!nHIUh9cbg}`zDhxwDu&j5WNa;iC) z-}DdQ`}}>q>&xwe$mUWpDyU(R`OuhC2M>RyH|&AOUh(XEDH9H@IhWYjb+PM_B_7W z^m>%TcUOIMSL&@}>fMma>rKTU}I%jt@7MF!0trA$j^_q(*G&Dml)W zDYNpgp85lz#844?GiIhEnzxpsee0hPqSL2Q(pFbv&Spe7IB&vR8oCKxlu{3mRti6C z({ij>Q7PclrjRv2t5kJHL1aqxT?F`&#Gx;&5~Fg_0BM_LgCX+g%{oV3^V!YZk2|DdnFv?n7KqMiy*7$nB~iGZ@ok zS0}nv!kMEo31`7<*OTahK&p4dzPPYjuh+qz+cWmA1quC95IqH-RZh&N5h?rL8V+($!p4FcQF&7ZS-fHt!KvXTFm0D=*Ek|+JRSCY~blB=AnjNb=1Jk<4O=t)LN~U5zEAMf#wV2)4EHP z9*I+{qn3&N#(9Ow%r23HMtm{4BhHVrIrNS$nfi-|o!Q7m{;t{sT&TwAFYiVTHoRp& zh7PvN5)!+qKfkeyfG-5tbZ@|dqDpmSRD0)=yCAEZ4S8AyobhTTQ{$t{{j#?KO%@Jy$deh8OGc>tr44c#p@hy#=I3Rg#OTNFd&w`$M0}p zNFfl-T}#%w4UVm?wy2&Vy*6Yc{6|n5=?*r&nT_#S8X!^+rNb>Yq&u5&`-n;$-=$*Q=pQ0L!_cft!;deAQj>+`a zk9;?*%)*5&+#eimG&-IBW0dpSCy4Jp&KtV2T|brc*?obBMcevd+j37(`|8`!>*T{p z*K=yM=q$#|#q#&t;~<<*#i(7p>VzMwIJpG2O>|2?h6r7&sE)}ICHXIzj?Yurf1GL4 z3(W?3i~2W4SpTpRWaZM&G8uWl(^)Skk4t_P<9F2UN#2>tHizF&lckPSbA72QO~tL{ zHXptY{hU{nO@B#S%pGI-e#df(Ho9W}i<`A7QL!~Y#Voclh1Cp=Rm*9fcLhE0vng!s)<~kIz%BL>I-g}()MD%sI#x9g_^6eNQU!lyh6I% zyW(PauuRgTl!hx)V^1Svb>hfD@JYC6ip;Q-MMQ#k)N9L2nbEz3%NZe)k%R>SV2_Ym zBkPWR5W}6^&n{`8f1O^#eN9Z+p`x`B!2~U%^Q0kB_neL^7ZbzpMP#R{dtmqS@X!a| zBEO&JMZ?H#ijkY-g>0wS&m<)dnJdKYcXUMcCI0DAtv}11ZDxGop;Kao+JdrTUxc+u z!xn4Yj%?xFic_SsK!>69!nS1(;brokNZ8m5MoB8Ebm;2<=xgH{_j-v5Y26&>;c!S zaJ%<-+W{6(+m^pvZNJ*a7FQ9k@U0CTtt936olkp>nBMSxOU_3=%YRrp0Qlf(?09}{ z*o$N`CWF}(BlFjQkpU{;m7x>=I4qphZnP~!!65dO*Va{I6ZKEAGWRfe-M08^Z_HX3 zcQ!d;cisN-l4V6+W;?H7VCM((jn`t(DasVLWNwuH)hDIXV-5@d+>NuCP{zd?9k=1A z5U{~&ai(!Nt~n`ORry`?oCMPE(b*kOdbKN^pncK`w z6JEIDX?&w6!=8+8e!wdjrW(gXpnv(J?;t2{Dv0ssQ6d?FS{uWgQzxT{bMzd$!%E*_tDi#(`uvt71HZKn_(^g0aldQ zi|qYGCK+f-Ym;a~qFaQ4jd#cl@hq>$k5w{LZkC@Op2CK`pwJ!H=iKu~;Gs-ZUmt(A zw#f>4sOooYe$mc0?3YC=UG(Bs15-;3DD0l?4zEErCbb?%T`E?wIv=ct{s4%5j2MG; z>2{lx;dE3$ zGDcfynL2jY18lXz>A$0EdGW$mLO0$2cK@YuwOx5kyZhtdHofo~2jT~7yIcSMK^6Gg zdDkh}`i)#E);%jZO9@AtBz1gTiSZUTPfaMlmx-UakyP!+hU{(`773i2K2o)wFeoHj!V+Xy=2DP@6>>2?_!Gu(qs--b)YL|GJP$ z0Rm96I|^kkdc2NCPVIY(T}~d($_LV5fIJPX|p%?Axy^7^=@?(f}7@wI={7YGxVjSLK&cQ0eDsj!Giuy(bDR|x404u>cZebg0~0lN zw^R7M1Rb6bV$?CY|0=TVc$Yle{`>Jz6Yz zEQO|qzwvGKi+{DV`uJU$WE{Mt=~O`!jMJYF3;_4`X>_#i)h1eMG|oDOByfFu$$t83 zKGw4xTw&AU>+mc)1cvw|?F*}Vw`!a(TJZXy=-Ur_fg=6sZbA&2<7d=5^_B2yDpVtGDDI9fZcd6yP6&CjA$%>8 zymj^vXQSl*@GNe2hIkrwPMXq1uYZuI!8X15%;Ep78-{SVfWGyKBS@nAmx=#v=N;7Z zyz_WVC##30IO8+Or%x{~!6y=*7RlK}g`obz_jSMF9MQn$h#)fiAn{DMFxktrMmE)f zF}%u&0ez$C|K$ERSp!5`GAn70*f~yc?oVHt$J8wT>Mjys6o%fVtSvCjkL*=H;Gk1>tSsKxAJo6i zdY|U+&9r*>mQB%t*$p!r{y%VwM^e^c+9(I`TQS|WHKwZ2hT~acKjq0MuS3js8{OZ9MKnsgQy{X=!J^_#c2AQC)+?^1!Pv z?S5}g)l_zb<$i0>1ted#o=do~B@JohhB(D}S%xdqE<<=uGH(h=LH7Fh4*i1n=t3%y zZAOG*FfzY{DqX&eY>qI{1$QQje(Z*VvY}B*^--ql9nxcd*#~#WZECk7GEq;keSRL5 zZE6djcJ@*unFU?QNJ~asJzwWI6ayM>! zt`fz4P#x`CI9b4u7bO-v)tFT1J{2^*Y|x8u(8zUbO|JtnN?mobquU<}dXRUqMlXGm zLKgIStM@s_2UYKLhR<7^vYTzvEyd0R zCt?p^tEj+)m;L~f1)*xEIggGOxwMn*zm*Aptfj52-oJU+t?JJaEYJ$88L;1}n&W6C z!RZCtIZL1ITr)12^HIitMmcK+mXohf$GX?RK5wKB8>8;&Yb@p|P3fQyWP6lH6JX8S(=SmGI!*(ovb~H5I1j(C}!5&N#o# z_Bv0sn%6P+*~Vr4Blzjo2{?aE8rjNLLKkGW(_1Sm9_#rcQ=N4QiWGSgo6PyEQsKk;*& zVm;CK!H+j^hc}Ck>fN@pwPpFqV}HMZbM&VJhy|$GWU<^7vD<^?8<`&;&{8?EZO`=F zecVrOUUBznyDi&=TAK zJ2B6=K$V~4m0?TM;7`(lHCM1uo$vf*2%J4lsV*4L)7R=46_$L_7=bmx86TWd)!3PM ztt`dZ>Caeez>2Q5Ni)ZEQJuI$ZDOV4D7(Z;RvwFBP}pVhFR7`5L?xRZppnT`hNP6Z8lH^O!LG{2Sza!_ z=EAO4Xa~}gt!md0gH?8-eyN`gP+2&|VKYx>kS|B7620_|p$y?35=15axFcgLvqt@& zJ%$Z$xGeggJM6;27z(oc8!x)G?VGsq)Or8nn1h9na;ZFNkulA7wdJVyV;n2mxlvkWpZ?&^6m zYnF`X%n`RaO*(C|t>bnkysR5Y?6S3lFpa6Vd4Yy=R=^aofmBAz!*SOFVJbwC{vwRv z4g*H_g@bdDwozqLqusM^oT}f|tlbn#DC6XV60S1ykQLHPIoDK&qJ}2XXg3u4iYti2 zdAU|Xh#1%CLbESR>ay+1 zAHyxMAS$yXokEQhVUgZmsyvYMsB!<#&s`@wPe@(eC2|-opjdM7`VWoj2j0#WzET+8 zL*yv#UGK=g?$sf{NO;SbQI>Hfc6g3$EE_yU$`mVbl&0rN$Y|RlEG%sJm?>$9O#B+4 zq0g3GWZ*W#%}Yfv0iA_6|H&$aC0_hRGw`!GIqll&$s62(|585ngX{ZKTQ+$EYT+dr zur2Xw_5W}eV;VpHl&Mue%rojAz|26ra6`I2PJOXs@SKXfbUBE#%?HZvBA%m)}9X%}z7CPtX4Q z!tsV#yj~%{)^vk3F!LO(-U}omloHo)$zNCsv08f1A?(dr`SJ+W&L%f5wtVrE<|RRL zs{*0hWR=if+1Cgo9iry{H93+nOy0e)m#PH4MP9Jr_W%FmKjs11vqI9cAP!YW zJI7v*8(-bCX?RKSXabtrbYqK+*X_1$PYiyc7QOaR_j{>n+4RJ^eiA%gv*^g4=!o~; zR3x za#R_!#ND%LFq&^)Krx6;l#R+h_|`^1nf*bSKXiE9f^JPACV{dwYFm@T>oDu|ShAfm zdTI7yDhQ#M?kOjZe)J{VuhK0&u3#@^I)VNVRKGOBqRv)mz1oZQOec*^ zvwV}qrBX~!0@T%XIBfmp?1j0$J#jU>(_d1R72aRlMzCg>7p*l<_p@K@Ea}dS(CNfZ zouon!LR1+|p1OvkD;M6P*2sOV4UK3# zQLxVb#UTdu-)s_DH>bZa|N3B!NTlZkMDRO;4*A{4o8T`o6Zg@d2<7>78&-ZWgR&GI zx_;JP?SgB}$l8PprYtt@guDZAy`BLC8Fjy&Zm4q=#mj0%_+4-|_XhT}VJyEbD)e#8 zrcTITMqFw`6qjsDhI9gbR$;OaQ5QTdIE6E|!k(7XX4CIme{fc&Y$8*yjw^BtCK5uy zz*+Lfy#Kh_nUwzsTAPNy{X(qz7%&K!(ZR+ufd_tRsO*~s!de%n0>4WPa*yIx=lk5hUG%X$x6L;wSg>Vvx6=+hefKv;8GJmB3(IaRfko$RQtxklvIaHN^KqNAkD?w`Gww*Q4%w9O zVZ@*5$Py1(L~6>{XH!gSnNo=}>L815;Ax>NwcaILd$Xwt$I10s=W@%c1Dez>b;JC{uPXa%tTEqo4=e)UKOlE#7Xa|UTp_xIJ~nu z_^V2NBYM+9wJ8f>jCxcdW4C$AiI#!eYwB3^jN_}KCXayuR~F%J8J|kgWZe%V=u_Hi zr_Go7ZOS{BO5cW z85Nn8oa2SD?n@>*9Gp478_CggPCf>c=4O1A6SzKG)al9z^YXd&xs`eT^3!}`KHg`o zGFj)9`22H;;6BH}+wLRP4;_;`1q=8w9MNV6h2#g-3EZ3s3&`ezy*^RBmTfKhP|t6; z3!xVUK67&zu*@@zgxOhLG1FX&>9fV2fD|V(-Yt~tmxNw0q25$eh}JlmcQ2{Ox&|Ha zlNMsiXCrE}U@F3Onc6Em(f!jxrBxOkpt0}Y0n$mr-}3ybLhH({j|U3d4EQVxIbIMB zkm)h0mq8WR2q4`zyDcr_VOjO;rype?F(pK>KVz)~+(w`C9k$;c<*bqxfZh1(ZRCO?(5=bdj@W9SbP9-FVCUM{pq2jp~y=P!t=tny&seyh!q0Mu&Q#}J*T+-c^=;9n!DTNTJL!xJad zi|K(dzv0E9AqU0G#1HvAMEUVPjn=R9`(zp!9BFUybteX%U;SXYPTRiQjs=+hlspRX zp7wJ37R|cq*o*J{hIM${FKND!ze&v}PrKB{h}E}a^0>mEP|}6nhlcrzI!77y`_I6$ zrCe7jLZy8^gIlnekoTb#^(mOTRTb`Czh|A(UVj8s;~FUyAZ zH8~0`w%?drBs!zh`FuY=Gz(S)aWacLVAV?ROwYS-Py>tEl za^;toFYNU>gil`}X`#vrP48+w?9xCV-h`1bL%1XxZ%jG{15TPSNe^Bv7NL7z^`V`M z;brw6MMLx&@uO^_Z<!KxUU{MLVUFiVSvk|vb2OT*WnL^CU27H3Mr{zXf332lA3SkTggVh`5IGM z2E1io9VM*Q;r&Z7l%U#V2|Dg&(jz`svALAikc2nWO+ zd8waao+>LA1w*&;!OZp>#dUxUoAM~nFgC9|y`gI2fmt&M5j^kCGwoELL?Xdj4psy>Ex?sDNc71EnTaeaF9u zOPz>?D>}+fWGWE9a{q&K*EDlzQ~aNA!-uI0>H+Yf=S+s=8PPNm=8`$v0?WlMoo(0n z|4@83@Qk5~iO9cp2!xbT=IYB*XU6#~Q9g|lr@LyN5KtJ29FaaofL*qG?IFmS{)b(`X6?Ej@A>RnNU-EP6%+cT#4HFJ^*-^%+M* z=0G>CPLe!E6gazOYi7RI_SBv;(3g_mOb!B8Vr&+{F!SD5+qjqlr!EE!@(5wlq1`hT z0{3H6+tgCpG3OTZ9xX^T>27ad0|UNA#aOt%f6lkgtW5XeUYu2r>=9_w8!pln7YVD# ztwygd7^-7kX&(lCtqN#3m!eW;#-EMi^rGOEj2L;heTc4w!8Ql7Ap9?7>vPF;O3aj%|}+-m!y(2tQq z?4N~w2tfZFU~nE}=^2D=BbAR5Y`8O>j9!do5iRGu1pfj(F@Vb&;YN_V_3(yn=?^Y0 zGnoB)P4M7DHAe%iR7X?X&wWQApd|>$dW(o)?L47zHJO|of|J(amsr4n%A3{$`I`d` zgZ)=SV%plsE4Wxa8v!B~M4X|PTnxgTvT1}yo3YFCnalE-$%G1r;ukR2mJP%>euL0D zyvD_AW_J0AjN97tx(XiATT1AcFRb#9Om7KgD-0v;1NOa#(l!UTrF}Iu@pQwFhYy>V zI7{gJ6&G%gwHZ^$cGG9NaLo(t9Sow@wA;vmXbRt&s!(>~gL^H!rDp9Pg*w0dr8mt` zr7@wRcY$ZY^uPw9vmYbr&o=}~S{mv10m%07fETJg}o;v%x`P zz_`P}SKa>Vd`cDm^;pS~EcGD^wsQVaYedR1t5o*;o{x5i!8Wzju7pPY+e)(z_Epfu zX#c`|<%+h3M^|ieHFLrYRUg8L!Wl^CQrASCYog^-D1baFiRAnQRK-odX2bhi6g~>^ue~5TYu2Vhgje|^)OYE}ICW#?qv#=5g zlebQbSw$*~7xbMTy2UZyV9K0~gD`QS)To5}CTM~=Xu&4T5p}PZLYKyeMM|z;K%d)7 zJq0q#HRD@F1x`^N6UODt0pMeatlFC%xE8u-)jkRhNhQc%x1+r$EWEk-DX{QL{dy+x zMEae-L(#VtY0Q`%4(2)Ee~ zZvHLwH!I_82}ZdBd4oDshJ02Xsz9C;AJl@cR?Jo}v{Y60=1%lL`l-{TpwwCf*iXS* z8`<%s;KXs;#;|$Z^8L78F0tl=o%TgaY49pgv(^K@SX6T}(o+Pc+GD=0vJr)&@!ePc zye|f~$LK5GP99*`Vnh8xJYq(uJhB~tp}a|oH!-We9%S*s)F^5xTHTCYCSo8PT^4m; zJ){s{B~pPps(#P5a;K_s5PP)KuE>Glgsr62)ODda794S3-_p|N%{eD|pg~d&3Q%Zdsjc6KoD_%p1EYpUH|Y<(g1?3(el%+sYP<~1MItHKPZ&PnJox9m#a zMdNXYlE~xC@VX9jQfIspJZ(<^EK(w(96vbk%eVim>0!}46KN;M$VH(!xNZJ5N-Tfg zLGSiCll~pH{qUlto`_VhNxZh;E^+fep#T z)U!sv)Y7fEDB-UC%Y?|&qhH?Go}s`8Kjs>AM))7Mec>8KfZQ#TjzddXK4c#U_Fo&B zaZH=o+35QuRbxvt;MB--^2{_%U+R3XC0N+c9Yy|HLwh#&@ugY7a&T+%5?_jA%a9n< z7wR5DB`2To1Bm3TLqrI4xiHh;M8;0x7%HA{J1MZK!9_KpBD%XN?eTT!BF>%qqrnea zm%px-MN5iA&ds(MkH3!}7J&|!ypSE)% zYEN-@NAcbA_h!SPK8l>?*>RTkIW$snnSP=V>f5%;!5Q27{~aD=E>elrjfNBFG$|>y zi=VHK5zJDo2n?y)1^Oy}L&YDCtQX$Uk7-ICnh%}l!HXEvwn*#k)v;3EU|xW3+4d?A z>}L(+k7~KS>RxcZwS=EWSFSWE=F*6}MZp^>XeVsp2!OWDoN9R|xM3(aTh!SL#U259 z>f#Bo`iBxFWK>uzK9l5E$l%U{Ul9Lu5b!HV5bwy{_zok}fTD@H0>FA}H#C)8KGl-T zd@0eMRxF7PYCmoE&MYnYRikN@a-O=b%H3Ztw%u%w_L~6R#hGevC(FO_+iZeo?OnQQ z?cCjI?$Y`C_MCXe@9?B-qL?|%3V)&gPbD|8?{Xn$LnWJ4MTfTa3zz(oxo)c{YBAOE zMrgF!!wrU-PB50{gpSF=fgr!hRW9{?cC4F8yGYpoorl*u>l&Rlx%}#dM5i?w5mCV_t%?#WN@)hu)*)t<_7@N z^q$w>SUnSgi*SpHsU7^V8Z--w{8oYFT_U`~X@}m|7GB#yuI;F}9pA5r1M|Vvc;)Cu z=snmrfBQfB%0H@FRcFqLaJT%L_GJ@EqCW%T8YmyCyWpF_2tMCh4li$5R|-d_x^Tg^l0=o{4^X9 zQ5gAMwU`gY<_;olj{g~uY_EO+@L>K!xC4k!cw1JhO4F;J0TAV@j}#Zx6a(-s;oE9K z%)g|wPO+C5i_I(cKb+;X<>#}f3mKaa&;OX;C6oKJd%js~N zvCY3of1~h*u3d$w&QOk%fA%hz?7JnjKc?cqs*B_0JpvG?7suM}lcX#HJmxgokK*HV zH=XwU=`AZ9V>&$_=UPU{x`p^L*dq8=>QI^E{Xwl;CP9a3Y?}zXF*Q$Pby(j+;*bfh zhB0`N-X*8Ukf7MvErE~&4D2_8#N3ELeTxIE5pfz%++`N*LYWB-TPG3Ucp7RA3W3!m zTnrOL0xusT9>lP2CQ$DMgs)w^teP9K`O}~B z^_}7Uf4|M%+AZxAfW(3(p=I1CAx1L9g5I|px8H%b=&?etW>{rIfm}E*@lf|&KiOn_ ze$J=5x^q5x5)=K0Vl!CspU!6+bI!VpKJ}?^(i!wzVYR9O^tD@M>`>4&+^Z`7hvL>v zv`+NVWI36uQS%?l@{DX0$Z2esPe5>BtxcbFSxqOh3e|b!S@oKR>$J&cF!N);3>xgV zB;rrh?A)xFf-^5J(Z@0uzCFNqA1|An_0ZtXIZ+=mUp6Gww=JG6D;FIkP*=r$nRw(e zL$cC#hx^^$8P}^ z@&8Mp=h#iOa_zB{7-=3M*I&JeMqu^9Uz6keQp)(goof5sjI6@yycnPE$Z)Q)yyzOw zm$?KHvp+#ZqX##N_#N9PH=CWoVCCJi+wHXj=p6BWR=z{g7ko4N z)+rqtqOZ|D!X1}ywQ#^zSv}5EHWBKgUq0M959(fUPB%CzJ0m4gd0y6?)h4$tXkUmq z1zM%NEycTVHL$W3po^}&I5{#R=o@KV#}xrm8qc`Q#$+E_%ywQkM!sso9tEH3X+HGR z(B<9xF4mVYdqd=yJeX#4hTeLe|3i7u5qx65Z_vC~Ku+wA?kQZiP1CdlJawwVF~B5< zRuj?Ah%U6NA}I)ne2A)~P1E4qfTr>9yAc}P+&2N1B#XK5Q5t9N%A8+AU&wQ*_hf(e z=~K3H%zmC*{4uej*pn@UWu%pTWahIOCq3L(O(Dm+r6Kh;U|>Dk5hwQJvtDOr?JFY3 z;BfBM@&Mv9&_dw(?3o2)Sx=n=jRCep3l61K{yLx6XVA}BI}6#BK}?!!t_|A@`lHp$ z%Tdw7l=SKgG3ImofnNpuuiPB;vc)R7F9EMfuAr?7)W@NNq*t^a#^c3L2;SwyRbGoQ z1xnSzMve=N%C2j{Usa+lpr+UTUBp7U^Sx9HvAw4LQM{EYc46kZ^wykFdf|7E0<@&^ zRvyiEaDe(0Rs&l{Cq=PYll!`(cYyTgj2O)}@wi*9<4xeu>HAO^B!Z>auG1e1n!fWP zYZehkIU&Ka1W-n}xNgoz{Y{xqAakfMbx8kO|7}t)fvr}Q9x^6?rUOUdtt4L#jmdko zAJ!_dPxZ!)EdE%@P{`C6NP1c(y1!nnt#Y5G+B^+^OB)Z%WWJS)^O#CFGvr5U&}YZB z6b;*JCdIhRyVu8BeZ!qYZKpLiTR%#vKC++9M!u_XHFKgj1H%4F59dpXzu;wlBFs*D zS}&N%sqAfNGFQakx45!R9EqVa!v4|gz_X+gHcw)V5dIZT;Z9b)U6Yn}$R#V;kprxm z=DtsxC(E>{Y64M;{TeSPhQ?;9?VO+lRF%+)!ew&^l9=T9yQzv@$wR+q>hUy#0s~a_ zUYt+e_qs}Jx94qd5?5+%EzzjnZ9hlA?pSB->$ch1jHPh&&P?|2aXWSfyUZ@Ydg#jx zly=Iuv906j*Oi@26StqQ^egp$)66P)E!6i!&h6Yt#>DEz}1PXN+SU&hG4Ali4)R3cJMYGYvXAhgG zzYZ=qN}gwyS2Ch?#&LdQoYbLf`0G!{&)R7zzLz!@Fs|_A!udwRqj2fbeq1UO986!1 zZ^&JP!Rw?KucZb0gUHRq>h29EWxz>*put!+FFD#jxa{GZ8_#oGLl_nBk?Q<2aiQDo z8X_YCW6{U%Ar}x<_&*dT=74!2wSOo`HX2jnu{aJM|F-sW)BR)^X|WnQJ{tQNF=tX5 zWqx4Q%5e^@GIkPVJsDBI4JAFB&_46bpnjcMn!URLV{_`Is~V_{)&VK+%4c$^pS+YG zB@c;ZKYZO>&1iLO+ll;ka0&J!JHq*^=)6LI5*6>RG5)yjfStWUaB*lx=dq9F&~K1# zIh*@po`>d;;P3p^SoGI}_|G@6;`Tr*UW_F0n)vCmXwf&}8EDErP(m{5x5VidW~`r} z2oO7Z>96UaB2bdVsj;w7lyxu$EM#s^ev)va<;)Ip*OTP#P>r+5Kc>v&%?I03)2Wo& z>D?7()>EO^3Z=f+f2ug^M@N6%q@p|DG1=~s3U@g|n~|1cmviAWtzf0IZ@V?e!|?mbDeQW z23}QX@L=+xYsQPX6!qmw&go>%awm2)+&;GuZ!L>E33KjcIQ!uhChi@ITdb9rH`Gixdus+&bPnCezDsPjN=cWRBX~_3os5EHLnjZ#n?UKG$nPVaeee%iBi;IZ1~lD4trkzfSgLIXYS z)uc!JPUrWOTgJh<+dk3c*5v8IKXgk@2L56uk8*v7{}G2%MB$L0ZRF@Y4=`QqLl77h zL>>o@UKi4aSwzd!DkiX99O*QGE^b$n%fYgZxlSRk*WmKKgo-V{GJ20FA=l9S*UmKAayY4)2_GWmT7KdFJ=_s zO`zav8jRR1M8$4oRW;{tA58)ID<>UIuvJ+KnDL*0nwXd8K7O9(K5yxZ1=9emgR&2l zPoR|d5k+Sn@4a>C4kBFe`3pt0v7bKhUXgS;jjpFXh4U+?J70f@eyHB;hSYu-mtcV9 ztT?MW<})Z%HDowOE2)GG{zEZja^`%J)m^1uY;=EAH<82=?PZy&ykB1S__~AKSoe)9 zK~lc)nm?*Hy@+R3f({ct%4))+4tdR6B_nAgx8vO5m9$lyVt0eazW4k4Nbya$q)p+i zHxyVV{6#_ailwvmNi0KC+~5GW!c!;wK>cBTQr}}r7+2=~ov9;o(^|yc7b7|KV|r%l zPx+(5w*dv=YdcRlx{l38aMJ5K-50@jrhr?o0Ik0U_xRs!QtmM7usCnEs#PmDk|s_l z8e6;i#kY_(Nj~6y^XiWi3zAoo!Tr=#k6eY4dp(NE*YENl;4PV^(YgLkKRKxT4R=u= z%N!rTWeDzTPNL{G82sv_c~1bMqwJvjGYeZ8>8GY00YUjppoR>_$TTVhp<)i2b@YAZXr8jtX#H)|ZL^4K2-I^@RAZhY#L=cg&2) zen6<)4x1SnAFSP!ZSkb?4b-lmJnPhJb8kNyUa%+3WpEb0V6zlT3eoNxP2VY$~M;ywJ?Xb#tJzCZj!(I0epAFFvr zLeW&}JZe!Xe5c1kp6Uyzp&qh4tcwX_;$B|Jk~fBG+O1NDa4zr7)yc6xK;2p|1T=yr zmHA_gX#=g$GZG&F-4K`BxWlcQ7ZjOfnz2&~YGc_larHDzDrb=rJor^9TXzK>2RmlV z0u&u&vWL2L8F-+rw0|geYx6V_xpZVdn+)}Ol;6UE*%7`{cI}_%SBh4$j)T|s3LcB9 zwYM&26v_whOXcXlXj^2%%lgOHekSlEmhJC(QwFT+gVJ5|P zc+Q*Jb^%t55+conqC}t#u2t(pJ829EIa5VrxMd&CbS}0ZU+3iB37&t+npA7SDxrRe zPf&Q3q--?kt@3rO%%S)P9sggF?Z^qkdx_x0=QKabt$6Y}+Vi=qn7lwK!Iba53PXNt zmNqqOrCPU=MrTr|V#N>9_s=T_GWwsF)XFOvmN7rmOH`aZ*!|>2U*{Q!16?b<8uSBb_}4Jk zA=0NIbC^T9VUjK3kNc4OqII1yYG6qf)9Q%mlXdWTOV@_j{(74*Gn1%I3@01pNqb94 z7k8+BS(JS631)+T1h@>ximN@hU-=Bqn!&s)H!vKSuX0OK0=tC?qxVjd^WJAQGb=2B z+p2kV@WyWqXpFgVrmzYY5U*!h7hIEfAF}Y*im+dr)(QyP5G2|rguE0)BnkWqkB^JY zrE1pnN8riT(+9a9D&Livi;73zZ<4ml*qm-1vNXq5IJ`GY6uQOS8&F6_4D7cv>3h~p zBg(~T(FY~VL3W1mf-7H{5aq^RWpiP2h>EU3?18lx&DSdUvi(sPDm2r-bOx!ec}Qne z{Jr+I-Y6AWzD*LbjuCoev%Q;Gv@EAw zI?X7}-eFrv0bos@-<%16wg&+ z=@U@2OoT3xZ;*D<4Y}Eb$TP*fqQ_Pg6&G+@%KTa@v6T}W{_Zd3br368lQ)^KwQRRB z^~m{rYDi-!o9$0S{Lri zhLAHh?J`5b3%11QMcloehll{;@Oc6)Hb#Ha=UtMg9<-zQRKzZXUK2=Pf2o4t?jC${ z0!2=}szAg^0JIv-iCILwjDx-jQmHi{M8=i@?yj3uBM-$Bcg>YgU)=wfe46Z)5b69(9}nfW;#Fh zBt_p7cq!~_gLXuZGiri$_n!YWxP)eYiu+OsSI&E(`vvWQE0NcjhQ-Q7qC=}81bB<@ zBtOH|tGWt=d26-lMo(2n8Sa{MjsSlCl3f)xeb~O>_kNV=6Eb%=NP+6~Z`q58eW~k= zew%7kZpNBYJ|TM&1$E1~p)gg7hf+m54eV!H=B@lpVZQC=$asD~C>Zt9YBg-k3N6mC z)kZd$xF7!giE8g78l-;BkD*UDu>u_D6Yzh z?~xn>f>Tjw+qO5XyjHH|2h&{i>J8~lr?B#i)G@5vMW5PD7{9c+1*P}Nxi=KuAXQzC z%h{jM&(@pkpc8r>DRa24@UmuoI=Z3i-fep>f67t;@u723Q$QR7xqcuy42MN^s?rFxtgufLDUoEOgS`1}8b|7i&rEk!sWv-3-r z-o4}JzajXol!g#HWvfLhB{otUO~1}ei^Sfh(%+UVP7f}=^4nTU9wKA%sIzdBu_IRreUtry19AD$nrgHwV_LPXdE z=-k?-4Pje+co(G1y%fNz{hiF2 z`tBFwtpnoWuCxR86}&CgObhvyYHXyS4Vv0+v{T?Z zI0TgSOFAqo^XMPSJD+5o)T?#10`>d)sY8=I_Y~@5JMZcYY^g4+LcO#r^GPk01f!XJ zk|ISuq`GG({fe^dSY_Kw>}}aS>}W2)_L*tAYwpQ1eQr!QWNbNR6_ucqm`C@7WpO$= z;mBy5%MH1^FOq!Q;*9BPA|Lp0aqrb!_8dnJ6@8Ckob?Z-TK6By&uzlam}^ZIaWvR? z>JiiL=+lWC=YJ@{`X7f2VVjQpsy9z2Z6Nd2va}8k)HKygnS zi?SW;uhZ9QyOJ4%cXA)_9PO4JI@820o z+1=lO>Ewi9^PJ}H4J?5iR;_HDOLbf*O~ueK>Gy9-$typ|$}e{aH2eT8;e2pdfuJex zU&grHsvxdgLAVXqN&FjKjB+LBLpde-!!Ac2LGGt8LVczFlX+tz_rxs;h?3iLN;Olo zrHwRiTFIB?bNy}w@X*(N$}pF;w;d@Pz;D$4R*ibiJw=48eI42o3O8wEm0K~}yRG|+ zgf#Ef-tPJ{NhzhtREo$TY@lhdjBHTkur|ZvvktI(x4<|Jjppo^h&UOk zTqUE1bhwdPYd%JUI8q$}0On&{(OqB-!l>uAblbHojlcV@sb%2N;L?sK(V0^LBR8vt z?6s0Fbw|wGEylWQ_4CCur#}kqs-6@cXPbE8wU`%06^HddgsZY8W#&{{JbtJ?vuF<1 zWH3wmH9n3lyjsbh8qV;UdVES=G_-(5;o7;P!Kh)*c8xCmuy?Uv@<7r(ZWS-mPO8$; zj2PHf0tUT%$=2pdxMP~VgovJJApcK0F?J6&xgpfK9=a!j^>06S6&{!zLXO&0#!`#= z*5iAMhc;t$V>_(+a-PoY#)#0W#w@m|aFv+V?+vahy5c5O1&N+Gjv*z{_muEUbL6OV zoW{M}Vaavkr1xWu8)fnRM3ISmK)pRQ9a=_y@k*-dS9R?b&%pAaz-i-vd^UkOe)HJ; zyjkIr$#2PN0@yjJpwYAZ)ibqf`}(6{K%SJW_MZ#BD(S}cyniV3PJdsG;O!Eq=^eo$ z99&a9X-mZRIDlOr{-iwU2Db(-q71e>c+uujsqUpTfjs8=0kf8rbg5XkJV<-b0P;Gc z3bMoDG%wiRax z*;3k(054w-*n*!jH2tl#SV0JrtS`>cN6!vorVgV@(0}Mdsx02zT%K>sDmQ)}22u7An@MAR2h!QMvRKA_lSnJU+!;|v6u)v&eeqdQR(KK6Wp;qzEs&V-UWX+~4ufgDD z#2fkj<#zE(TSZa**4Qtl?SwnJw^BA;%`>>36V`-VOPWOsdq+4(Gs$+2Lj{3oJ^3vS zOM|w4oGRg=UUrME?3y;`$p2dozuK5deJaMEm=m3a{e3UWbu#qy;gSEj>)=hS0eyz@ zss}VOr4GnA*Nu#G@%QCV7FX0= zjN_kKNBUdrV=Pt`+l%j44@g#XkGnoK{RsULRO62w8vHh|!i2wKD~+R-0`GKAQxf)w z#+u#fbrpRYKw!dk{YY1*@}%R>ft&xDOlS3i-!mAMKW$-2b%=71b{nINeFoZn2u{$85?E7MB)iCuA+Saw>HS4(%lu6+d* zNI^d@7iBddzM_+mWw~8L>c+}p)^}n6d=+BWL}d4t)ydN7`$Fjlhg&v^vIeZJh4P7w z6r^lN|NW{Cz+T^ZN`SGMRCn8AnXxkL_rZK*hyI1P+UAScft7{_+y%vbF&Gx>d0i!k z3kr-$?rw=g_)ybOjX~Qpa(5lj;4)=S+v_AWs_OQP)AU!g3UC(f5YFN3crn7Oz#n&l zrbo7twz>T~466R7s=-e={)FUFI^`U$D)P$I4RgiI8~+Mvf0BtCjWqxx-`T<>Ss`g( z3p3Voo=$SsU{WzO>-u26rUCOB>DXhvZ7AE>nO2A%M*b;D!wp*F(Eo=l8tc@j!MV{d zQ{^))Ahu^IRAJHCd+L$eyTAN`hzDk8=F8`z2lHf_QEOxw32}du%YF9;FuGTT#alr z{^uy+FWc#?JrUK5-1-5T8V)rS=_rMl`VTN=4{qZ!U!u-V)=j3SZ6R%%H~d$WcK*}8 zlxAJl(!P`hB_bG>&T{`})24-q$n^6R0W9nyr>wPcRp#7J9X@kbOa27Z(;6$rUB!j# zn9?0gapK^9cX_Su68f9vWWHn$&cj)_-2H|^`S-Dl4w*VEQgoRL%`pCpcYGW&E=kk1 z**C3P0p@qh`9Oh?mE-6_4b2(*QYx`eajo_=oBI7kB||2rRf4OnHXoKFc7W)suQM)q zaJTqC-T<3`HSqElgaUqfK^)YC`fPchBU0sytH#o-N}_@ND@AYP(R$71&hRtDp~Hq; zPs};H+W~XWHzqruI`#|Q&mxCd?724_F9W_3_@~k#g|0SpuGa=$RQR7lDGH^gkn3#K zh_~OA9nLjFfq(K?BcFtlY~E*Nj&kycAPcFcOfN@$*vU({9K!gN1`BeuU zO*v9cDp2Sy4NEjhJ5z#9nsoth!`E`bfjx$X@8z&89rk*bp83xkeeykzUid7Qw~#8@ z4@KGi$h*QsKke#*^+vq{@|OT4ZZ|-uTwbv=>*#DQ1(#E{(T9u(OEICHr}OBt=_71YUph?O+x!Nm*LvrOA-bTq7)p44b)l?W}~IDz4QbLFTGg-ra5Mo^bzjHHg4!h_bm_h(-g$!CgyA6L^ zwNlwA&YQk@5oKXvvmCo4tDd=B7|FZ&(Bg1gobse&RZ`o9tZSx9j6F5~rG=*k^Xpe= zhEFQh+DLJ;Zpt8+9>gG}HNG71%}M+}^IYx{IXhOVLf`l&3-_5V^=^T5!PL3mP7moT zQ=GfsvQwXU_LqA}*I{Z?WzOGMuwD_D$Ig8#XntxD?)-TL5-YwJ^x&FWDiubh{KRRH z=mQnTljHbbia^Gd|I`q%PuobF5kgf~hqqv|#VYyt1fOm6*_#%p@FJc7qRwckx1|9m z{Dgi|r#p1eYl!OpJu1HRFpNb4tR=~jljFc7*@5_?z%p)|v6wYQ(I9f`n%|wkT|oo= zEYLf|qA9MmR_BeI%Qb)Wq8vQA@6Aaq_e9J(ZZ8LE=>Kh(DoA7gtzTt(MKq{fx(6EY z<4o}%O5&fa{4<(!nu9p;npXzZo@VZ!Yc!~|P2muTcb>qsi+fo~qU%)cct#BBLnF~? zV<9AkkIX5$&%i92Zj0xCrZ~P};KMKOI~Kp3PvsMbuNEh!?h@}s9s_sK|5E_7H-VU* zdP7B>P&2~%tEV>yUN&aWxgD_A9BPCXQK&rSkqadK&|EFE{qe5rRsil(nY(Og9-Bt% zsN^2m+QQ%Fbs@A0?p$A@9(fkQ4yB~#p_ucu%%l*S?`U-Bm*-1+znQ~FJuZ9vz$4>C&Kse%)5n$fj4sQ$c% z#%4ZXg;k`fYSO=#T`x%E1pmrqA13M~-o&Hc$1Y%i{SSp~=ngYW)aBiBj2)-PYwhGK zA!(`)8n5VY0x3-1HW$HA5ALE=jQ5Huoj3MHwh6Q|W`&kCxSX{Tu!L3|xYJf7L|F*0 z50^)}LMmI%Hh{u$TNH{20%FAtJi;FwrBUQ6Yd4vz3WwvLNXVhS>Uy{+;cmTIG}m(M z8^mx5?owuRn19q)g zKf2C(DP1Aw%1%E)M~wkjhhvVbXZ3<6_K1!?V7 zE2={LYe^@XV$_%7?ZNSyBXra(!?44se<%ii!mp7VpCAdUr74n^dI!6kCKJ!WZU#Qb zY~S={%zD(ww16(WYOZavfmR<|Oba7|4Cj+R~;$Ct*{sNXE8~)~FVoS&qqqVjC z!JnFv2HL-U#}5(NYI4o4ocXY*NHUbp_;>q$^_me?Wi!ePL65YU`ZI^)aMkQDJV#SI zL?k(n+e_b&NF1OvA{-)&inoggP}b{Q_=oZwfOWI^cGOD>>HpB(itC*f`C6EL# z;KwP%06@%r@-P>W1MWTCaG?`0W2+X5jfUdFzyYJ){#D#nvLT{W!tZJn4c zQSQ&o)1&pq(AADC8swghrYWKm0g1uUZUR>qm`f{%Z-;>VmPtixpaI9>61gOg1hrgO zqKVM;E(vCr10XbDBIaNL?m^T`63W{Pw5%E@saY`F@Z|j~B*d6S+s*|61(WI5WRJlZ z*635%bKi;@%p5XqLV~L0Q#wIoxg@beTeXDSTkJ}NSzcgUi!J?eD>=%iJHsnd8^$ei zE?Zu{1)T zR--$Hzz@psib{%f+u;T_E^s^EFTzOy(jwzGsE0%u6pB?pH!JtK=Wb#wa8-H-wvR=X zI_BV2gRW^#);e25wzN*ZHKd7Ku5tvT@0<9bJl`e*)az=eTB5LUkg~W!t1uCA*@4#N z{-V>Na6z)z2hE%eGUg@mt#?+60{CgMKC2Ns((zmw$H=7*84zULOE@UtyR2chMPMJy zsZCX~wn3t3H&zHXy?tE8207Y$%=u)>dhj7Kd05V6PZanog`XHvfBwMwRPUFl>T;-h zXuG(UDM1{HoF<_^gzx%!bbhpOQWYtaXmkc)-HXBHX8$D4AW@pRx2ZZoojsr3pwC3q zT;R4tIZ5P2OZSg>BJQD{X34UBe zzVio;FCk;hLhq4)->kazOT5r{^w%$^YPr9uy*3BOhlm9a35TqiqW1M?BmH3{b9*Jv zj${fZp~Y{Y_@E_V4^qwlh0iEumr3y#6P!Xzu8&)IBXDMz72IsL@^ajxU-zzZ@Y=kS zJpdPXVe>)RKhmG|4|k7$QpW$G>MWz$?3!>*9d~zkDNx)B6ev*KB~aWgxVOb6IKiQ~ zOMoE3S{#DAQzW<-x1PM;Ie!jeg#c@XWbJu2vuEbM?pt4U5BxX3&Xz(VElz28>|&p2 zc(A&xbwI0_PGV-3S+51P%o?ls%xjyk>YonXCH30u`D}N)Y#pHFKLftI9yTFJ{T=SX zW>E+ICeM76sXz6Hw4+*%QUe0_g?F*Ylb5*JDopKCWtxwPm0vu%I5zD7Pegle^Z4n1 zT)`6(Ka}an6rAOwZ&vb{4=dq->&n1en1koTBF1nbk zVpq3r9fCtFfqv(M0txZ7s)$5fER9n2S#6a`xm9{*xV}T_x0W`QILeplcGocr@3Y79**nr&35B+ zFYE7rW)FsxahE-8wK-VVbMuNZ_6Lb?{kd|hEkNqoZLy?}Cb#iypq6;s z|4@q8m7~3BJrGphN`-w=iW?prz)D{1zE8_4L4wRN({nYopBF86<_9ygeDdE+$qXAbCQVD56#_Lw1Y&PJi6PR`#v|W z`&yi;Q?(r&qXj&sk= zc=Q|Uw1@#&t62zW)9!~2A+y_SDy2tRO&0!0+WMz0XGE))Df*;UUX8p~RsJ@ewR*c} zD4(tjnZFt4>dy!~Ba9d3%I}4*(+(SM?1DtueM229fgElk!`|#Hb+Z4lVAEB0W(Wv~ z{h!+Zp|rTJKiqR^+!%7h{XZib(KxHe{rYoQZ#4YWmPXT_s|CLs@!#nq2ir=9ymctN zTIZmeMz_TfF|Z6*ONPS!z@oX>WM4O3b=<@HY;EfYryt8GpEx|+5QHmI7}bmb*uzHq5~2vEOxuce!X7C}48xh>!5>#&p7V0p5eGh!wo*dFn|bs$TZ z?t#1iWJKccL%RYlOv|R|PVa|-ZM#YrDsBaCdBsgYD5Yh!-XC{_GoLqId}bqN?-=65 zCe)sfpUEd3l70;n11eTFPPG?F=FW+60mTDtzc@j-+`JkI$qY2pm3&O!9$DqGMkOkD z3-=ohm{5`Qj%B+PO~82tt1g5;ZyHQVi`Z3w+O``o^j}T}f3&x;Q8JaAqvHLXu;9fa zL%C;rg+1_zyW#3J4(q%hFD|kQ6^#S(i2k0%?Z)419C9#0parlL)%&4EFjCO&agkav zQJ(D7e{~7)xO;*B+r-OOwBhL`iSvSSQ;K}#L0c(@&2@R%g?AzKYMi^q_A9Lz9iwj8 z%~*8bjlM}m+tR#TPI#l2yXIIPuV1idU3EF6WDhst%`Dou(T^EsCwo4S5R{6bLxIY5A z5tIa^w^9&i=N25@et+%v55>x1d9(t#KMP$L?6!r)9ql4Vz_A#5fDAozc)tu#H=IJ< zNJwDEJYI%6Z2C@Jj8WMo_qh>cUuSgYEqD`R`j3z2?I*(gRBOzhR(CgQyWNg?pOmHiO1BDHqJdOz z);*y0CpLOoDm~H{3hyImkxl0qiRZP`@r~1)0Ws~9{iBu9wk=02f@BI=!}*l00>}!c zD|$ImKK}s}wZD6xqkor=jx>*k@`R|+N>9rE$w0A|p*XJ1!lRN|pkLU__O>o(UrRKa z#)GZYvguTYIXrvS>--(=VxP&zO5w$<+t@w=@=LJxx@DdEaHw+}G2t)8-qle0MXg~H z>$W2$jHKbMjNN>0H5C{dQQKB&=2;LwQq=`f0B8%LuBKW zL}I@X?})qx-MTs>CXFWtN%5>Oi%}PaD3y8@wO;$RMA&#^_d$=aUTry zEjgQ`_IC7$D<_+??O_EZ8IZ(vpt~PSpeL)A&p|<5pwYK0n-~+z!Dz}SBSGr+R%sRE z#B+QubIs0@DSgGd8~)$}%lBYjN47|TrQ_7YV*35TSWSC#EVJ9Zjb^q=MVO#??|s%s zfvso?HPr;AWtVj({|?57<>Vvhk3jZP^P+^NzDiAlK=M`F)%RxMr;47TiL(w~-LDJ? z)y|L*pTmWY$QGV-SbaKxKAEV)CzJfG;huU-7Uk-7W65%UFN>Qb zKJ>(ebvE&t&`a$V6h6eNT;0(u1($N#tlQ5c94@x6Y}kTI^ZyE%(Cyehw@9vEnO>;> zcgh6LI*gn6-9ZiSq*YED4S@y0%0vH=?&Ofr$b#+Na!Xwat9uFEncwH&1gNXg+Qfd5 z9f4xL$0R?=!y$*Z(ky+Fs;Xy~v@|5Ma`r{SqZ$udPMI6Z&8 zjp0Jr{VdyGtR0MVsPlkY!{BycbYip^vOkc&P5m)~MVz;GvpvAYg~Vdor;=RDCyO+K zzyfzOz>rcW8r3b1u9B;t6t1Ic9Ur;zxFfL`C;*Vf<2qUYCe3|eP;vh$DKdTEaPDD# zb4LG7afo_cOuZ_@X5|R$0rkr8SVIR+3jnNkVD_Z+obIY;87smmP)yYxqR9a!msY*x3~^pEH|(Hp z{_^ha=iH$jJgLNJX#jU$W=>A83WhDmvv@?MWM-1{k5~5r(zlBz(}_(k#ga~w z&g>xLl!XUfE*(uPSn36MgwlqABdO<4d5O1h{i{|8Gd7!YVKVNUH1k_OE;K6EXd6~BF73rm|qFX;uT$p#m@m3P`A~p^*nZlAp!=u0;iDe>I`n}=rY9ZB@a5}{{MKWGmn|}@bE23e!ljyUr2TJwXmpQ*E%e`sPsg?L&e)C@`7nx6+ z79`E}X&{rcGS*PT3L%B-o&N~h1<`Cnh|fClib3Hwvi9>=0(OSB#rLKw@e0Dn8;RA& zx@Eba3be9obFbnONvaS1`e;^u1LAE$qkKq*KfJv0)e%lYpZJH8qU9H-yWqj#0L!ej zn))=NQ=)ca1j|;L=vZ{~hb^56$_SKT6w-5Y8B5%%Nn{>7E=W}ytp7xJZ*(f#8f>wk znC4m<@bbhf%|`0^T6*Z}c_brw9Z}`cuN`_L=yYbAA&Ey6@`=5q0w?>1R3Ey9j}`?f zud9qgh_8h&$tUX^__ZMVAk^}wAP-(F! zPe_5|VDR=ErjTLI#X6BJ`MKDE7ATaN62Ok%a)z6jMJm$vjt|S4S(FGE_{k_5+xF5O zE92Y_Ed}0!JFz2i`p-E_zC_6$*uUVDI9X~`wVp0;oz#!NiZt|^?ku*49C<|KR+=)t zG7zjTUpCAt1V;+X%k{tV+NvCl%SLyKCewE1W8i&Z1t{U`%BM(PEHv7OI8NHiQ1z~* z;T)?QQ@pXOd}vBe&1NF$%j}{2)IfL6nn=?&aJ~HzqdpsTZRZW+*VX+uQxvPXC?7xU zg-xQJh3=)PcudASY}o7{KRLyiFAyGMmr959@K!`Fg{WhE648HB1g`#+6Fa5-nIOMo zu;1jstlP9z_;L^kkpd{Xe{$iW=}?rsrcpa8>)Z;+TR=R~Lbp$Rx?tBwWa>a0$z6!V5_V-WC%1Cnr{cOE(NxHD1uCf7%$;yFt)hX^0 zr2iPi=Efygbn3P&+Uui04}IS$D)?AC*o5DaYo$4K|7F@{RIBj*vbA|uGPgAS`f|mT z&_VjyWZycKB+a9UT945t)NH*{(0SGO9pS1%bd9h=UJ~QT;}rW}vID18=ZLbuILv?E zYHoD-kKoEC=vSqrSKek98Tg^{%kC*_kqU{~=Yns}Us?CYrX=TAG!+&{XD9d_ zmpxNg7Yo5tAs)z{E$b7R|NAj3o5q}IJU+GW6&irU`7bTB><{fV69wH5ev zGchumq=%OM!8>cl*E{@TK3Sm`r!S&uPCdvF{cL5>bex~z=+fByURYD)D_2+4rnhHg0gYlV#y z{%O2UEgJ>&D%0EOn59yPDt4V>$1)jQZ>^vf$ylv)mF&Eza-XTr7RM!;x=MoYpYpe& z;f^O_%frPUCEqwvKo+$RNy(Xi$PBA+#g=s-zAkMSsRKFac1h#l3O()QEj|u?Yf|p!9o>j8 zRCPDz0korbwteNo@}$Zdkb~U%k=1SJ2}%ZeQQh3Tb)aVkC2ULv-W@~7Xf0m%rI`X= z#ky-pTRp6d87jE#=d+*(3iG5SP*ZPlG+KyB&pgxp(;U`O`JZ*|AurOp_PmmScap?e zL)W8hTbQ#Nzh zTQ)ca>NLciEGPuy9EslP+JihyFupoQcvVs#a~e#I^ux|h<%O^T6N$h1j*h4=8awu# z{Fj!f{-^G|##GnR#D8S<}J5;D-1+8zDItqY3?9h7n+W7EtD zNe3WTx%8D&5`JAppJI1jPJ?q*89O%I)*0RHOh$bYCL3BeyqAn8UM*-_^+|20E6&<@ z{Mmk$>|gTj#r9<3wGLM6+*34qS6f9++*=pVSM!?ShZpnOzM*OH|^!Q?Ux^ef)UwoJTW&X;eJOk36%S?UNBiVE|+S(5WuB?e&WHQ-#$OD+hG~ z4x|aB@DO^su z${oa1>eoy)qYcE}K9sP*xwrB-7V_9)>G@Eqi_u*O5G*tqst|k!c+{!b@Qw9Nf38aX zt{WE|Nk1%2CBoH7T4YpYW;JScGow?hV3t&WR9Th5m%8-sGYvhEY8^RKnoIBlIaQ** z6yDA}5}&0UZ}Mhb)#Uwl0ild6d4^ZAAeSfT`mbWa|X-mR7m-nOE$dl zn4C?Fnzi92|JypLm-5O)VPIphW?}aL)dqmpIFz?=5Oqwlii(7UW(MSzRcq|V-Y;on zfYgR`K~iEmkv0{nrrC?9G-|(U z-EH*nxnJDvEfOwz+qd2c-mT7lVBsCla<947mfo>~_xluo7D{4@+PE}0n&y6qO5oDH zZ$$Hgj$Qpc4FPfkym{3Jn??-FPloq17qb|XeY|yt-xXvdKdNxP_cW&yq9^Zf1dX_< zvFmvWHiJ#vjW*X1^S}n~d6=dKH``=V=2jt96B80^&*2M5>#1^g;$1XziHxGaV`H8~ zET`3Sc^KY#8df6&XR39MNA@{}7s;k;TEa@&3PT_ABK(@E8D2PX5ZSyU(r^e>dKbF( zMv^R%QUQnPL>!$`EFuw`;OEsd0Yw@Gcp&$VAzfo+&+^X^-2JwcxmUK)O=Y!IMSd)O z7GI54}c^aAwCPe@{Clv&Vah-V03$Y5)_7yGY4w;{aUrElO!ZQ=`Ex8r)u*r zPE*88a`yj+A|oDK75ix=B_%s>>Dw$H3JgoL|K$=MWj;B$UuJn+^)L296xG&v&8_wD zS~%;OIOS>!nl-5Y9LLeqTC7(J@TBl&M&4wN{$soBWaHI`6W`8YM;-gqW=RelXO5Bf zmk?(%GwAEt^^Yb{%Gfr88gN@t-FzQUKR}uNKHPBySnL0(Fb6{lHHB}>1a{#S+{c4nQTX{`u@!mAQnf*fo?+0ITQ1 z94h>O89^1aciu6?&-j;UyLrh7KVTW5FA&dFR~x+C&xG|U?y zhks}N_&zs_*Tw)U?Tqm3xm3yDb>#Eu4+l)zK7xFv8;A(+Y?XpO4%%dlC&lJRkg=Xu z2Q40o>AE#|w$oz@A8RKlJ!E8ra~Bq^Y`lMqMY%zW&YImY@|2xtx4UWh_h9BGNJ@>w zdQ>SqS>j^E3m?f|#>}Uv3N$b)UbD%&3`9>An$A%uMUK*r%TlOCyF@Mab$dE5r|%hC zae~Z@@~Io@(sL5uicay~2%El&5tlZlFwCYTeh0INru3!)jQF?399c{3g7(5SpM>Ql zt>MC;B1Z>R^Q<`C(MX7X@P#-qMhewSENZXM^l_THkB5537Ji0w1*4vdy_f=G%gnsykvZ$r? zuBRX`xmD-B3o-tz7StCJE5-8j8?up+9(1VkPJxtKJ}5R zLb3|mFE{0iiYgo&qtvh9ST#;YbTlpJZBtM;|DJEwVM;deZg|vnY}W6l8dy5==?$y? zygMx+y?t}iqI30Kaf`lyj^J%n(BhGsDJHX1Y~`6ks} zztTeuf?v0`iYC+;VqRfPBGz$oCAD<%a`bIyYhz3 zE3C6HIhPdkhyNGvfSF8$IqaIpM=H=Ciw*K224Bvge#N6aGi7sL;X;NIYP1Ba!U&C3 z9Tk}S!d-t|wga{i0WTF+CuA$iGWH%c|F9ARGQ=f+?HjG~@!mgCCvc0kyRk&!%jUAJ zCC~uVw>!C(j81tcIu|JxA5;RW5+-#pza+HN;%qg_9w(0xywl)cg z^jG2iDjOV>dx*>QSui$_w~eF&6kpLUwS2SF`G?}P2KgYWXglv)*xYLo%IlBla97O{Ar!h1=)yKR|r0&dsVgd4olxb*nv!!cc$XnBpmHy zyzS4`#w%5J@z;0n8X_F)146Bm%u7A9jRl`(-J>``rtd)TsqP^$>jC&{a(l>?s0(Vj zK4)LQgb^vd+m_;FO5af$ijo zmUm=BopzDK*YAnqV}n_qyIVU8QMipmOe?|zXZQABZ$IlwXtioz(dY?2x%;{HU^Kk; zc9pk1>~n~LB(V9MbhfIpIm?jNJDG{38)qd~X)#BCnkVK7^NH*x`K4ui$T8*c#@B#g zmC1&F?m*weLe68P8jP3jb?Xefe;UJ9BDO^3D$1p(RT2>%`^AHeR{O?F(^dq(9N^xI zE6dgpUrAmLBqab+j0o%XFF*>4LI#PlWjXaju%~7I8$vcwO&$~dul8&xx8@@ps4Xoby1Aoe=Cqe%Qlwm2 z{M<3_x3@;_FRBj%o|jnODt%+29`5qaR-f?Pb&~DcTl4QzZLY?VN|=X zA5fyOiz}b4ev{Z{6&%<%@8P8OsLvf6{`AL>QW&bGeTnr(je73R!OPQm^25!Q=%Zj$ zr}bPM6ER4VI?GX77K>9hf|o}&3{uH~B}rXr4nI~RF5hh)oOA+6=*fBD){sb77QIAP z+5iDY{KAW-oR=~;bAqTvTMGu5M)}gIgGCHN5vq|4`iOHLFbz+3B&i_(w&g zICRdvUGb~J=^=k5NpWILD?cEq<))VKkjF9>ul>qV*1@u5K;Fke(bvLy)xPHvzPUZu zOw_$!bOtQZNp#bxMIZH+Yo7XvYVA4;nWS5M+&moGyL}Eb} z?gf3wez_sF%@*7E**QD4%#g$iv>L3K0r?gnSjkg%@o<^RnI_**y!7QlooP4mAN;eo z`k28O1_QcjIAE}Na6@?xw$$P?c|isF(xcJ_b1*N(YLv6fYJ)4%9&YQf(l?TujhnZ$ zmp7_OT1G`X33ntVv8m%xheA_`%8+TJ0D!7x9wkSr>E+3Aw!OvErqgErYdM0-#Mp8k z@XRkc-ThahrhCD58lrMlF;>;~#qx$q^0($UMJg{|P%{BtQ=tFFhB2j6OtgdEz{;;# zEV!jp{p^R{-wTyen%?ZWg$Fz1twN>fRCgn_gi`~R0Y(Y+R=LX9e`D~!6WtZ))Cm4g z9dXQgksl?BrBwk@lv@V*W=5vaU+5@W@QqtTt(fKQ@PgW*=xkqjAY#?6HA{`sV<3TD zJ>GaKMge<|Q-71=(agfN_rZk$h=t#qM&?q?$X=lMI$~Hz!HB9=@cry!agu##4E>YY z8~dujPLt^D5f5fkb6;KzuM*4BWFZoTEsQGp+X>keDengk6SH9Bobd;Q>D=8@E0Qvv zZdy1}=zUc_Csv&3vLtmFh0MASL=F*XYh6>4HFx8e3doBk-Kn-&GoFnTBw8d?rgXqv za8UCT@DTASZ*Ym@w2`aK&lz3|I z;;HVf7N92Fg4tl@Z+M4vSjDc?>?0s!+dE2IeMsz2J*m%$^*iH#eG4GlR*f88p^}+T zyVNjU2lH2~LY3K)hlyxfrWebobp<@mr2fQKg{8rc1G2B??=XLfN!cBYwmL~6hgrlB z@vQn0iI~^hhmw~`{fGFUcUt!G+}vrV!CTqz@%r0UaS1<3TG15n@f9;%bVUU;-jEVw z>}cVi#TqX16F+LXGu1GB+Am30`$8t$=st!iUwX`G*0 z*T7`ERNr61+6|IDL_O{uP^y1wI2_kmb_lA)HziVpkx|n|SjCa2^!1V$HGP@(tlb5& z4c>kAns3~L8CF?31}+hurEkNb+k7k00?aCds~H%4*`u+Isb6oxzI7nX)Y$6Vjq@qo z?Z7W70u(6ZX~OJe%^TSD%z1gc`}rh3PYQg0yH{ZSH?{UXH@4q?do32-mx9g0fp4MY z92Uyj>t|Mghex|x3U4Y<%-R)9ZFsa}(Y2p)nv#!KC$w)#scM~(u$O(~sfy?-vOh0= z|EQODZ{*{CD}R$y!=~P<4$V(cX8Y zQQcbh^YWY^b_blPPN+}fq=6N<(SSs=u<+m6#oNAEja2pOCsVd?^EekNjsPaNDb6RN zGrOeydi1i3q%Xu^w4enw7kaB+nWT!8wvj=to!hr&fH5bfzAv9!|4Xn;=T7_Z929tw zbvod?M#ae%Omj-;-`A(6`!sq1yM8Qwk1Zel!ew7=HFuPF)e?~tE`$lz{7-Q?SMW>k z3m3?UvgyuXxBgM&9{^610~=fNW`$6@l;bc zeVp|Dtm_GFRLv_=9}~;G zyUirsUA1!vGqwz3!3&DLI$q3DusHp~nDKqmV*Fr7`fX*i%yefArz9>-tC#}oShS+h zbh%Hf`SE)MU;H%Nk2J&9x_Ko%B4a|{6S3@Hh4!G1E$eW+wHNErE1xu<-p%C5XI~vj zn0Y~usqf6TSS@zX<}X4NFGlAw{t{|@qVK9@kYtuQ4R3w27Z-Sq703Dq|1;^*-MI^0 zMUA)d;NrRZ0kUhQ!68ATN0wK|^y@5rF?I!($w2GjF7#21j{iliH7R1;4X-Uo`BL_4 zLx<g#aOYUuj!KxsNv$e@2hb-sG<-g@}XN^Wn-zV7)h{Hc*^HMeN5xGR>R$@5;ha z`l-oHP$Qf;QQZP-=@Zb$J8>52VU@4i4A?*Bn0dJ<-R;!?_i{l_bJezV4HTaUKJX!N z&;;>8p8hXJS_DMR148$pcL^i6H1WfNQ@r++tMzsjT!z)20Kp^&0UvW14@Sx5lMK&k z*%!x8KGuYGP;xH$5WTjgER7QDNi5Bd+375XdEi(mocNAyWTZzO-WzV%YI0tq*UnM0 z06KIv`Y~&ba{@4f^&S(Sp>YEQtbTPAHmyzR*r~<0E$HhaFnXAU;?>6m60S!9B??5G z60~%_s!^k!H`on<3bY@jX=t~7scq&{&<%uIQY%iNlL9?@$YIU{%2DEIyfxE-e&MwE z1a!C4C;GgL>(pu1fr7+i022PJ5YR#kJaaOkw6*`yP7xf&APVvC3^a&NFFQZRa$*jp zpF~CWbh>##hNHwecT{DcNpZ&B$-TNMZS1~J_T1W5;)7b6R>6=QnMR_YF0`3=azsz1 z%mFYk6~%SgL!72AvdBsdjgUSFcp?)u{XqL;qgvb}jNi319yW*@btiI8@<`rHm#KU~ zoyxWmAm|vQ{QCUlaf>BYk}H}eHc*L}%2`-SY4tWVrcW?iU^+Q4D3u|I_`pu?tTX<1 zc87_-cb&GgoGI(PgbtQR>+;wjAe#P*E9*~9C`;qILUYH z)4B8gr16^t67WGH7b%<)b^jKLw2O+k`$#> z8%IGtk@F@@Gv?SJ`@XYk@+vH17024}BMMN@X)Q-eQ(4)i2G{VDLoFv7%B0kZVI0m#h)w2hlfrCj-1W2(0Iughx%s-z@^jy%koDu4aO z7+uh4MGtHlK7ZwPJfJF@xU;qoH<6vs&2E&<5XF|$CQm3l^0jXS=t46Ej8XXEF#~Y$ z;Q0wNrfI`^0`5g9F^b5%Pw@A>r9MQ9U@~vRA$jjgxY8ZjX5#2=DmaR@_auvTcu^0h z?UEnNg1IMp8RvLe*xS8Re_gk|-TYxkEpke<<9Jr5=s|Px+ShNYtvf&}N|*nUPDLor z?JVS&`jIDV35Q^CcYRX~;@X9`RG2)th-(3I0#G@q@Em zBaJ3}wWONT=0l6twdizCT1mZOP7R(~+iu%xTq198GHJ;}ndM!;A1ocKK+Du~2S4vC zV272aLu(xpCnXdFdUPi+jcqDNsTejr8{BhDU&VJ6+jNcr3Q4VT#l1{Wqr5Qqf3B{& z2Bi@OF;ekA7*^bx^Qieq?1L|Rc`mKf_149sc2Cm zn~Y{?v8OSt*i-2sbq1;Jj9zJ`m&OT}*I%a7(DN~?`Ovv@J}k*Ju-w_eEb(<9)>RcmVjq z6!|@X3);cxN4ir{(}N_Jy@(1{uRDd7?Lao~M1eOIs|8@)(ymMIN|xrdMavocbDGAo zFWC$i5h+MHUj_`xE?-gneH+DP8JMM$GJWlxY}rbUF*pN$mRv>0qb_89J7DpKxqjPj z@UC2sa03jG#g5_j7&@69yUmOD?jr;kxtFd>`rjvt4DN42TXQ~IG18-=0y**~`B9UC3_BT?*BUgeZb}`2bYQ7wsM#eBz(^X@Y z<^Ix!k>g^}wUrx0`e-bMSSl2fUQFfnsUGW=nIdB(#TOrHcrl4jsiVMEv4Z(3D29p^ zjDDVBGYH6OVn1utaagFu>9jSkME!dP&1RZQbm2i99sfhw_X}&`8k!0ZH#qn0*c%~l zE@!cnT@y&{WU{i+*;E22SND+LfJ9ayk^OzW0kRGh8S$|;U$#)MTnHzD@ltV#3W>$^ zUmZh=^uAQb*e@VQ#gSfJ`VkdF!o#1pUC2dB)=3BKa&ID(?v{?c@hl6wf-&-<`vRI+ zw~Bfu2|A7NI-Wi!dc2nE5x=y{Cj!{G$g9A0T0aSKgg6GNsB@U8?ht1pZ8(-ohu_Hv zxFU_^2=g@2LbPRK4+>LbLrTs_(fr}*p}(qSkmg@HT?K|kX47U`jdc*=z0f|i>AaS` zrt)wkUaIsSekBHd2`F!19?WBC_LNm``V-d7fsaOikWYW`8i!3XKA9rCSG@Y~Ka_xc z-K-`Vfr}FkZ!VHjo_&3aKVU~vFa6X-K#QGr3(Q&XULbvh=Et9SbmG6c z95%A<9B<0k)M@3KH1C;`2J%dQO?+tYTBZ#+X|^AwesL$8E-m2N*0fVD!|^M}LyR7k zgDHuy2e3GbLn3TJ15`G~e7!LC&Pr%?V;L8275D9QVQy@QZiI?k;Uls`jYZ@W(lq3F zlzTHB)5@i{m?TC>`y|3*7d?~T7a`AST$5|?pe!SWCxy3uPWHm`5VR3xq{oz#A8R#6 z2Te$-#a?Loy5YxL6{aLWmx62U-XQM;S$;dtf3$R;Sw$7F23owTw7?Xz=NCVjh&!CDQ{&u~~G8Z0I>#heBglNZ9 z4Sbrbm-u|PG<^R+x#Pm=j1aN>a-OLh5bt~NnZPD7|8K`A38QMuE)J!7XBe^y+ut6b z__yBKOR~lc?Souzj+T{*tKpBL8qbrlFG10$nOFABN;FC9#SVx|!;ASuIT{mnw!#B` z0Exx8Dxm!vH=K+_m8dpL+xJa{_uyb4q2wujtRw4uY^jd9Q{XQUIt@Ke=}_(2j~0Q5 zv9aU7G=JuTyA=*Z>4`Jx_G{_3XKiuGkwM&5XBau_k)~*i%s{Tn{_0$=WGj=YP9Ke% z?}vLsAmQ7aU!msPXD_z#4&u9w4k>HH{u+VJb}6q6FDSq39A!@bLwOSAbe!#Wiz^a8~g*|DJkmX{tc)^bjfVJkgU?re?gr@(LXskEF z68g$(fJCn* zuOBlNo5ztWm~_?MX&H>n3VT-mGfa0g0wZgh7(Pbb_`J^MYqK4d{>0Z@M*w;5LK2Ob z3gl48fA)*8YPG~-*x%WAd&sh9W++JdH%%%~ZG za)kjZR0ec)#reD%dh0ad@~!gtvr|k0nIPnV*niuyF)dUVr&r`{QYbe^!G97o8u%u=cni%k?^+uc*`CZOLdU@8Lj( z%et|DD9k)+%w0VR&M{*{C@uz-RWNu}=mwf$)H5dcMAuBUc*rnPx|B1Re}JsHx)~lc z+5wnTrvO4+RD(ns)r*^S=q`BXf*U*N;#d*vE5at0Dds@3q0EC)xkWFd$&^~8{9M2= z;P<#!R1J1u#G$2xxnt1ONY*D{i1lbEV19#D zJO2>b^p3a?iB@a--j{yVz3_6Qw5j8}n699_a-#cpYx!4{@u`UI0&QK2$-(8r&KL@Y z3o@nZS{i~Gkp9+fQS?6);`#&Ypk8wm#=+r|4)cNMVB~4|f3$~YE^U64W~e>EbHNYL z_Dk=#IRvH!wU&)NPW8O?&3a-LH$r9}V|M4#7Nt?pt7y56P2dMQ)-8H=@>kw4bm54? z=u}F_@8qhea{$TAcPU7Kg+av&5I`4MLf5US1AP7Otkmae+?>=*R67!%e)kGlm%(8; ztW8$q&SduX#OePV8j#jGp8F4FQu*Xgttcq(ZzfmhW8~wSKukZxXO4%w56QK0AOV@5 zSD6S@_CtMswSP6?_33UmFkyrDN%&-tL?=2tV*PJi{9^gQ|NH*7WY&ae;h;O~g-bcd zd|GLC5>zGUNS4E_a@|WQMof%hz!H(U-p<5)EV!7(E~L9a{Oi9U<+7h(34_{=Kin}S zoHTy91$-#60~4EZOR6}y76)Vm-i_!hnEwO&v99d`XkA_LSj)qnzgviETFiQ z5@7^@mUzyABS`pg!M895o*w-BHfbti(Zt-!bP+Q;t$xJQfZ^KryLkwa(odp7`~43L zq|({ZTt*rgNZb!XRp5uIaoY!&hMXV`5AlAn+LWnxp4yUr2J6MU@kyG{TjU=7O>S^c0rKNJjp)MupVKpip`~>`h={dQL=cfrL7Qb{h%;Vrd;Evb>gbIZY%Tju8(Rn>WX^ zr|`;kZ@rG{Lcbx^Tm`NZ!+$88;!vL-VJVzYpKv-3CG2;@#Mxb)>KDPYaP1vCR88Ie zIN90f*sH?Zo*ku$^WCmX&4*5{eVWq89j`fRDzw``Ty3|0I84szvAn> zX$h*!zo;P8i%^a2bv-U!L~Ss44AOWn*=YT~zxDS+CySUyiueA1l_eRL+ZWif^^Pv| z!qlebp@}N7{i29LyJ&UYd+vFjM2L7imZ-Dna`uYK<Y*HpQ*h?PFsOn#nN=)vGg;mlF>2Ug44()JG} z-#%mzpl8=!lMj%ak^7ZTi%~|5MCG*5%CKYwRSWFMP`jKyOkxk>Ldi7}t39JA^@E75 zs76wB+pml|^<{IE>H*M@{(-3ee<+j%8W;A_^WZKddRbf{wYHbo-IpR7H zoLJMtCx&&4+4`|^O?Ud#Ai<|R(ar52%HJGcXO1%8gXWT(6Cbt~xZ~Rwk z{fSOBJB|>i3Os%i{!^cTe1tB;dU2^fi)k^bp4$LBJU6MQ5S?$l58hpqDeQAU5nr_;SLX3ggp^gf8bJ{6@-2dfxr?VOS0NmmnQleyA4>VT zl9@kWk=S}dhiTo=E}{Upbx_C8-%AWOiFYv|To}jU(dSbdBy_Gt5NylE5m%Cp)}vk^aVF(0`5ZsaPfdhVXC30zd9-&P zfBvBC$lQ?FNQ;66(-uTC5nQZu&^wK>_DyQKHL4N|uU%FTTe59)&yBl2eO$NeM4OW+ zK7YM4fM><^59Q#@7sJ$$uc@FxKskM$;$u3LQ))SCkUkt|Ws+7AtmglDb2F7f7aMhdYMEn~0AP*KjP@U_J zo{R6tvf)bcOHx?Fw=Dy2_!aOs(aEa(BU_F0z`NlBi|AEIZ=<<)Zt`T&CPKetwvL6u ze`GDCVaYE(LH-naID+r@K}Ea+y-V0J*%o+05yiAhNIyB-)&fdjj{+B zr!j29Ew4!Y4=)8ycn zMi|81eKBU{eK2n0)?jx=gJD2~VjNd6TvO$UU6Y7uMLoLzb{QiuhL{Tke^6lt7Kp0V zZO%VJDuCvN+v?HWF?3p_wpyRys{As_N)1w_rpzG1A zfh!(Sy#3gG5oF*i#YJ#2A;t45S?%{wL`X8uJ7#L^FXEKxJ|egxcp`XG-0gX3&#JN) zKt`aoC=d`^?e(;;l`LYSYRFf7)RZ_PTQ)Mj5We2FT#kJDb@RD@C?K#SVh|C`!bL`J z6(7s39Bls&MV8UjY$|F{f#FVsk575v_#ivI?2bsd)|t>oYcqaW#fVq%)Wv=T0sjTo zv3ck?Z&ry=@nU*Ma|<``*J77o9kJ$_1{!Tp890|--H5maM#wW1JYZ)hHXl{sn-@Gp zXKOS!Ey<*AC)DrQmJUp6=vkSt$EFn@=%pB+V)8XG@bQEDq#^2a-5Zm`KaLDISuUP- zp8HI1`&7|}m{rLb=Y(#kZ)LY(i{|T2$oHXVHkD@9SN(iZ%FNEuQUG2%%sdilj!6L? zF}c~wCxJ`380b<`f7U3qDa+`TJY=jV5%GHo5#!vzw;b7B9;{;P&n(kI(il>ax1=#; z5k3*l?S_>9J>WxrqbULu0iIc<{TP47V{uZY^Q+z6>)D1h@m#YI-cT_uc%)AoJ^56P zbr&Th8Tr>JV!IG2ebJWdNQB2f7e&pOk~(VSd#pSV6k_Z}`-^S$3a%z>Z8xeNX~fpY z$coB)KmW)ZdY(i^b9@N<`8D6bZ{&kif}}WPlv@H3e}?BD$>h{@gyHKx8hV)rR!E(w zYYY|5t5T**KKO5V;*bp!dbV!oVfh~?h+IdQ+zo>wIx!!(ex_3|e>i7$QZqTcEXHm% zxt5+>&aC>1$yco4(2cndO;B2egP$!0{`?~tV$i_YPFYTwoD`?@1ggw#=^S8YVYw60 zq%=G}TFkKeGsN>&(Fgh0z$TdWXI55u_sty0@NNK)8Nfws5ry=842)kOe1+`)eKP#- zzKZUkm}zTo;_gc7%@u^`K}_*PRJm^dgW;+YS9UpN-nMpuWqkb4?S$Pw4GCq3Q4}vE4`?5>eP2^PR6$6Dn z#E!`nGqY>RL>5@>w|Wg=<~GXR*9bgE)CvfW2F!*ZlPs2{3 zJxD5%GJ4bj}3^USM?}l)>quo>kg);@V~xU z1a6nKq1EhsZb*sK5&?3~LsC2Y4Se{NeCY>Q)JOa7yX(5d5WK9394Dwf9t234l z=&^BXV4%5W^pVl&)Kr0$jve%*1F~@wsBFHz$6QmzF8O)7sr!4XIsBI5#3l$6bYk#n zM0{FTj+(frxLjA!Pa{D)XF7`Frn^6p%5z~yh~fQ~H_(%-fJ%{95t%yNaD|;g1n=`H zG9}2w%N^y6!(QJI3HR-RygogvopyAF4mUm4(_b-N$PGCwoWf!t{+n4*Wa|Y(C%}zA zh+a6&%jPLa(*G9YC$In*KpDTiJ&5=`#6#1-+FVyQ6deHueoeG&dWu_s&zv6BL&bcM z^~yi|ah&IQ%=h~^Ua-E~wh-Xt;^>8O>k$Iy-e+Tv=wS#AfXK8oQL#+l<^l=2se_+!ToLyycO`%O84vVe<@~^AF4U5oURr-4t%!*j&EIOg zw6qi1sar^ul`Dj^(Ihg$^2%Ux7PzS--VNGa<~vpk7sO|Vi;MU@T`%J_E-?%&i&ti^ zWt{SQ#P+|oYL-MQdQCOv)vvd4GKqMtb(SHzKK^b4mLZT=M!831C@8Q)9j2Z?ZBL3T zeE<&BTrQbmi+?*7u;4dl6zISw`!SYmvMiouj$j)abp4Yd5QXKg!Lkp>5B^%(%;CR= zM6iAnx3g_-44*YX@~DhxS@jn`z)fV31OMe{DkPu~8=00NOsy>}xF;psT8_LS-eOPt zLiWLh{JbstQ2aLfm&(bo*Ae+d^gBTd-j~Gp18p2if1mW3Dv%h5;Ch#^V>IlIBEEvKc zHpB~@#i~C{_NZ7bK47vZbcGvxyZ&xrJw4mq#ktmV4*$VC_0OqGu@cE`*H;S_hMOod zsgpAL&@MmgZ*CIZbbExnP18_e@iSO+ttVHSBTKPgp<@>N*yVO&8PJgArkAdog&OfAz*J*)*SrZ`BVE_X(I+4S`KqXTEN!D=u0&i;~JV~S+!wvkWl zCuc418)ePBvWZc+72k*x+pfua2>$ILJ_WFEHBFyFrFqXjLTvcMrFkS+;ox6~g3ft9 zWvtIY1S)^N-v;PM@jl^zXBMW$`MmBoj_8>&xl`H%{mrGcS) zOGra6F-#UqL=6WzHC~SdK%~eGaDf@@suSY%SVAg}yzXL2 zV)wkME?yk+j`dWY4~1|Wr}3{xl_T2cGL$%|Dn3q2=W>;>K5HYCLXjH8)OyReL670G zt2kqCU&%|+eC**5p{k}XiyI=))IP(U1_xBNbw=kS38JFF>_*GS|092kCdbBT_gn0) zQc4I((;c!jb%un?ls&Mf&@=V_DsKfGls}6o3LZf+$H+T2Q$q%iDkY$Ivg7weDR+8Y z!O+pVydHAv0Z&nQ3U5S>=$Y?K4)$Gbjq2j%zb^|o`ZEPzx4?zf^eTb*XiatJLV~{! z7|3Z+W3tsWp$Eo5%3V6(o6c?Y~ z%$Jpkp=CnwB_tOQ7kQ_bum^o6%~b)$*CjJx&_UB7`tyScHV3kvpL;>L?!r{CR>%#~ z>%)ted9sX-W3t|IM_YEP&+-(m-O|g3T{U1JRyLt5CQM$QiLR$N`+6$#k=1OziZRY?mXFipgG7dBCGPJJe#5N&oz*_{2TnjnS1E*Rxz4tJlrq zuqw#S(9L0G+juyW(>?9UAcK6Sn=hwR$eh8R=$6C{f7>qA zH5b*ABe-vmq&V;x=mh}HOL~3cKa9nr(pA3vWuN+G8pjv$q3=+v>g-d46L9M+TkM_W zo^R2~Uh?wtR4s;Ev)-%JQpPh=QB-R3TPh3qCiotK$09m<3n_&Erp2?5+Sz>eeLSl6 zs5+)(#$O8q8GS@^nS1h5SW|u1Vrj)w6I%^N1*Y7oO%uPrfSe=y&Xl7s^%gL)NfdEE z!-0+d&>w-wI!Z(fS?2ZAm8?e`)}XLd_YLDECXh?~O_sM>O(|%qg zq}S~|?Ik~8>*H_~^y;J1H)O{wNWT(lKhVeev-oC zM;J^uEu8pg?>4}A%5E->WG^uL4CAV?T{no%+mPHmfY{zg9(6WT%I< zH~MXUwDeHxC}CDIuO090IK{lQFE!03rbs?9TDAWys&)`>Z6+b{n+*G9S-E!f6YG?B z1s-t(OGrCMxwzFs=#7ei)s17Z@2X2`QQ1@V@^BFD&>Rrn()w|CKWv_JsqjeZLwcUG zRrOwe4NLg#lY&k2Q&x~+Z?=m$=GX2E>Q@a9-Zna;*Q=;L@5{>y(9&ydqFd^+XEjLl z429__wDo!^tWVag{=Cv$MsI2>I@q)v#z1TeH%DCTB=Mi6hT0MjpD7gSDWb!|GZqu3 zPd<}T8F~_nrUfQC8zv=OTj)f0W11ciC$ZdSIabyj|CXINC=$cr?`xUb8yUcA}^g4s5aT#kZ`zD{RC z2j^AOmE(2J+POBQQbyU?*~Nn!RP~p^(*<4^gNDVmm{67$IcN1hma}XtYJYIQshE5g zKETWCBK&~B)JwRTAjOwHbAuPCFKpGTD+%v$3t^+J98ernC>;vR{r9G?kDJ-JYBo(@ zfLrx0Vq47m7iM1RTBjYTA*C+>L~-izjP^<_XETU2l3Y2&QjB#O(>Q!^Q?9&v*+mb# zPBQ5|bb?-%3~s{gY%Ro zmr-oad?4&2dh9(xN)Q9d-tbPZSvzLnV&}5 z7o%~hl>IxyUS=p^dyc@hF3rHXuOB|II@^u_8PM>KYn}4AnFOnZaISFKWN4D$J0`)6yd= z-P%hN?53{P5^3lCau1;GV+?@y?~4U!xmku z5ZSE()cnXXIu+ADeIPD&8BHr>e&KO3cZQzlZ@bwxbn=`9{xbKOFO?Lu@U&>alkr@n zyru-WCw5ljoJ3SZL7UYHbM!YqlK&Kqw9}6Td4gIYTxxa&fhP{KaR&c|(`O zZgJlBIA_*GXDX+s0*2-00ee1`KXfv^fNS)6_^fiMG<(D>OzElMd(NRVP;ohu8 zD%{RBgr^m)?M1I=a!I71Nx_bU|NAPU(2Pxda_s2BkH{aw*4P(Lg_pnAb{4N>ENNJk zBj2gp)?FC!c1@uJk-Nj!tH4W|z^kg|P=8 zxsOHn1QoTcf;f#p(nUlpee#h+;%9oI&V$vGnb%!N1ChZN$0TV;yWFs!M#UgaoviTf zEd5AIHN=nbWqxFE#O_3frigLTl-;-D3zo6V44y5*QgM8sBF1|*3E1z!q)&ejU*Nia zcfB-`?;3W=d?kMlDqDPa&YS;of7jlgmDXMB?2`F%YrD-dEkyWP#?LFm5vZ0^-Mw-x zbK=VvanBA#zcs&{YD3ZrFL;0$7e;g`p=F*m2siWgEQ53rU+?6geqLdpaZl)t&RC9v zLE@H+s|GIxHB>9p7K2|5NXKjMRs=|W?U?AhIIB#ZlbWpOFk?O8I7z24nGnLHqt zW96r-tn~UxcY+R13zmIY*x{oOPh0u+z9Y8JEb{F+)$A;~->fNHIn;16CYB?opa6O( z*zU;UmmV}l0qf-SqcV$INs->E^w5>ZL@LyBvf2b}5|?!IQc&KaoG;Z-ifWXtlw9PiU&+$oOQbZ;6)(tpe*HH>8rohsg$x-3uuK zyu+5zFS3!2I_iOdZ|l+lpELG)0%^1I)Y!zjOBd>t)=rZ^Bb%5B?1_uURQZL{kE z-4DwdJcakIKKkFQ@9U#qYqigeGdO7N&6c%jnFP32Tl>6J`S8@tm)5`;9M1+4x-OSOC)g zHG@}D_0mlXZJONJ<6StO%oaHZvFC^HT2*;R%$shcNai|7O5a& z0KED|G$)XIanTeJ=Vza#m*7ri%JpQvA_S)V(ZCWNA9fH>&fT?c=lhUX+z}o}RlAOy zvPlniHv@F4wC1_O?DgYrpS^JGtiZ@B{577oW!RoI>;cwAeJZ2ekzo`@Q=quawRrVc z8zw08w^*AK53$t4|H<@xl+OP#1T+;8Yr(e5GjQAFrInASjHKk*>%Sig+7UkKbynly6?$Y>{QAo)<+kl~ zRFdBiEzr1D1=i*quKSg}*JRm==*r!DX7T8YfZ}{!HWVY_{AZw`|jL|lI-PNP0FP5S;>QLE7e=g zsd_HjKBTay(PyFT>sWHm2(8QS(I7^w3L+1&`HgmYv9#D@qO{S1L%1Cxlmn&L6DT8l~XObH|#38FTGRQ0WO1_ql96k9gFS_ zhq9mIeA;F?G$wEAo%ZAp&^@)HD~>Z8X6cb;xkForEy2}w;ap!l{DLEdh4k&O7k+=J z%9oEHICnnF-D*~0nJ-p7ReRJX*}lk_hjqOWKa81gPoB?esGm(QbDsc^`YI!By zWH2X_2A>OyyA?*~7nlE0vDPs?ATW~dTvUL4;Zi*pd$5Tj#*I3g5naIo0wQ07&Ymj| zr>29cU5u5_=|$Hr#_l5AbF@nhKtHeiZOT=)ICjL2p3Tp11XQ#?p)`dB=sqKp<$}-w2#nGFcFN65W z`*TsEf)+?YHjY|umgg{dLM$wC zcj?rpaMcT0*3i4xx_)i4V0~XlyMeuYqq&cm%@=%^2V%T%{F z_V`={`Ew83^1rS1k1zcY*)5p^9~IMaLwytx7bCVJm>!R0C0)R3SCzG`lQePBcg@{C zA8$Upf4>Yl6jA1sc&?QmOnJE3C~otedqc-7!7SbFD>3Sumf@!H+m9)*DW2eS{0L3} zUNs^5@*}c;pp#?$?{L=nn`>s5+0gLo<5pgJvFQr&fC*zUlvztPa6z(dh%~RW$NIhP z^}i2Fd0g4cBsgnoAnnd%#w+v&5AANJa;1h4g6L6BXYr`W;_~7u6TI0*5VsQVh=x^3 zx!Tr;rNv8zV)v$YY3PrSJTTtok(noXm`{MsY%6WB5{NM+*j;U%9xU+2frEPEPMzLq zUlZId+~JJHC4T*Sjm$=1{hU9*eXp5w=aq{coYh6slUw!n$;Gq}qav7x5rYFptdsOO zPCE1KGbunB@{@^drDXXFZu#i}ek6^F!eFX+lS6cW;ZVLM{HbJP?%bYtY3KX^BAVB?vvAG#T5;fhWb3wNGgphH+3ADVJ4s=8 z@L#k$uLtkqmpUa@*iBbP>}WY0O*2w;9SJ_4MEq`AQ!TAhlZuz-r_VD}Pjd7t%t{-< zCbfp&=}RYHuW~D&Yj7)BYROWR4-C-p(2Z_B@=`5|{6+jM!@u&?J`E>oR^ zL%oQDF}NC{diis+9;3Rr#pbn=1r`9W<|xivPO=|e(~>XVE<;COA9H}Rd@wD{N9Vek zS-cVl4wJq62PfTdY{d#$71^SRvx%Q&or@jVR@O=2nJ4@udgqUKfda`)N8PoNfqO>F zXZ|7B9BSZ)IUq-Ib$feg%Ev-dJ|BreksPmX~~8)KPak&aY7?an6U) z=^Xg2vJ1IKm=BmPl5yYD<0bK&&*EmLzs&w>~scj zx!|V~vS#mlPnf+^hhAff)4^8C2M`_`FWvdNP#@`2zT{f(m@a(eT-kn4#bLlem>0&) zIq+^&l<-QAnpIB+TYp(rvEyQ!u!)G8tT_f3h^`z0JOcesRtf3Tdh%nBRFg{#)wz^c zo!tRQfY*6rdp>c0=-Dp0cK|+hPy~}OerT8BiBP-gtR_gJzv>ZUzc1Cpk~O?NW6dfB z2TkOHCi3nDCsY}F*$<$L8bP{-hNF4ybGeV3-*Ll~9Vp=HtUrM|M>_WFct%5pLLIR> z!8&&ndB$-!Q*;mM5brOGgvOue8?bE%&mC${4Ydz1S4~;#ca`=}coF7d8PTfmFo350 z5x8dA(e_CX8p>Le2b`|?nkBRie9Z2^oB$oAq{^=N|2;9RsJyYRo!+AC@h&U=NbKTf z^6d4kS4UFbQMsGiE`1+1Rc!+|-(TploCfp@2~otuAkfeK7+{$^0*?y%G49qPXoZIm?aLbuUNzhvdE@6L~@!KgG2@ZT@aJ<#C!Oi-NW zFn3)_d+ughs^I$4VmqiushLr`aMQc4>#|7iys&NysCfB1h?8#K0omI}`d+xYy!&2F7B_O6Q$2w;# z7TS=ov}Bm)mh&LZV%O#I^CCuhzsW1|1B4xhetv$n9~DbwUv~b&P{!p@p)AQmwP*!S zdd2p@-k-KcAGDCnMXU3)&vM*4ARyYD$r({t_dNk`!debycr)f!$JqQ8j>R#K8-r)& zPO+zHfq9IKWr;bTyZWDAB%%TO$L006wLF$%xFrJ@bMUd$d7GQ6;qqIOick3PJD_6q z9!579fJV#A!xw(rt|Rt^xJJ`{;C@%@=!XJP?PbZdMY9ssd%~y_kiuUlg&9bV?26-1 zjP z3O&@M54*ZwAx~i1Lv9<^*AIAO@n9W|V?oQg)uP;Uft|&23Ft%*aIw%g#mj?9Z(RP) zx<1PGd+-T-pnJsr7JAoT{gnzLzkBrZ#{Ds-THDus8KtDF%H1A*u=i%ToeseFEt_S@ z7b*ex`v7R{#G5%!)KZJ4#+KT^ByZG^j^em!=R><_)y2WU|B{% zhT5G58s#n@c7}b?<(T(Mmx*!>pFH^M-zTOfQQ7;Jz*x6iQ45NBUvGkU4L2j>^%QE1 z;&Kks8{ygkD&r2t)el@tgwm=bNwU4)hSv?LDt9xkQE0M>37{QM5eVs zOO`5sf{dPqd@%r)(GfP`hTn_P|6aF#|0Ap^8pp$VK>NX!S?tPQa-XZQG1`!^=xlt- zK?JTthIdZM{n^AIEr(4js1?c;o%9cJWNw%Sd(R?^YB9K)r45x$UR~M4*o?8~FhjEd zB$#NI{wIGwX$UZIH4>TR>xc72|9bLfTT{){^dCzW41guYDfKyB=*ZZtUwN+VXCssKrULBc9zt!yGUS0_~IhcgHYyju$0AI5#4#!wF0yf~*E}w-eIIb~P5=Lk~U==*b+oOMkp1-N{!P zpibQYz<}JXr+e%}@;C^D#v_hC3F;A=%LL%DeQ?Vc?}RpOPVP#FNe2oJT#x}gLyCvZ ziKtih9|XZ}xXS!HL;v=wGM5-gH#+FU5VHIENZ;6tAR+ z7JrSZ zw0+vpsoN=lrrbXmL#qFRQrg}Ji7qxp9|$!$I|KMq08v`|S?FV?6UAf&-^2mwvm`r` zfaa9AOjUHFMR$98rzmLl2K#{VMr_l_rG6jN4?P&^xLCay{q9zOOrGD=$FtmoAC4|I zgMIfTTj&!JsXW6lQPu0Ai@R^~Jkx{U_E2R+IA?N@FHkXV>8-H{j4S_uuGKoyP46*h z39pM(-tAc@@jqx9cnOdPIM69;Th@9im$kimOmM0UsvvPG_JIEgyo)U>R@_@WucEht zU-15`7JDD!L_U2^p}|1hw2H>2w~k#v7d}Dgf8|*GLr5}#gc9T?M2YW6 z3glt1ShLhd-&O@8^AuFDxEib(BvQ*kC2h!x!r}Z!@`Pq_6IhE8>prZ~%@Fo-BDG9P zN7rsZgyC31Yk8^2Lx-*U*^zAcQu+Grc-QTu=Yk7TX6a>}z`L|-@eF-3pPP^`>zE82 z-PLA}S^8ToL=EO(wHw)ygqyxOM+t{W055SCX%yEO$nmZ&T&X(awl#kcE)5K{!(^a? z=PU~Xsy+^<;;nHGQWNaaKd(Gq2MT|Gs!?0VdhW8_x;cpcl^ z009XYa0zgVx%sc!F<(!(sdxs#yPv5fl@~i#d0p!!!d_?g0>`6~YTPUZKqfl}M6{Xb z4g0kAU+~|n^l~$kK=J~Arq}>(-M+W`ut6GPbG3zR6c&j(<2rUlKT8XOJ*LTC%{P6Z zK@Vm9uL>kz^Q<>-O3AQ3=6Dw*S~8maB|8Ax$!LNRZ`01I{1tj1dRRK>gx>->3EOsU zs&ee6;Jmw6@72CJfI!tx<1-S+BLPXY1$)(2Dq zOr;wD-25uJlbyim`TI6a644TcQlpiYWl9on&y-0a5G`y>&$I`CdH|#gpi7E?BJ{6_ z3-}Yy8`30D3JH`!l+-vDO_en_H2DtX^)XOR4I%DHt6`b7XxlRZ?NOmIMTm|K|DjA1 zju%q+rs;mO`CkGB%&DS~JD)Wy{85H=lYM}Cf$98q5}>(14rL%tX4bCGkFvVqKzkxtp` z!>j=&C7?jjeOnObz6lE1m z>Lc4HDM$xma6H{2)H)|^-hSOg+c<|fQ%dh%*gP%Ad3#C=6(Uj+>@qlbzkX^APo?GP z&9GZ|uFvxG{1>Vt6Ah^!&rq!tHg3OU`BHHoi7bTUzNrnJM`YQJp7HOZ!mp!`#>G<{D%9 zk71D_=G$taB=r<&C`jOw>)Vg3MXhxXhL*cEN5Xr`0Y!c0zy&v3qs|O2G5OBJ$^=up zXQXp9NAJ7qL2c6SMjILNvN0VUo45mw_5zkq{lrhoZvV)U;5YaF8l+sK(xj63F#n{M ztIJr!cGx(re_%QClW%v}0GqU35`LLyWNahk^>9PLx{Dr#G`i~ISp-MKvu7o*rr3Q! z3ht$UcJ@%31osPx!0^pyvMdf$PQ}IB7VpqiWZ0&?o=DCJr6JsL?xy!tCLIKTPR;w%y)NUxCltSp_QRC#r9vay+zH zbmqBN^5gs-ZJERwZxTe~^4vAa55Jau(*yVDR3xa?*@S~jXZQk-#6#V*z5OD)dA=}? z*U0?Uk5w=GIdbb^i`wtVvmQlC|Mnn`zayyj6 zq#})?hNi}{Y>t5eD%p$@X|AMSGq#9ZQo64hSX*6uwLg z!JrwAWwAKgUrcYL{q{!zAP3Mp|GNv|5Wlj`*jyV06Fv>&y?)RV=I)!Q zo+W+eJp6Jl50N#=5}NA0VGFoJ(?+ee;qNWdY_>fapSTa0&G-4I1x)!XHnl(G(m%A+ zXfMf31F#O2*y@6QJwIeav|pkrV>5-V@8og+%$P)cqI(P5zpEEZ`i2Q_e_o+}`#c2k z62sX4vGuOxfIJo?N}ckA(pIPQ*^EIh;-D#lD|uKO%5I-@EM85sx(B6&M1BLvpjJR; z$N@0xb%j41{buk+4>xFbY#*Rwj7zcS@$y58$A2DZW|qV^ZtAR#*9)|ZT9otpD+&y; zV8cgY_GhGGlS;@C%j2vDX&)0ATa&@>B1N55u?9FE04CZmjWdNy58X8^_cO@DubIx&{|qD)b%I0o>i? zg$*e`1&{VI3k9?_J&y}u`#GwDFwo?;W|76V;;v^#gC9ea{_}SEP_+B~Q17#hdRtqu zNs1I40aa`v^A{=s^t-H)()u%!%R#rWJKOPH(*Xko&ji-NN=UQw2DsjO2?*#Z5>p2n z4HghJJ^BP8$1)XJpDUjHM=;@}%ysW5_ed&x~>vYj95BmCC zCi>s?);aW|T+UxBkARh7s>O#n=r=<*ZGOXrD{pOTGLtrry>KFfF`M>`=6-vs0l;+` zNo!fy`N1i%`{<~LO`{L&VnGpgobgg50$^hF>hgzdI4=Ma-RP?CY{5nf$N;GL7+O3D zFzp2$}k%c2v7?o@Evb9xe=K#06vFQ*!z5V`NZh#`x^C|GzxtPb8U#4S(piV?u{8E9| z4J!K*fap!frVIZjNc*7jrW$R;+fN$*XpAa?J)?wpqmI;h2o>hR@e)=7H8%HpIWED|XQP-#h z`=dfkZDfY@uPxOAv*9MmJ;FbUjL=OzBc0sg0P zS+|TI%`X6V`pB_`EVy@JsqHTX0c2dyV{Y}Uvi+zqwLAS%A9+FX#&;xS|ImwUA@2XU zxcI>W-6WWv3uNV8GmK!h`GPS>K186+H3PNfR|V*w2VfF~mNq3_$e52HQkIqrejkyF zsQ+`m=}0!b4t+L1bnZu#zS}cfh}_1d%%Op*Ad!db@p;`=wCjshI(p6Hj6IxWC2m|o zI5;cVSKahne34|lp)jSf@C_9LkB$DyqaB+-XB6#jcq%+T0RRRZ0_6jN4@3yz_5)uf zUnr~8*&3LCN&0z(e$vKzDB!?p*mR%FD~J@n6y~j$+JpHT+%V0a&p3&95WdDsew~KW za%t2k_xjA|Won2e|MLv{=Cncicyw9qcPmulBwY8b0hecmWbtlHNbQW;XkCj|%4pWI z>OXAeoc*n4=(aWP8du*|k;q(3eIrL0c$6b8`dLIY=suoHSsPO0YGw)%%|g`2B01nMo=1$!^S|16||HE!bq+ zI1ors?O;_MyU&z|0reF5_J6y(tAkmsXzL;y40SvPf zE)R)-X!C|SzZ}=F7O2Xsw9s6+VHKlzhG^`b0l?5C6teonQn{wIWz9wgvnQ_;(UcdU zVwW_&Z3mftcRjM?&=F(=M_GS7I%l0Kis~)*8^k?cy;Wc^=bTsMz5UR(wbEN&mFCN3yxAe$9IJ_btl-I}VA0H8lu>E~fGd)tnvR&wu#4)aEs^1&2M)C+xXXO4 zw`TX6Q{Go}8K1V%T3ZPspU9_e>oa{NC$EU4a027_$o#}7QrYH)?}v|y;H_8dc|8fl zZF+4`HkbcXV2Lt*0UX|Ql7RKPMkPLlNEFrki=_68Dg^LBU8qxtl}Ugc z?zhA7XDHZPwjmRSyVFCA*d%@P*`7(BRS=)wq-39J9(iHtay8FDwv%18N>Wue|(ur8{yFm~4H4tNq=LRA~RZ zo6u6;tMSMf`>Tt;_O&+$cV9t%E5wtb|AJ~ey`H&4t=4;5u2Wg~Bt3}d_URwZ9Hk`` zJ{Fk^?gz3pma;{@=+G3pn{`dAZ_-9j-U+xYs>BJ_M4Q9v{kH8uR;zQ4(6?uhGq+R- z=5W%;$k@o{81N3jbwL}YE z?6K@-Icyn2TdeESSl}_L>(_R=7pXJI40&vzYh`#3hUZJ}WWUtSPPj-i4p$J`bwy8_ z4oEP@XiEjp0r8+W+gm#e?{Xc3@3-fuvAok)LpAD#LHQiG{2~Z4c;E?L9`^1beSjD{ zLpg)j*`%g;b7&{*cFR4D`Qaozk-yTr>P7lDM-TsOTW{A)NW$3Pb_Xe&sxnRUQcY2eV&2!L1Z9+X68`8bzh zISjQeDANqI;5PNK@0knDq=M1|;G(2o3@RC6H{9~bseNHN?_38hb0BW|_UzTlJF63; zDWfT4DPzud6La>AFRqbTncl>FN{enklZFek60hsmbTp-j=53f_?lpXsHp<=SZ$>V@ zZ4#-D#)Yz51Zv;Y1~Mu1m6X{H;WBz-9#LRPJABOBbFER?UeR<5m0ym0heGOY0id9jOUn@f8Qnjk$ASf#-C``&yhiMx?G{%X8afwy|DB&-C= z@`F8F4@`ViaL+l*T%qo`o3)y}GlA|4&x(ZDk=C`Gk@M>@%l#)Nry>sB8*shu{1bT2 zAZZy$T`!(l#w#2W=Xu5Q5>sYx=Km7=Q^pGUed@M(bW3~fkN4mJrzO@y$q z&}|vXkQ$Q^S}**q{zEkn9bywt$L^5Kdr1_hkK+ z_eSt*WB9=lT;b0vb#S@t8U0aw9$!|XEs-uljcN0;(dg~DGl%NrTJM>u;CAILHcr{b zXU;|dZ~fe|1NZYvjPc1qh!w!o^7u^TbACgEH!C7>a8X5cs$JYvVl!N0f%CrlFd|qs z{FzM>YnEe}IwcUlFAP?+98Ne+tg>Q!!X`2+d#L)u*1T_B-nx)R@?Vi;uPNH!^4;}M zNE_Xe@YZ>8Ts87vB7BAtoiVkGbpd(^Se9EY3EaD2{oSPF!=?e!?4W%OKx$t&V*(b) zuSWo`i_Bmc@s_zvyywfKEZ({u^amcl)lA1PN4NV7nDH+C)dgh1F^{PDX&U``eYauH zvcP;6XD7)=>RBgQTIai9g8yKZzcHdvfgL>BaTQf@p{-cBZM@ud`Or^39}CA5@uBsq zx3~RRKj>oV*t%^*Ao+q!xE}u3I%S{d{mg$dZ~@RE@lHMmSbzrF zKiu`dvfEj19YZYNORj0E?Qp2aO}%VfHrf+-kFI6UZUzA-Lf+XD4`&}v1Be7t=CGev zL~)y#{FgwO3?9zj#NGosDEJFVi7KV29%zR}=18$89)@zfY$?Vo9D?*~`@yz#2Ry$H zSSI7+Uh>f~&~d`=$BzS=4`IM3GkOaj!pYT$_)xErF69or{%q`SnTSWiN5?=V44zCSf8Ge1%1rwhEGXAxBkSUnNv)d_Rqxx0ppp3;RgER9#O2`@ZU$33T zUIY(Tnvfv#zCM)$visVMSm{8x%Q(HGPu4rYp&A;&}_xy8#|sck)m%|43SaOCbgl|eu16%n|;R%QG)13Z}-CV4)->ud1G zP8;_l8qZy(?|%k|s;MSJBoK`PBW7ytVrmZ5yO3F&Jt+7b7QUF*3>Q^_3y%gWKi}vw zNKqNpnRV8vVoZ7Y%bJK8pXSN{%|ar1qZHQ&Knn^6Rh&3troXo>6}KvVH%;T}&^B~0 zb_iDzaNizwmu|x&GXF=?T*L) zpv#FfOs)~v` zH+zCN=ulJ^()w95&816=f5W#PrnAM~WBdiOK$b`g0GE5=)b|s0B2rw!6W>KnJ%{*5 zzRDU4I42apBNZQG@_}9!R3LYKq%JFr+@Tlss&s)ePIP*11yOFe<)?C@tT{riW7O(O z3{2?h+gqD~-5?8}=+*=G`r1-!bleY5daAdZK6X_lDSc+RMn_HOv!ZqYhmx#0&d#_=DDP?ZSIK9(!aI!t4XQ z^$5`gl(es*F36T0WG$p;?!kI9>r6?P?wn0pNPz3(RngZP6*$W2TRFem;Z)_0JVcfM zEl2CJVtgiccW%6aOTXXIVAGQWyz%9)$K{_u4%opVc6odc@~ZoD-GuZStyee$SgsOl zCqQ3rKc35KAgklLy~oLwd$O*6(`ktM|3Y-f{1GMN`7bv!v|1iXxnz)IhZfIxHK{AY zi-Vs(r(|jJoCrJ8frC8@4*`H_zhIR>n%Qs#a0@cYuiGJdVa9%@3FE6&!V9OewhFmX zM+iiX^=ZDzr5`2I17Kf0d6d_|Cc9z5Rlm-KuuV;z9|77%{I`z;q;AUR>_WR~3xWEtB-_CN5Tu2?C@B_i4p3xktm@Jb#PF@sZEGEqt&!g z`;y;nE|Vn#42zc3Wzm14lk-T{peXv#7*9|vevwmBmLgVEI&jm#w0dMl#1Dnjzy&b% z+FWmy{5GlnKk-0r%I@a3NXIQgpiu%BDzpSksMZ^$H68 zS=*XuGcG&)Y2nCrUlS~+(3Hs?l)$6dgYXLEvuhJ+Pty3DG|-?|;v*8tses>^c`rVf z=srMU87`2%htYbZ;#3&>@JJ9L@&P1h1*s&z?gZ0Ab_gY-tWW1hhqSv5JZuZPnk*Pb z%Y(+9tEL!D^KK`bmGNdsN-5glL3B=P_^>*V1&jFBui10l4~*LBSNHqzNpzg?e~u71 z$=xnpoG7WaYUk8GjEj2bV@k|DqrA5IXhZ7W4i1l?mUvzK=8_?$6HQ8VIBE*fI*(I^1sJ8^Z5-Hr_WMn%A^9RJYohYK~s@5*2JNcQhdglIfoVwfu~MAxhSay}AF~daZ=248 z$IL}wuY$nd^am!^-e!LdY0Wd$0%>IwtwH6|j|V{YcS)5xI3RJzqDreA+hzC*GV|-) za%Q`Nc?;PjRaD;ivOH5{PQ6<%tdmN&_E#f#PG4uQ>$f+NI-$jiHx}oG%*H4a%;7_7 zt@|+%z?By7WYzKG!#iasnTU^{5CNeHz!{4Ez2MR7*oM_#bHv4#Xx_GRYH~nF5z-wT z5ZFX_PqNz@IY~*as$`Y+PTu(K2tlW*eQ56&kI)i ziOzl0(Y4H37G? zGVrb(LxNb?Q;2>5{7q(G{ELb`-p+jOCdn9~>Qrm-Gw%ty#Y7nybL_jly)!%)Y{ko4 zO)yGBijSqG`ytesf?~TvOC%R-5vxg&lKdd&XYVT9G`u7N6{O3$bYDrY;)z+m8AsR; z>%p%07T6_A!`#xGy)ODwN5;D^kh_t9j>IYdS&irP(R5a8&k%xbhBfySF5r(Zry+a? z?pmSQGw-?yfl4xaR#pwC=S8?S6J*PQALPIUs8|VO2Ve|kV!`gL1*OWMb+2J@tKCUa zpoe|@$olsW-r2azYQm6-t4)2}=`cKR-YbZ?=HQk1tr9JG&e*Q;O(wgZxvVgi%vg%U z%mGXV#m8_@xmX33h<_*sdlvO_5o(fb{>k!~%J_00a!geZt)MrlflurTiw~HPUEQ=2 zx^)qtC}7}ON;P}CuCtjl-CQI@?9)2Kvq1;Lk2g)S!HYnj2d48r|JN4jYJ%9GmHQGAZ)fG9XHL)| zGZ!tRt+EnUb+HIGfcetDw1U_;?rm+o#(f%h|6_-9&|S;L!vNuZ;L#=pgg8t5Ie_EP z2wZ9MX;OsI90y2m<-2SCl-o1DOGoNeY9}`Wo3(e4>HJ!U$SM{rGMXhgH_?+Eg4eSk z^|WcSld|CP8`4{~!p_GEZ>yggihp5#VV0gR6`NFkWe=`x zcU@#JIywX|qU!^8aKCG|TcB1BHDvdXz=D#`At>2l@S0ZRVfn+j>k zH7JDqu!8V&q0p@J&mGDtMn-90fkK9fPF@$~hw7_7cBi5}Fylv+thR$TYyqVG!^jt@ zye>{+_J*6LDvII6&q(WC{5;4VhggS6ZY-JX`2QY4GHtE2Q$NybuT~!cQbV?Dca<&v zVTZ_zlD7E_AHe`I(EaNhI-tt1>KSOwGJJQjK)%yzSW zi)wSVY-C}PLk?q^Su69CBGg|KT*k&siFS%)usMM_%09$n3aDjkD`?AK%CGiK-qXS+ zHMGND(GdEmDe@uD^zBuGrf)kXe3+|T=s^mTDSm8VJ@zkz0T2Cz%?M8 zSvxz`_*jkE43#@SgGr06XP3mCL}TTj7RMr{OzIitA>c&I}_`b?YcBJ@5?uEL@e%0vp`j2bhGvLram)2(E%TbUng1KRF>!EHIVR9?3Q{nHE zH_K^3>A$UdW@0-6e;KS(d8h1813m>%@T$M?6ZMuQEj6mz*YaCAzt=)+pDg4Bw(pPG zDDe;CZ{vpmDzZuGht4jLc7&?Y!r|+s5(%e|NAP!5#1W(nfHavpoF^rN$X;d(1bRj9D2RIb3M)Uj1(X^M4;d9$>{WcD?$#(6+v4 zIhoZ8$fJ-Guki`aJ^q4~*M(_df}P-_3CYIOyUtaL2%Xa!hd0J=_m4DM*nzB_@_W*4ZH#fHcfdB!K z<^Z<*yrBRAcXvtczb~yYwz949+w4M;;ZSe@EmR&YB_X)jaB=-?X*v9ck-yib??(1# z`1RvKM_omQS+HOd$rWHQOF=PA(3<1=#x|R4)UW2t@6sUpjN-@=B~7_2dGtwu^VXOD zfma|(a^l5y^}^ofBeQxBjCb3Y*x(yYWI=e^1pyX4*RxU-*|RyL!g6rd-`mOPFNxsUxhm?`D3G6TIZ-1hTj-jvq zm$`3dT|iwL{<`>Y1=U~vqo6wQ4JDwM=Mln_@Yk6b_Dt1z-<5cs&0{Q;YKj}GB5j(H zVf!I+-^s8F(&iLy7=6Z|Qmj1}nOb?n8Jg$8sdK?-f>gkGn^01l#7OBv ze=V=2y{24u;r+9YX^k$UqoVn|4vd?r+HUVwn?wrE&k?O>Of+9Lwm|;8X=^xe)6J~L zmkEWY=UidR@j15H29NMZFVjC!#Uu{%w{2T~!9|%{=$L02>q7i*U&0}kb=pD~$ya<*N)3Oa6;l^FY=OVzk9BI5hwL1bQWo14dl z)ZhmHS7&0BVzW_gmh)G4;`Lbk{JcteeZaog#sBy5q3A4#uDZ~9f17wcb!}8_AG`5B zQd4E4*5FJEe|G!DY5w)T>f;XH#kXICJBLVP-~&-fL{hRLCvXC`Df>t7$PN)ny`t3u zqJ+vEMD)tATrKfdQ(Bd!gZHv3Q2m(s$-Y^ncCb}^OE~q~foQtD=7y+v@v+H%>_(QE z1f#zBzpn4?TQ_TeRi*z#TeDiej4U^O-By+jm|N&jtbU@#mO5+OaEYqg7M+uIw>?Ea zuM@@Jus@M9X`A=lMe>AL@vYx*^t;5{|5`CSh&9pvW%Y|QVBAOTb%uj~-uyx?N6&pG z9%$3|#x>ZbvHezwEGlK2k^U1ARf1$h(l4kV|Fh;BYyn-MKaXMExe!OP^lx81J^S&X z%UrC56)Kar8DKI#h7|W^*-CArym6F{f>+DTIS8Jq9n&L{V3?^d3e)Pxbnm~2_}wXD ze9UY~W*gWhT*Fz1u!Z-FxpeWPBzQ3I`fPYoGXL~wc|rzLI8c65!WH?5J5s5v62y~_ z$uK9g~p8ge32T4O0?37c~&7(!?+*-o?OQK;VHs* zx5oou^EmIMZt1~M;|_el5c6FyWwb_f!$;~MgSrRuvc1*p@VAv^IBZ~IXaYEXLvVU* zn`89t$<51>`Shv}W9~VKOuqU+TdL=J2j+@26RG5}6uWQ~nArAZ5oAjhg}>6~c4zUg zDzKlp)7%42i~yv}9MH-(F<7PIK!X0CDG%pNke`4?+1Y+0VO}zOt7zZYkgbRjtp6yp zbzSi>&tR+t-I@X#Sf39!ST$51-!RmQh=pVPq0)}VYR@<~xkf{Q9HV6}XFV{@2_7gA z=wK`;V0YNxqq24kb+Sn9k?;vr^QtE~m@~~L9p<+?Zr6MD%TBV5#7Hg#2Fy`~Bkeiy zgzI?Xbz+oOt}fBst)tMGH?&~ad=1D57M5iH9J2!DNni)k36XnChVhc7xVIOt8h1i3 z>;Im~UBf2+_4V*S;KUT>xvoDUgLs5)Xd)qvXnMi4f6roA zV3PEmp$#_Y$+l$W)>^sIRxM~_!kOK}?i79Uh@H4_i<~@d9gzu4pJ%O1ATo zu!eU#&lV((U8Q?Fr^w80OyPjrR01>#zH~|ve*T?eiW|-b_RW$5*vwGkV0?h&;k{K5 zUk9q0R$&*VcmOMh36gxf?iolBD z%w4{f`CQae5Vwmz?hk(zLv~$CcATUJp_Ie*o3z(qMt=jc~|L)G7DfBJgaN|)z!%? zdq$R-404B-X0 ztwVUiDuu_Hs2NHv**!ag+>2Ry|1=Bn>#cK^yutf%D5 zHa=e;m^*31jO<wKaS#y`#;IA_(^Mn>;JR-8t4DismENJ}UFyQJRtxbG0$5HKTI6K<%w zG)?HLddGfF$Btcv69g?B(dBYb_rf}Y<5pgr)ONbj0-&-j$8BdE3kiN2GCme%(yJ{V zuW~QIl%WxCJNwTyTLn7*Y+*7kcsg0*=8UeE%(kFjz-DG!19-dtdjnsg_+C+)-B%me zv`M=PCbn#@^sN^|S#9X_%w`b?Ad*y`)Us?ON=$ZC3um!l+r}F`RSHbZa7%s>-(>|D1*bv+mkl`6u>{F z_fyr|KF0d+h@P?le5Ooi*9reoS1kO}$Ki5VjTw#?qXIg^h<3Wt=Lj>w;eAv)Fa8JX z<39x*dI$I;aQ4KS=%PD$K+sNl2*^rk({M_^LifvQ)BL0sT ztZ;V9GtD5Tj9_gRcdj!D!wMX;l9bJWnYdXl(X;kWa2Ts)A{jP5coQA- z&ZHWR*`;Y;V8`RH7LUm*IKcX=8IB}1UM}_n!m5CvVpMTool7%77Qg~rkoJ}Vk&IUA zY!>zDae%up9j#*P3}>q69IB^gnm`!>_fE31L6iEMAf?>#%PtIeV^SP^QkG`}564T1m z-9Ba^tcVr!lU~^}@fYQ1Y8Es!SOMj)AF9QOA~f|%!>W8NFeMSk$lOMi9G3!f*j$^s z)NZO$3#ZFROG$f@uW+!#LxyK^z0OLVtl3`>XcHroiWc+*UutJ|azrZC%0_Afnrr01 z&^jDtX(-j$6V`SX+%fnCZ04jX$JxA0#V1nIF@$}z?lFctvY_s2e(B*U!9j0g7 zm>|$n1P=dG>=J7U3``v`Fc7s8__XJ6kzUpY8vJR!EmYP;uDk}%*{*!#H{|9QnL;Cf zt9tkcf>SdPEh2p?@!Yl0LcvnblPU+78N8F#f?j6oCUvf7OG!$JIXO#>%Qr8N<#cA8 zCW$@^6gd;KuBGCE!z_CHv91l9er`uCBa<&(#MGin7S+r;S+EuSeJ7qLKZT6{CVT^M zANl{JM}As(99iQioxfrRmIonOK=(4h<0v1aG6irz81Sw$)GBT>;&_RgWeAT2vbnLb zpCaf3^kqP<;1r-4!>WQUOMfx|1az?odAC^vP3Aj8r%7pq1}0S}D)lFUV^gRBNlGX_ zg*xkOMK$*Nm{d)U%IP!6({@f87V`t^ze$ARdwvN9MyZ^syCj&DmrE?~T3|QQvaE_0 zC+{G!$5{oLK+yAZ)kax+FLMTTFCVEQED8K4%>1 zN#_jeCBjZn5@MPst;DhtsJ*7Am!OMiEkjjqfaG-YM?v)4MvmFFqP2JP-5ho)y8liI zB?Lqwvrb*9Q)E((Rtr{Y$MHabhyN5{Y8NPu0Zvv4l*gGAQ2PU3VW!wX;i*MHdDyDy zGUSXO2R_E6>jNnfhixRh0(z;p+sg-Xajo5V{MajqWP>D4;| z-=^=0W6t(H|L{@oi-2KxednwOG-ocYNx|7rmtRuU&&>_`m6`>-ANX@n9%mN2`djFo zgc+l?O-|CMV`YS^{A?Lxv1mQEmQZDbl(B*@6Y3YpY1fB0GP}bU?+gu;DMyP9KCu-* zvVzep-@U-x#jD2E*%3I68tY1{gC4afB`^U4+kCcHc{R-pOxs)ui$($3ZNLU3(AWdH zZae)J+s%oMe1ry$Gw5h|LXSnlHJb(dI-uLl!7t)|vaq%K4hJtgA+->~Ux}V>Co?x_ zEN%gcO!{RhEV7qy;)tkDa>;K_3xG>~#ff!T{QCDZb|(-T?!Oq?W&z0hPA%we3xVa- zqMsG4F9N2NJfC6d>>MB%P!;cdN-~jNpKyYfIqy z#%JDjfNZmUc;R#Xxy`yOnm3ezf-(#yoxBkv4X4B)ko-=Rvx?=GWTzXnU-MzdbLT21 zg~d;_rQ1`0DKk68uu-$t-fuc~2I6E^Mo|q}S9h+A(QNU4j4-q>+T189{$5;A$Cmq=~FCFWWdJd!+ ze)95mHn^0%b67w#*=OP@<7ExD6sdsy>cL|vjiR3$OTS?e7~Us%H#tI)LYBbCDANgL zoj%w0CS6ysQ<^kHUs{SCvr}|~Cv1oY_URdadV37Smmjpm#29;s1EaFEx;O8-S2A&~ zwM3>tV8;m?rlG(M!fMW?+XKj}jXBITd(>a)~ zkC$!Y0GJy-GG?1EAt7B_R4sP_oZ||2sr2$swRDP?a=I_FFJ;8fT@$s-66kI&a-m*P}W_ZyIbc{RcSs z?leG@3%ryS_pq90OJVb-h5=U2IZ3m0sZ-XO-6xcuDo>4rs^MkI;z_SeNN->U^yPS_ zME{sp(0Cd0&#TJ&W4P`oyZn`BN7dDGkM}K-%G_xvI;%7N(Zuux#;vMD=;~r2?l#cu zlelILB)|yD5nxO0^udzXu{75GgShZWcJwIOkps{+rq0?*6wWX)S<1YHWNppiqUs?xFw4Hd8AQQ<)_B9QpH$8wIx*BxN>TI7JqjyGyEEv z-YCe~OZoToSUGrl(=bw~{<@RLF#g5LNTx6sFgbd7;x|@*jRWHerWC3oLe@<>KzVkD zD4BAm_SX{*j#OCcT*(c7)Rs7wXkXbRL0ZprAp=WPGe4wWmp`*>QF4u8SVDDjn_xD^9>sY>V>=ys&q|2a zWsW4iyl`sc*Cm^k`C3TO7IHnhSIZ&-nw7Eo%p@P=Q#$G7vXdt5YLX3oS+23QkHa%k zro%9OiBf~}Qcj*kwXosmIvdq4t1-5YOcAX=eEH<^ZD7HMikV#>_rx6G zj7GA{O&*ZSZ5&^u&bka|*-8XoS)_8dg>0>=rJT4%J|t*Ht*OnASfu!LFx0=X%#V4Uu=fmpG#s%SlFxoB zGpd?gKRqfuy{@x1vKBe{i`+9gn4~r~6FzsLs%CZ1`n9;NlZtzthBy_OG$jxa#JeF- zQ0kiPNLj7B4pN8+wApTGKkGkiUp|U&9a0-m|EpNM9yW&#J{sliAG_rJ-X9hyEVhee z)2|#ez1!IG+8^}P3$awmftJsi=zk9u*$HdecU?H7lwl4wFq4`a#mBOS04MX!59) zo|GJ*$SGH(rJRgDP|!`E)v{U?kFot4tJfIw&6m=y>_KiSt z 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

x` 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�g%2dcYBJaVVshYki&>#Y~mS#22|cVO}E_5 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

+5es%*XI8hAoZf85DA6<;cBqki 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

r30Ku>Rf@*b-2pBuc z1Au_bqEihUoi_x%0r(K`sPdLQ2-cZzH_WV~c69^k4qc~*b}-uLW$T(%={eXOa3T>} z9wTJ|2!R0eV-;vBz$>Ra)oL>#LgEzFpQLvMwA+`0Vanc>1$}{ZS@#~njoweR=4nY> z!Uo#d8J7gSc35i{2%yy1cA?ad9v6m)X{O5*u=Gj@wzW@)KEu>6m_0>QW7aTGE`s>_M6KKWN+~%TLKr)Z`1gp`_`V%& zlgsvKc^~9u@IZf(`UjD()MS{=ui2fAopPM?udJ;=;~d;gmoenmDVaa-1kQeMnG?9wp1oSIl{2Q*g1;#RGk14tA&zlp5wC zl81_v&!RHZ7O3m4@2|q7{baf`c6bv8r*dYwko#64QuHr>kHc*&o9(c7%)9z9-bW@W(#%&T6Zw~ zXY~nEt_=8#kiUiw{^)v%m5o7NP$nI`y)aqn0^Wj}nNHXBJ?$ADvN?OrH#F^n-|8pX zMENjo16(uI9XrhHYQ^51mt-Ktvl_ccdX4TWwe2`3Nhf2)Z&{d+JwR!{s4uiQRCyYW z3@B&=#-b?LgT2aSl-6N^R0hHf{?JY`&JV1^_$>29enuLBl6qPn5zumxfEH*1?dcdh zY->K|y#15=N>q9AgS0!)ni8Juz$wi*+;;_}r7gzGD>0}V3#lm0wzl*lw(4SuZs{>| zUh$_TYvxsYmfofbR5T6(buaUxp)Ik*2B04BK^!4KPq69(!SKZaTH|t)W3)5tEH0`( zpq3D|e`G5Tf2gGvT&N(#;7_2xQ31Hd*p0Pu`Ahm1I8pdQLd(=Y2D(FWQZVt%zlFxR zPxPsw{X{)$cEc}EWrP4%f}ts*bhu;s5e1Z$Lx~`TpHdz>C%Kst$(ZXv zFCk~@GG~HCby{OZTux*nviTix8ru;oFvth0ul$-0vZ^J$590peB^d1m4)XdVfyG?k z#2$#AacP%)ejUeCGF2D2{^A8-1n)Ml4nF?oLeudT`iMEO9q2ybCk77$=wu?}Y~o-p zjjuv1CG{eJQ3SI2l}l3p01jD+7l&*@J++tke;m_@L8w4f9K$nnyjc7fh$o0-MgBbr zg8u+{pY3&KnaA0by^r(A`dInvEDiD(RByWhWnu*#kh6I2QnyQ0TB|_Ry;cgDdq+cf z*05)XI?^?LQ;Y-HhU&ai8lJqdWKXA22x1H)tYEB$aS9ausAiBo5!cBY#ln!eC;EkhXvdL$h6%)EFY z)%k(-D5gNOiC7!WHw*s&Js}4QqsRWxi_RD&qT=eo1obQ~a$WmoTdgasQ4<@%Qn`@~ zuBGaaTSO?5@ZY>eu<)PBaJ*bB7(ngR5)gL0!wC8)dhIP47%y>8t9FaF>=} zq|1ZWQPPU(s2VzL<_pg~;j;pCxGIa*BF|9FeC>w1^_IS*IK(d5zge|AR2XTMg0A9e z{X-iOMsiR-KkU4@3*2YrVXD=O-}^8GymyvGudGvw1FvP5D}`9CX6`fF>4#b?DVJrf%L2>KFFewV>py6 z(WM2|543yWq&w65n{_#aT3>98SK=BQ02=d83^cR?g0BmcO_|FLWh)s%4ipO)xU?E7 zvRBMX!`W?8!K3?(*yQeGtL-_*!Wgg&s?kwn+Gbb>#bG*RO7f+n9@Lmd#+9OSE{7s& z4qQ6z9O9d=FnJFZcc0=={Z&zH)H^x<=|M_aLxd=+S?)OlybbAJnr@ zL`!rFtLCN2sg0Os0|MtE`OICg9Il@eh>P7Hk1{M9r#h9BR?hL{8W8wk1s)0pn zGsp=E(%vEVDhTcsb5Lv{2X(kO6*wMXAb5L4$b{vbLva2ru&0 zkb5mhH9umVl-WU%;~0s+VvyHxEvRTN9YB^PBt-RJ684sd3Qn|I?#oC{Fh>Q8G^cxt z^;QezTeF*EMc5Q~U>PJ1IC%=2YL?vAS2NAtfuSJQ~22R1HA2z-W*An1}a(HKu zFNP;blwvPQWE;AWvm57(@p9h0)qv(Z_Kqj?ArUl z;%cv|W0^8^*g}OU1FJK`S%GXKJ9Va}Rz*!HK&)AbZDifnaSWkmi7{9T%NfbC)mi8Y zPsH#Kls|FtH;BNEvI4oQ(~#*Oy85Md`z9x)zi@m6ZfXI&M}1^UHl+NL=SF_WFos~N znnV{Dz6o_qxZFaxb&R!;55uz8G4^t&c! zvdSNSxJ-+)rNtsO?1s{MV}@tSrX8Md6ch{_5HE%b8z1+iAvt5IgrfqRpoNe5^oJx@ zJyug*jw%d~8Lk-L66;qO+1_9{n4H10@(nl4z5Nm!b5IV;ij`4){{Wrh0k1lNaj*P_ zl5c0+0pmaRR0)=@8l5c>$~b||@wx;mk&Hb96%ZkQQp=>k3OC^_Xdj5ss?6-A+?l)2Va=U;yB&t2o^CpScy)afT|Vg^Qs|W1&}*>M_Bnd&B#xI`17v zWEB)E)5=-tG+D<6x2i8J&wT@duSMsiyc#v#Y`gOXVFk8y#qAZfZlH&B0=rKQC%*oa zRJ_CV5Lb#ah@qBT;w;&BJYfwjXuLvTl^ZP6)&-|Vud6zAh(pk?mR(k+i_f&SKQ9(T z-GT7e%}Uy91I3}I>m8MIBoq(xbFpFvtTp3Vo9(D>0K_HqghV2jn6oYlOtttdlMnIK zFIlbTr#NEo)3fhQ2Fw`U><|*jFCv;g$q`2ZmSfqwF6TfMplh$(knmN}DvBp9;FJ4H%b4%3-cH+ycP;L(;z9kx;Qe2*3)5Xiu zTUgOq3+S|S3l%{^CbUs_tlr*fls=1yl?S8{5Mm7VfYzX_xq^*NJk31!gWjfzzkk4A z?FVRas8p13A{&z&5M@o@=>GuliFf}1-R7du=wSv!Yb#j(mDN0sw& zc<1p$fKNtN22RjH>HbTFSP8A7_2RE^Us{z;$}4WwGqXbH75bIomrx96!1LxCVon^} zN~*_xks_FLErWwx)$usAX#Ei^iq(p?g(+I3twcmQ7NC0#0CPfNPl^Xe+CCo=x(ucN z0GI~a=8%=P5p=lzm^luyxw5O?SJ3i`F;vkD7nDV1T0y0>?2@LXViNWxrAKRlhWaed z=03Zo9|#pL09&nM7GXOqRA6{NQDpO6MVJ`yrFc=fYHn{bb~<)WTvs)UcD)5dFhlL6-2u zyj$c4y8~!@m4yqFTw-x^jh+K3VO_l?U;%?S8BYG?L-s~AoHx#81^{D?qpw5W70nV_ zY9xT&*(=746CT7hp(2b$NBLPIv)Z$=9R)p}y)svBF z*_SAwGCE9lc}Q#x5hB>v7=jvoqK!OUd_t?Ghbj<`9eRiM2BiQssnVN(>bo6g)t=_dG#0@(!mQYR zR1}ihYlS~iAicU4?z>Hp)|9yDK&N_z z?Vw7fk)X|9@i1+?3X2f?3f9R$~A5+Jb!q# zg}{q=^8Ajqe{%r< z8avtigVpH2M7u?NC=HLB`v!_)h*4sm5i4THf8>_hC2o{KwdTKU65X9A)F_a1NvKpy zYZtacw+OyywpvCOW|?4Ta{mBHq$Znl^q$Fx?*OIb#jAquw(2V?6>ZI4J!4~@Ll6$Z znRMC-4jC`ZO9WfuD#p1J8*g}pD~o;t8LZbzG14ChxH-@TJe#0!#L`^3Jt(!Iic#w3 zw*cLSnETJrM>jGKjXc!0SlqhUdFVVwqgZRxlRb0a)+#JZXBbwy^p=3{17~`x@WXIS z*4Ud3gwjq&`9@e;2jcxeD}5mayfU3uXc%ULw7^CDIb)o~?=tqA-u+5`jyjUtmuK7k z-dG!PJ+71I@AFVQP-@*oWGvEkBY_z%a+wIz1 zX+^vHf&&SQMJZPW>ykbb1r2yCPLUUQs*HAR*p^od1YQEVQQe@so7ziEA4ct&W}M1e zp@ev~D zF7E8<1yXA%Q~*1s`HN*Pu`FpYL!>(_?whG;d|zK!JuO@4i0N`FQxuU%2QV;0G+sE5 zE`%=Aw@5Q|1=R3r3aar;1##Pm*k`;&X7FNGIm`b0PMc8reuP!~kA*7$VjZznx*M+1 zi>SC$h_b{W)_X&%kD4N{GFk50*7d|zxW)icHwmBwFh1nzyLKJeIFQj<5WQrK(*OlJjlVwG)EgguA6@&zmfS?OfYdf2rjc)+}oFBM~leQ#c zYU>KIi212dP;{&yTY6TB)>!`MM-KIHy}_x*D>JyhCE_LGhjmWr5h6AH=qh;_u%>fr zNSHv{RY)GjyunnfB1fT7CiaNZyYf>iPz^pIf8iFR+*or#Q83^?_Q^pc9d zh$a632*BQZa6t!u66yZ{kkbKWF&t%>&oNfGnn{b?8EXAvf5f802tLR_&4SqD6$o0z zvQ2M@G7|dp2Y~&O4rM|1Lp@r#E3Ng6zh2U_-4izx@EpO28HoEqSd|8tEbjvGrVPP# z$9a?2iQGnmybx-uP~iQ_vEKxM7gM@#^x<`u{{SK#!OBX^yv|_p_)#!5tbg=v65yH( z9gz*1wLUqGZ-isM*NP$nxCxnWKY^%Z_e~vvQ2PhC1wC56E>eTRYNX}>hB`oJm4S(24qab#yeO%a`ymE++xmd-#YJRMEZV#C{eyH% zYOy^c2-O{2A6h8nN1l!|JyMRZw8vMp=?pf+^h&!2B)?2fC3ROY+eK?ql1PEk)5S2y zHxoc9XvJ3H5ycNRo5T#ERM6)X<2@mkvd~{D$2LJU2ZqhZGvqe?k-+YC9}6~cIUsHy zSm9v=`hQtPwuBNudB=vhfc7Txl-N9O1_j#&>A? z;cv?dwN}*q!SSP;agZ-lT6pOPKvn4<>4pxdhjb+{>rl-kx{r$e!NqnYUkPkQ0V=vo zAEq#OcKd_%F^-4SE;*L*8d^9jec!2a@hjF^vr>Z9{{WF^T6KyeP?WJ$!ZyQ=_Z$U=ueXt;U7YmXo77BMs+0%n>}zG2g<(YE= zC}xkjW3!N*Bilv_#Zob`qe0Lt;i%=5yulI`W@BN~dyXn|EoTbLwSYP#sxkwS8s`$y ztCq9^`NUooeTeLIzW)Hqax?e@7$a$>)!>GUB}gDG6?jZh%7Om?O8)>-)3DF1%+~(Y z#Wj~iW3ia<7#o;%=XVoM!C^>^%o)c-qgN1w#I854u&UlAqZVIDopgDqRL`=0iDJ)P zvGaznk^ao(7kvm`w1~Seh5k!GsxZBj{^b~zMQ9Ye8rKfWz#Mt>|^)8kl|waN9gYdbaO#DI@pgnE4(R?TEmDb29eU3n|>vS(9B-~ zY3c0|UwCBeVNe&Vy~P@eMw8jhW*T_#hAk#5xtCY0_QaudGXyzrlvpt;E{Dl6@Il1f zSngfLnX^mn{YAN;;HjHw0p`c_#2Qa^BT|Q_c90oy;D2$R_;u>c9?8WQ98IeA;#9y@ zlDxO>RH%B)Y3k2~6Fn!?`(Zr@FvLO0mcE~8wFcR=Ac6?N?=I;3{9Q}yJto9&@!*SY z=6=)dgDvntLTbZSh`peta7rMG17hOp$Et?09Qso7E*;j>GosK1RlqPc@2ib#kV5XT zofwZu?34|HnpZjE>LQ}A^+gL*4ZTB9ERnpikhn!VG(Fgfm|GnhDOksNP!`sO>QkZZ zFbLFtnZUgvhnS_~t1Pq9V!5hj35S2Erz*t693yg%s8wRrAi6Yl#0)YKNinOgr?~(X zq%5zlJYQyVD}2z%-*>d9#k)AQzZ|j;@~dEtTJf+!nNA&lLfn zpl}!oc14MN#cxSLU2{hf;@i{~_?^QMTdy4@OXtK?V^9~4AnyI4$)JXbRhSt+b9m2) zUbyT{;^otsemD6y`O4SCJR%15EAQ$XslZ+LO7Vt&0MCX)f&wFQPrfW5#R@9zAU#wH(55xXeF%;6K$KY+WXH6}z(crh0!HPB;#lk;&2_4CGmJM> z2V)p}qd0j_C%{u3(3;W=u7lz^UGxzupj})&A`OdO@fFlykRV=UTXWSN3fMGljmJ5F zplB^iIIVFLK~8R-^ID~v#Y_M_d6ifCAB9L%*?mdHopzZ)`lI_NX3PQlgJAFm>1r=; zzkg_<-xRXES**Ff7;~Hz+Py@?6s4#8jJ*(^jS=EM3>#(O8qZg2fh%6r{{X8}^qs!w zKV+{tfVX0-dZ1#aR4|UotFkGMk-Dc2i{D9fSZ|Td0ExWf;@hjtD*npQ zr{`PuFkk-CTy$by69%qVQuwV~ex@g{?3Tn8MR~L}92HiPFM`+NUBAaF3d=*{0*DGz zSu>W`Pcn*A87h$Wy>CpVJguApXEv*p%L>2`D+!f_mWoYOdw`~;EewOPA2VH1)8dz>BoAF^EMHDi@%=G zG7hUX_%`?4z*MCMTXSmO@q`U1(_YIjlIuqclY=Qtsw~ zanD&vp+*HZIK?|WM7hnX*?9}BV~)7g8-!syHM#_)U2hC*!V

rp4LrDC&v;j2zp& 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⪚ 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
$gJ+ zbbn(6-kO_Az+PU_4pqU}v^X*D%NIsIXWV3)5IP^Qp|8ixAxLd7b&Par{33Kq%)(Zq zDRPv8qBH>Yjwe#{F{>{2{2N9XUa?@8XhA{+U@XK@m;*()m-fC3^h-PsS3dPMo)P}S zd=P(PiZSbF>KK(S&Hc*pZEzpBVu_@q>QiDhb6@`e!F)O~Y#w5qf{`D|%MX$T$py*x zf^XQtis{KxybfB+1?F}}9Y3=HY3m2i#+0*vgiQnQ7`rCZFIVaSF7c#@snONLZVlPm z7cH(Uycmaqs<8&;SW5SZ%RQzUHN7Omo`S0}D_BvgT9+=6a_q(o>vX-fIw81H8XklN zP^Sl@=?S+lx);=%w4h+u65`m+o^aH5$;%yO4qD7?JvDG|IC@+%!Ntrsi@IWE-!VOI z1Fd(4LhC%v)nu-}5Z_Rg6?p2vbdvx}A$9S@Dy(wKw{?A{2R=EEi#;kHSZIS9qN1iL zxNABV<>0xEAoZtuS1z64=?*3(O8imMRImR4B3ugB;*plQE6hevtBUxj<+)EV*MbP) zxF7jDvk8up7~?aakWa*q zyG_|_UgT&QYKqDU%X4Px@zL!UoDc-iDD|hhAR$gq>mE(05!GGGddqstddvR+{L3$9Vkk(&5GLFA5HLk72gDEhWB&kSANv@8>|y@^ zp@p3XNi6>47)pDu<%H(+n71}<#k`khB3)f^>^EIiHvId#)Z=nbMThf z;`JJp!xGh=u>tD?6#TbYqOFUgtn{~eh1Q@^nOI7@gJ(;DzQhzA`gMmlc$Nhi>n{yJ zR|!*XL%vj_7)#9dyhAD}s6Md&0Qjz=3%OI8j{5eB;k%St>%0_*d7X4k+~CAmUG65J z%Jh|sfL03D3OF~@{{RT|5l5s+qG5BbxGpBZ1FEomz9E4V37wz*s1fXl6&go`=_v5k zD5_g6RBqf&zSVUHQMcL=zz#WOaEDoz(dj9sYwjd6q5*6ohu#l%y-8bQn>5V@Oz!6} zTiQ|^DD44;`QkFHZ>KW4gB;A)OixWrt_5|1ojlGw!qNioq&I=Lyt=iYGiO5OsCQJv zXhYLJ?G~f3l-%QRAm9H0#o~{|{{YT7cigvmAhd(tR_xfR-}U z0JQPpuumVA^ zF5ZJKw(;f>bB=Bm8uM{ORqGI66C}9_B^w4DPoNQ3YPiN_GN$##WtWkxa&?xVhRlG` zWo+0loHR3KKs%`X!3DMALGlN~`imM|p}Gl;bs5O&<4|YC>uh*vR*CLCXH!nC&T{WB zzyAQOVjC1-HCTJ5y?WC*W6W>86a9!-TcSTucn>a*U_hj^P(hb&8{AyH^n(>)>qgjW zXw`Hdb8;#`N-rff%3F=G(3O@-93+|DR{Ajfh@=CmfWgOPGyq*k gzhx4-)6Oe!n&?$ do - -- tempOver <- G.gegl_node_new_child root $ G.defaultOverOperation - -- let !scale = (fromIntegral resolution) / (fromIntegral dimension) - -- tempProps = - -- props $ do - -- prop "x" (((fromIntegral col) - 1) * scale :: Double) - -- prop "y" (((fromIntegral row) - 1) * scale :: Double) - -- prop "width" (scale :: Double) - -- prop "height" (scale :: Double) - -- prop "color" $ G.RGB 1 1 1 - -- tempRect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" - -- tempProps - -- G.gegl_node_connect_to tempRect "output" tempOver "aux" - -- let tempMap = M.fromList - -- [ ("over", tempOver) - -- , ("rect", tempRect) - -- ] - -- tempAProps = - -- map (\p -> ActorProperty - -- { apName = G.propertyName p - -- , apValue = G.propertyValue p - -- , apMapping = Just "rect" - -- }) tempProps - -- traceIO $ show index - -- return $ (index, Actor - -- { actorProperties = tempAProps - -- , actorNodes = tempMap - -- }) : acc - -- ) - -- ([] :: [((Int, Int), Actor String)]) - -- indices - -- :: IO [((Int, Int), Actor String)] - traceM "loading complete" - return UserData - { nodeGraph = myMap - , foreground = buffer - , keysDown = [] - , indices = indices - , liveIndices = [] - -- , cells = cells - -- , updateActors = [] - } - -loadList :: Affection UserData () -loadList = do - ud@UserData{..} <- getAffection - init <- foldM (\acc coord -> do - trig <- liftIO $ randomRIO (True, False) - if trig then return (coord : acc) else return acc - ) [] indices - putAffection ud - { liveIndices = init - } - -getNeighbors :: (Int, Int) -> Affection UserData [(Int, Int)] -getNeighbors (row, col) = - do - UserData{..} <- getAffection - mapM (\(pr, pc) -> - return (overflow(row + pr), overflow(col + pc)) - ) lcs - where - overflow x - | x < 1 = dimension - | x > dimension = 1 - | otherwise = x - -lcs :: [(Int, Int)] -lcs = - [ (-1, -1) - , (-1, 0) - , (-1, 1) - , (0, -1) - -- , (0, 0) - , (0, 1) - , (1, -1) - , (1, 0) - , (1, 1) - ] - -draw :: Affection UserData () -draw = do - traceM "drawing" - ad <- get - ud@UserData{..} <- getAffection - SDL.rendererDrawColor (windowRenderer ad) SDL.$= (SDL.V4 0 0 0 255) - SDL.clear (windowRenderer ad) - SDL.rendererDrawColor (windowRenderer ad) SDL.$= (SDL.V4 255 255 255 255) - MP.mapM_ (\(row, col) -> - let scale = floor (fromIntegral resolution / fromIntegral dimension) - in - SDL.fillRect - (windowRenderer ad) - (Just ( SDL.Rectangle - (SDL.P (SDL.V2 - (CInt $ fromIntegral $ (col - 1) * scale) - (CInt $ fromIntegral $ (row - 1) * scale) - )) - (SDL.V2 - (CInt $ fromIntegral $ scale) - (CInt $ fromIntegral $ scale) - ) - )) - ) liveIndices - -- render Nothing Nothing - -- let livingActors = catMaybes $ parMap rpar (\i -> lookup i cells) liveIndices - -- unless (null livingActors) $ do - -- liftIO $ G.gegl_node_link_many $ - -- parMap rpar (\a -> (actorNodes a) M.! "over") livingActors - -- _ <- liftIO $ G.gegl_node_link ((actorNodes $ last livingActors) M.! "over") - -- (nodeGraph M.! "nop") - -- return () - -- process (nodeGraph M.! "sink") - -- present (G.GeglRectangle 0 0 resolution resolution) foreground True - -- render - -- Nothing - -- Nothing - -- _ <- liftIO $ MP.mapM - -- (\a -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" - -- ) livingActors - -- putAffection ud - -- { updateActors = [] - -- } - -update :: Double -> Affection UserData () -update dt = do - traceM "updating" - - ud <- getAffection - elapsed <- getElapsedTime - - traceM $ show (1 / dt) ++ " FPS" - traceM $ show $ keysDown ud - - relevant <- getRelevant (liveIndices ud) - - new <- catMaybes <$> MP.mapM (\coord@(row, col) -> do - neighs <- - foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 <$> - getNeighbors coord - let ret - | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = - Just coord - | neighs == 3 = - Just coord - | otherwise = - Nothing - return ret - ) relevant - - ud2 <- getAffection - putAffection ud2 - { liveIndices = new - } - - mapM_ (\code -> - when (code == SDL.KeycodeR) loadList - ) (keysDown ud) - -getRelevant :: [(Int, Int)] -> Affection UserData [(Int, Int)] -getRelevant ls = - getRelevant' ls - where - getRelevant' xs = foldM (\acc x -> do - neighs <- getNeighbors x - let slice = (x : neighs) - rels = foldl (\a y -> - if y `elem` acc then a else y : a - ) [] slice - return (acc ++ rels) - ) [] xs - -handle :: SDL.EventPayload -> Affection UserData () -handle (SDL.KeyboardEvent dat) = - when (not (SDL.keyboardEventRepeat dat)) $ do - ud <- getAffection - if (SDL.keyboardEventKeyMotion dat == SDL.Pressed) - then - putAffection ud - { keysDown = - SDL.keysymKeycode (SDL.keyboardEventKeysym dat) : keysDown ud - } - else - putAffection ud - { keysDown = - delete (SDL.keysymKeycode $ SDL.keyboardEventKeysym dat) (keysDown ud) - } - -handle (SDL.WindowClosedEvent _) = do - traceM "seeya!" - quit - -handle _ = - return () - -clean :: UserData -> IO () -clean _ = return () diff --git a/examples/example05.hs b/examples/example05.hs deleted file mode 100644 index 9985d73..0000000 --- a/examples/example05.hs +++ /dev/null @@ -1,313 +0,0 @@ -{-# LANGUAGE RecordWildCards, BangPatterns #-} - -import Affection -import qualified SDL -import qualified GEGL as G -import qualified BABL as B -import qualified Data.Map.Strict as M -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS -import qualified Data.Matrix as X -import Data.List as L -import Data.Foldable (foldrM, foldlM) -import Data.Maybe (fromJust, catMaybes) --- import Data.Vector as V -import System.Random (randomRIO) -import Control.Monad as CM (when, unless) -import qualified Control.Monad.Parallel as MP -import Control.Parallel.Strategies -import Foreign.C.Types -import Foreign.Marshal.Utils - -import Debug.Trace - -dimension :: Int -dimension = 60 - -resolution :: Int -resolution = 600 - -main :: IO () -main = do - conf <- return AffectionConfig - { initComponents = All - , windowTitle = "Affection: example00" - , windowConfig = SDL.defaultWindow - { windowInitialSize = SDL.V2 - (CInt $ fromIntegral resolution) - (CInt $ fromIntegral resolution) - } - , canvasSize = Nothing - , initScreenMode = SDL.Windowed - , preLoop = loadList - , eventLoop = handle - , updateLoop = Main.update - , drawLoop = draw - , loadState = load - , cleanUp = clean - } - withAffection conf - -data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - , foreground :: G.GeglBuffer - , keysDown :: [SDL.Keycode] - , liveIndices :: HS.HashSet (Int, Int) - , indices :: [(Int, Int)] - , cells :: HM.HashMap (Int, Int) (Actor String) - , updateActors :: [Actor String] - } - -load :: IO UserData -load = do - traceM "loading" - root <- G.gegl_node_new - traceM "new root node" - let bgProps = props $ do - prop "x" (0 :: Double) - prop "y" (0 :: Double) - prop "width" (fromIntegral resolution :: Double) - prop "height" (fromIntegral resolution :: Double) - prop "color" $ G.RGB 0 0 0 - bg <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" bgProps - traceM "background" - over <- G.gegl_node_new_child root G.defaultOverOperation - traceM "over" - buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 resolution resolution) =<< - B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ - props $ - prop "buffer" buffer - traceM "buffer-sink" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ - props $ do - prop "width" (fromIntegral resolution :: Double) - prop "height" (fromIntegral resolution :: Double) - G.gegl_node_link_many [bg, over, crop, sink] - nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] - _ <- G.gegl_node_connect_to nop "output" over "aux" - traceM "connections made" - myMap <- return $ M.fromList - [ ("root" , root) - , ("over" , over) - , ("background" , bg) - , ("sink" , sink) - , ("nop" , nop) - , ("crop" , crop) - ] - traceM "building matrix" - let !emptyMatrix = X.matrix dimension dimension (const False) - !indices = [(row, col) | row <- [1..dimension], col <- [1..dimension]] - traceM "building cells" - cells <- foldrM (\index@(row, col) acc -> do - tempOver <- G.gegl_node_new_child root $ G.defaultOverOperation - let !scale = (fromIntegral resolution) / (fromIntegral dimension) - tempProps = - props $ do - prop "x" (((fromIntegral col) - 1) * scale :: Double) - prop "y" (((fromIntegral row) - 1) * scale :: Double) - prop "width" (scale :: Double) - prop "height" (scale :: Double) - prop "color" $ G.RGB 1 1 1 - tempRect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" - tempProps - G.gegl_node_connect_to tempRect "output" tempOver "aux" - let tempMap = M.fromList - [ ("over", tempOver) - , ("rect", tempRect) - ] - tempAProps = - map (\p -> ActorProperty - { apName = G.propertyName p - , apValue = G.propertyValue p - , apMapping = Just ("rect", "color") - }) tempProps - traceIO $ show index - return $ HM.insert index Actor - { actorProperties = tempAProps - , actorNodes = tempMap - } - acc - ) - HM.empty - indices - traceM "loading complete" - return UserData - { nodeGraph = myMap - , foreground = buffer - , keysDown = [] - , indices = indices - , liveIndices = HS.empty - , cells = cells - , updateActors = [] - } - -loadList :: Affection UserData () -loadList = do - ud@UserData{..} <- getAffection - -- init <- foldrM (\coord acc -> do - init <- foldlM (\acc coord -> do - trig <- liftIO $ randomRIO (True, False) - if trig then return (HS.insert coord acc) else return acc - ) HS.empty indices - putAffection ud - { liveIndices = init - } - -getNeighbors :: (Int, Int) -> [(Int, Int)] -getNeighbors (row, col) = - map (\(pr, pc) -> (overflow(row + pr), overflow(col + pc))) lcs - where - overflow x - | x < 1 = dimension - | x > dimension = 1 - | otherwise = x - -lcs :: [(Int, Int)] -lcs = - [ (-1, -1) - , (-1, 0) - , (-1, 1) - , (0, -1) - -- , (0, 0) - , (0, 1) - , (1, -1) - , (1, 0) - , (1, 1) - ] - -draw :: Affection UserData () -draw = do - traceM "drawing" - ud@UserData{..} <- getAffection - let livingActors = parMap rpar (cells HM.!) $ HS.toList liveIndices - unless (null livingActors) $ do - liftIO $ G.gegl_node_link_many $ - parMap rpar (\a -> (actorNodes a) M.! "over") livingActors - _ <- liftIO $ G.gegl_node_link ((actorNodes $ last livingActors) M.! "over") - (nodeGraph M.! "nop") - return () - process (nodeGraph M.! "sink") - present (G.GeglRectangle 0 0 resolution resolution) foreground True - render - Nothing - Nothing - -- _ <- liftIO $ MP.mapM - -- (\a -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" - -- ) livingActors - putAffection ud - { updateActors = [] - } - -update :: Double -> Affection UserData () -update dt = do - traceM "updating" - - ud <- getAffection - elapsed <- getElapsedTime - - traceM $ show (1 / dt) ++ " FPS" - traceM $ show $ keysDown ud - - relevant <- getRelevant (liveIndices ud) - - -- let new = HS.fromList $ catMaybes $ parMap rpar (\coord@(row, col) -> - -- let neighs = - -- foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 $ - -- getNeighbors coord - -- ret - -- | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = - -- Just coord - -- | neighs == 3 = - -- Just coord - -- | otherwise = - -- Nothing - -- in ret - -- ) (HS.toList relevant) - - new <- foldrM (\coord@(row, col) acc -> do - -- new <- foldlM (\acc coord@(row, col) -> do - let !neighs = sum $ - map (\n -> if n `elem` liveIndices ud then 1 else 0)$ - getNeighbors coord - ret - | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = - HS.insert coord acc - | neighs == 3 = - HS.insert coord acc - | otherwise = - acc - return ret - ) HS.empty relevant - - -- new <- catMaybes <$> MP.mapM (\coord@(row, col) -> do - -- neighs <- - -- foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 <$> - -- getNeighbors coord - -- let ret - -- | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = - -- Just coord - -- | neighs == 3 = - -- Just coord - -- | otherwise = - -- Nothing - -- return ret - -- ) relevant - - ud2 <- getAffection - putAffection ud2 - { liveIndices = new - } - - mapM_ (\code -> - when (code == SDL.KeycodeR) loadList - ) (keysDown ud) - -getRelevant :: HS.HashSet (Int, Int) -> Affection UserData (HS.HashSet (Int, Int)) -getRelevant ls = do - getRelevant' ls - where - getRelevant' xs = foldlM (\acc x -> - let neighs = getNeighbors x - slice = (x : neighs) - rels = foldr (\y a -> - -- rels = foldl (\a y -> - if y `elem` acc then a else HS.insert y a - ) HS.empty slice - in return (acc `HS.union` rels) - ) HS.empty xs - -handle :: SDL.EventPayload -> Affection UserData () -handle (SDL.KeyboardEvent dat) = - when (not (SDL.keyboardEventRepeat dat)) $ do - ud <- getAffection - if (SDL.keyboardEventKeyMotion dat == SDL.Pressed) - then - putAffection ud - { keysDown = - SDL.keysymKeycode (SDL.keyboardEventKeysym dat) : keysDown ud - } - else - putAffection ud - { keysDown = - delete (SDL.keysymKeycode $ SDL.keyboardEventKeysym dat) (keysDown ud) - } - -handle (SDL.WindowClosedEvent _) = do - traceM "seeya!" - quit - -handle _ = - return () - -clean :: UserData -> IO () -clean _ = return () - -parHSMap f = (`using` parTraversable rpar) . HM.map f - --- catHSMaybe = --- foldr (\x acc -> --- case x of --- Just a -> a `HS.insert` acc --- Nothing -> acc --- ) HS.empty From 8a5370c857ba30aa4bfad629b0202a5f656552b1 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 28 Dec 2017 16:56:49 +0100 Subject: [PATCH 200/261] new examples --- examples/example00/Main.hs | 145 ++++++++++++++++++++++++++ examples/example01/Main.hs | 201 ++++++++++++++++++++++++++++++++++++ examples/example01/Types.hs | 75 ++++++++++++++ 3 files changed, 421 insertions(+) create mode 100644 examples/example00/Main.hs create mode 100644 examples/example01/Main.hs create mode 100644 examples/example01/Types.hs diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs new file mode 100644 index 0000000..c9090b8 --- /dev/null +++ b/examples/example00/Main.hs @@ -0,0 +1,145 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeFamilies #-} + +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 ())]) + +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 + +instance Participant Window StateData where + type Mesg Window StateData = WindowMessage + + partSubscribers (Window t) = do + subTups <- liftIO $ readTVarIO t + return $ map snd subTups + + partSubscribe (Window t) = generalSubscribe t + + partUnSubscribe (Window t) uuid = + liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) + +instance SDLSubsystem Window StateData where + consumeSDLEvents = consumeSDLWindowEvents + +instance Participant Mouse StateData where + type Mesg Mouse StateData = MouseMessage + + partSubscribers (Mouse t) = do + subTups <- liftIO $ readTVarIO t + return $ map snd subTups + + partSubscribe (Mouse t) = generalSubscribe t + + partUnSubscribe (Mouse t) uuid = + liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) + +instance SDLSubsystem Mouse StateData where + consumeSDLEvents = consumeSDLMouseEvents + +instance Participant Keyboard StateData where + type Mesg Keyboard StateData = KeyboardMessage + + partSubscribers (Keyboard t) = do + subTups <- liftIO $ readTVarIO t + return $ map snd subTups + + partSubscribe (Keyboard t) = generalSubscribe t + + partUnSubscribe (Keyboard t) 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 () diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs new file mode 100644 index 0000000..28a1ec7 --- /dev/null +++ b/examples/example01/Main.hs @@ -0,0 +1,201 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ForeignFunctionInterface #-} + +import Affection as A +import qualified SDL + +import Control.Concurrent.STM +import Control.Monad.IO.Class (liftIO) +import Control.Monad (when) + +import Data.Matrix as M +import qualified Data.Set as S + +import System.Random (randomRIO) + +import NanoVG + +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: example00" + , windowConfig = SDL.defaultWindow + { SDL.windowOpenGL = Just SDL.defaultOpenGL + { SDL.glProfile = SDL.Core SDL.Normal 3 3 + } + , SDL.windowInitialSize = SDL.V2 600 600 + } + , 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 + , nano = nanoCtx + } + +pre :: Affection UserData () +pre = do + sd <- getAffection + _ <- partSubscribe (subKeyboard $ subsystems sd) exitOnEsc + _ <- partSubscribe (subKeyboard $ subsystems sd) reloadOnR + _ <- partSubscribe (subWindow $ subsystems sd) exitOnWindowClose + return () + +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 -> do + ud <- getAffection + randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599] + let fullMatrix = fromList 60 60 randList + nState <- liftIO $ load + putAffection ud + { lifeMat = fullMatrix + } + _ -> return () + +exitOnWindowClose :: WindowMessage -> Affection UserData () +exitOnWindowClose wm = + case wm of + MsgWindowClose _ _ -> do + liftIO $ logIO A.Debug "I heard another one..." + quit + _ -> 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 + -- when (lastUpdate ud < floor now) $ do + liftIO $ logIO A.Debug "stepping life" + newList <- mapM (\coord -> do + let x = (coord `div` 60) + 1 + y = (coord `mod` 60) + 1 + subm + | x == 1 && y == 1 = + (submatrix 60 60 60 60 (lifeMat ud) <-> submatrix 1 2 60 60 (lifeMat ud)) + <|> + (submatrix 60 60 1 2 (lifeMat ud) <-> submatrix 1 2 1 2 (lifeMat ud)) + | x == 1 && y == 60 = + (submatrix 60 60 59 60 (lifeMat ud) <-> submatrix 1 2 59 60 (lifeMat ud)) + <|> + (submatrix 60 60 1 1 (lifeMat ud) <-> submatrix 1 2 1 1 (lifeMat ud)) + | x == 60 && y == 1 = + (submatrix 59 60 60 60 (lifeMat ud) <-> submatrix 1 1 60 60 (lifeMat ud)) + <|> + (submatrix 59 60 1 2 (lifeMat ud) <-> submatrix 1 1 1 2 (lifeMat ud)) + | x == 60 && y == 60 = + (submatrix 59 60 59 60 (lifeMat ud) <-> submatrix 1 1 59 60 (lifeMat ud)) + <|> + (submatrix 59 60 1 1 (lifeMat ud) <-> submatrix 1 1 1 1 (lifeMat ud)) + | x == 1 = + (submatrix 60 60 (y - 1) (y + 1) (lifeMat ud)) + <-> + (submatrix 1 2 (y - 1) (y + 1) (lifeMat ud)) + | y == 1 = + (submatrix (x - 1) (x + 1) 60 60 (lifeMat ud)) + <|> + (submatrix (x - 1) (x + 1) 1 2 (lifeMat ud)) + | x == 60 = + (submatrix 1 1 (y - 1) (y + 1) (lifeMat ud)) + <-> + (submatrix 59 60 (y - 1) (y + 1) (lifeMat ud)) + | y == 60 = + (submatrix (x - 1) (x + 1) 1 1 (lifeMat ud)) + <|> + (submatrix (x -1 ) (x + 1) 59 60 (lifeMat ud)) + | otherwise = + submatrix (x - 1) (x + 1) (y - 1) (y + 1) (lifeMat ud) + life = countLife subm + if (lifeMat ud) M.! (x, y) == 1 + then if life == 2 || life == 3 + then return 1 + else return 0 + else if life == 3 + then return 1 + else return 0 + ) [0..3599] + let newM = fromList 60 60 newList + putAffection ud + { lifeMat = newM + } + +countLife :: Matrix Word -> Word +countLife mat = res - (mat M.! (2, 2)) + where + res = foldr (\a acc -> a + acc) 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 `div` 60 + y = coord `mod` 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 do + fillColor ctx (rgba 255 255 255 255) + else do + fillColor ctx (rgba 0 0 0 255) + fill ctx + ) [0..3599] + restore (nano ud) + endFrame (nano ud) + +clean _ = return () diff --git a/examples/example01/Types.hs b/examples/example01/Types.hs new file mode 100644 index 0000000..615651e --- /dev/null +++ b/examples/example01/Types.hs @@ -0,0 +1,75 @@ +{-# 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 + , subsystems :: Subsystems + , nano :: Context + } + +data Subsystems = Subsystems + { subWindow :: Window + , subKeyboard :: Keyboard + } + +newtype Window = Window (TVar [(UUID, WindowMessage -> Affection UserData ())]) + +newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection UserData ())]) + +instance Participant Window UserData where + type Mesg 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 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 From a1974fb2f38c71170aad2943cb204f684c40ba7c Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 28 Dec 2017 16:57:11 +0100 Subject: [PATCH 201/261] add new examples in cabal --- affection.cabal | 136 ++++++++---------------------------------------- 1 file changed, 23 insertions(+), 113 deletions(-) diff --git a/affection.cabal b/affection.cabal index 03853c7..18ce042 100644 --- a/affection.cabal +++ b/affection.cabal @@ -116,8 +116,8 @@ library -- 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 +129,24 @@ executable example00 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 + else + buildable: False From 93140bd713e636492fb8bc10fb405254c8d688ac Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 28 Dec 2017 16:57:21 +0100 Subject: [PATCH 202/261] dusting off --- src/Affection/MessageBus/Class.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 9be0fb4..e079a22 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -63,5 +63,3 @@ class (Message (Mesg prt us), Show (Mesg prt us)) => Participant prt us where -- | Helper function to generate new 'UUID's genUUID :: Affection us UUID genUUID = liftIO nextRandom - --- data MsgId msg = (Message msg, Show msg) => MsgId UUID msg From e83a36910ab76c9f0f44cb493d24487d7b41e82f Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 29 Dec 2017 15:03:21 +0100 Subject: [PATCH 203/261] game of life works now --- affection.cabal | 1 + examples/example01/Main.hs | 198 +++++++++++++++++++++++------------- examples/example01/Types.hs | 3 + 3 files changed, 134 insertions(+), 68 deletions(-) diff --git a/affection.cabal b/affection.cabal index 18ce042..64d1d7f 100644 --- a/affection.cabal +++ b/affection.cabal @@ -148,5 +148,6 @@ executable example01 , linear , matrix , nanovg + , deepseq else buildable: False diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 28a1ec7..ae9fa98 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -3,18 +3,23 @@ {-# 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 +import NanoVG hiding (V2(..)) + +import Linear import Foreign.C.Types (CInt(..)) @@ -30,12 +35,13 @@ main = do logIO A.Debug "Starting" let conf = AffectionConfig { initComponents = All - , windowTitle = "affection: example00" + , 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 @@ -66,7 +72,10 @@ load = do (Window empty1) (Keyboard empty3) , lifeMat = fullMatrix + , foodMat = fromList 60 60 (repeat 10) + , timeMat = M.zero 60 60 , nano = nanoCtx + , lastUpdate = 0 } pre :: Affection UserData () @@ -74,8 +83,13 @@ pre = do sd <- getAffection _ <- partSubscribe (subKeyboard $ subsystems sd) exitOnEsc _ <- partSubscribe (subKeyboard $ subsystems sd) reloadOnR + _ <- partSubscribe (subKeyboard $ subsystems sd) showFPS _ <- partSubscribe (subWindow $ subsystems sd) exitOnWindowClose - return () + _ <- partSubscribe (subWindow $ subsystems sd) windowResize + now <- getElapsedTime + putAffection sd + { lastUpdate = floor now + } exitOnEsc :: KeyboardMessage -> Affection UserData () exitOnEsc (MsgKeyboardEvent _ _ _ _ sym) = @@ -88,14 +102,28 @@ exitOnEsc (MsgKeyboardEvent _ _ _ _ sym) = reloadOnR :: KeyboardMessage -> Affection UserData () reloadOnR (MsgKeyboardEvent _ _ _ _ sym) = case SDL.keysymKeycode sym of - SDL.KeycodeR -> do - ud <- getAffection - randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599] - let fullMatrix = fromList 60 60 randList - nState <- liftIO $ load - putAffection ud - { lifeMat = fullMatrix - } + 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 () @@ -106,6 +134,15 @@ exitOnWindowClose wm = 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 @@ -115,64 +152,89 @@ handle es = do update :: Double -> Affection UserData () update _ = do ud <- getAffection - -- when (lastUpdate ud < floor now) $ do - liftIO $ logIO A.Debug "stepping life" - newList <- mapM (\coord -> do - let x = (coord `div` 60) + 1 - y = (coord `mod` 60) + 1 - subm - | x == 1 && y == 1 = - (submatrix 60 60 60 60 (lifeMat ud) <-> submatrix 1 2 60 60 (lifeMat ud)) - <|> - (submatrix 60 60 1 2 (lifeMat ud) <-> submatrix 1 2 1 2 (lifeMat ud)) - | x == 1 && y == 60 = - (submatrix 60 60 59 60 (lifeMat ud) <-> submatrix 1 2 59 60 (lifeMat ud)) - <|> - (submatrix 60 60 1 1 (lifeMat ud) <-> submatrix 1 2 1 1 (lifeMat ud)) - | x == 60 && y == 1 = - (submatrix 59 60 60 60 (lifeMat ud) <-> submatrix 1 1 60 60 (lifeMat ud)) - <|> - (submatrix 59 60 1 2 (lifeMat ud) <-> submatrix 1 1 1 2 (lifeMat ud)) - | x == 60 && y == 60 = - (submatrix 59 60 59 60 (lifeMat ud) <-> submatrix 1 1 59 60 (lifeMat ud)) - <|> - (submatrix 59 60 1 1 (lifeMat ud) <-> submatrix 1 1 1 1 (lifeMat ud)) - | x == 1 = - (submatrix 60 60 (y - 1) (y + 1) (lifeMat ud)) - <-> - (submatrix 1 2 (y - 1) (y + 1) (lifeMat ud)) - | y == 1 = - (submatrix (x - 1) (x + 1) 60 60 (lifeMat ud)) - <|> - (submatrix (x - 1) (x + 1) 1 2 (lifeMat ud)) - | x == 60 = - (submatrix 1 1 (y - 1) (y + 1) (lifeMat ud)) - <-> - (submatrix 59 60 (y - 1) (y + 1) (lifeMat ud)) - | y == 60 = - (submatrix (x - 1) (x + 1) 1 1 (lifeMat ud)) - <|> - (submatrix (x -1 ) (x + 1) 59 60 (lifeMat ud)) - | otherwise = - submatrix (x - 1) (x + 1) (y - 1) (y + 1) (lifeMat ud) - life = countLife subm - if (lifeMat ud) M.! (x, y) == 1 - then if life == 2 || life == 3 - then return 1 - else return 0 - else if life == 3 - then return 1 - else return 0 - ) [0..3599] - let newM = fromList 60 60 newList - putAffection ud - { lifeMat = newM - } + -- now <- getElapsedTime + when (True) $ do + 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 + then if 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 return (0, foodMat ud M.! (y, x), 1) + else if life == 3 + then if 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 + ) + 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 (\a acc -> a + acc) 0 mat + res = foldr (\a acc -> acc + a) 0 mat draw :: Affection UserData () draw = do @@ -181,8 +243,8 @@ draw = do beginFrame (nano ud) 600 600 1 save (nano ud) mapM_ (\coord -> do - let x = coord `div` 60 - y = coord `mod` 60 + 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 @@ -192,7 +254,7 @@ draw = do then do fillColor ctx (rgba 255 255 255 255) else do - fillColor ctx (rgba 0 0 0 255) + fillColor ctx (rgba 0 (fromIntegral $ 25 * (foodMat ud M.! (x+1, y+1))) 0 255) fill ctx ) [0..3599] restore (nano ud) diff --git a/examples/example01/Types.hs b/examples/example01/Types.hs index 615651e..2ce7855 100644 --- a/examples/example01/Types.hs +++ b/examples/example01/Types.hs @@ -14,8 +14,11 @@ 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 From 90fa5ccef83da3042b675cc68d10e1107d9b3e6f Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 29 Dec 2017 20:31:52 +0100 Subject: [PATCH 204/261] indentation fix --- examples/example01/Types.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example01/Types.hs b/examples/example01/Types.hs index 2ce7855..9fea586 100644 --- a/examples/example01/Types.hs +++ b/examples/example01/Types.hs @@ -72,7 +72,7 @@ generalUnSubscribe -> 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 + liftIO $ atomically $ modifyTVar' t (filter (`filterMsg` uuid)) + where + filterMsg :: (UUID, msg -> Affection UserData ()) -> UUID -> Bool + filterMsg (u, _) p = u /= p From 3ef0af36fb1e71268a99252360f7744267dc3783 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 31 Dec 2017 04:14:06 +0100 Subject: [PATCH 205/261] remove exports --- src/Affection/MessageBus/Class.hs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index e079a22..b53ffcc 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -3,12 +3,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ExistentialQuantification #-} -module Affection.MessageBus.Class - ( Participant(..) - , genUUID - , UUID - -- , MsgId(..) - ) where +module Affection.MessageBus.Class where import Affection.MessageBus.Message import Affection.Types From 2ff97b7c66f6e7ee72ef5bb0496cc34cd2ba2f94 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 31 Dec 2017 04:20:22 +0100 Subject: [PATCH 206/261] readd exports --- src/Affection/MessageBus/Class.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index b53ffcc..93d826e 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -3,7 +3,11 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ExistentialQuantification #-} -module Affection.MessageBus.Class where +module Affection.MessageBus.Class + ( Participant(..) + , genUUID + , UUID + ) where import Affection.MessageBus.Message import Affection.Types From 276966f80d54f93135aeb0b721cd7670b6672645 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 9 Jan 2018 18:40:54 +0100 Subject: [PATCH 207/261] now with joystick support --- affection.cabal | 5 +- examples/example00/Main.hs | 96 +++++++++++++------ src/Affection/MessageBus/Message.hs | 1 + .../MessageBus/Message/JoystickMessage.hs | 60 ++++++++++++ src/Affection/Subsystems.hs | 1 + src/Affection/Subsystems/AffectionJoystick.hs | 94 ++++++++++++++++++ 6 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 src/Affection/MessageBus/Message/JoystickMessage.hs create mode 100644 src/Affection/Subsystems/AffectionJoystick.hs diff --git a/affection.cabal b/affection.cabal index 64d1d7f..ecd9b6e 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.8 +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. @@ -78,11 +78,13 @@ library , 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. @@ -112,6 +114,7 @@ library , OpenGL , stm , uuid + , vector -- This example shows the message system. only makes sense when compiling with -- verbose flag. diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs index c9090b8..ae1f9e0 100644 --- a/examples/example00/Main.hs +++ b/examples/example00/Main.hs @@ -6,20 +6,33 @@ import qualified SDL import Control.Concurrent.STM import Control.Monad.IO.Class (liftIO) +import Control.Monad (when) -newtype StateData = StateData +import Data.Maybe (isJust, fromJust) + +data StateData = StateData { sdSubs :: Subsystems + , sdJoys :: [SDL.Joystick] } data Subsystems = Subsystems { subWindow :: Window , subMouse :: Mouse , subKeyboard :: Keyboard + , subJoystick :: 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 ())] @@ -30,17 +43,21 @@ generalSubscribe t funct = do 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 Window StateData where type Mesg Window StateData = WindowMessage - partSubscribers (Window t) = do - subTups <- liftIO $ readTVarIO t - return $ map snd subTups + partSubscribers (Window t) = generalSubscribers t partSubscribe (Window t) = generalSubscribe t - partUnSubscribe (Window t) uuid = - liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) + partUnSubscribe (Window t) = generalUnSubscribe t instance SDLSubsystem Window StateData where consumeSDLEvents = consumeSDLWindowEvents @@ -48,14 +65,11 @@ instance SDLSubsystem Window StateData where instance Participant Mouse StateData where type Mesg Mouse StateData = MouseMessage - partSubscribers (Mouse t) = do - subTups <- liftIO $ readTVarIO t - return $ map snd subTups + partSubscribers (Mouse t) = generalSubscribers t partSubscribe (Mouse t) = generalSubscribe t - partUnSubscribe (Mouse t) uuid = - liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) + partUnSubscribe (Mouse t) = generalUnSubscribe t instance SDLSubsystem Mouse StateData where consumeSDLEvents = consumeSDLMouseEvents @@ -63,18 +77,27 @@ instance SDLSubsystem Mouse StateData where instance Participant Keyboard StateData where type Mesg Keyboard StateData = KeyboardMessage - partSubscribers (Keyboard t) = do - subTups <- liftIO $ readTVarIO t - return $ map snd subTups + partSubscribers (Keyboard t) = generalSubscribers t partSubscribe (Keyboard t) = generalSubscribe t - partUnSubscribe (Keyboard t) uuid = - liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) + 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" @@ -98,20 +121,21 @@ main = do 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) +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 () @@ -130,12 +154,28 @@ exitOnWindowClose wm = 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) <- sdSubs <$> getAffection - _ <- consumeSDLEvents a es - _ <- consumeSDLEvents b es - _ <- consumeSDLEvents c es + (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 return () update _ = return () diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index 0af45ec..abf0f0c 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -6,3 +6,4 @@ import Affection.MessageBus.Message.Class as M 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 diff --git a/src/Affection/MessageBus/Message/JoystickMessage.hs b/src/Affection/MessageBus/Message/JoystickMessage.hs new file mode 100644 index 0000000..efb3311 --- /dev/null +++ b/src/Affection/MessageBus/Message/JoystickMessage.hs @@ -0,0 +1,60 @@ +module Affection.MessageBus.Message.JoystickMessage + ( JoystickMessage(..) + -- | SDL exports + , SDL.V2 + , 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(..)) + +data JoystickMessage + = MsgJoystickAxis + { msgJAWhen :: Double + , msgJAWhich :: Int32 + , msgJAAxis :: Word8 + , msgJAValue :: Int16 + } + | MsgJoystickBall + { msgJBWhen :: Double + , msgJBWhich :: Int32 + , msgJBBall :: Word8 + , msgJBRelMotion :: SDL.V2 Int16 + } + | MsgJoystickHat + { msgJHWhen :: Double + , msgJHWhich :: Int32 + , msgJHHat :: Word8 + , msgJHPosition :: SDL.JoyHatPosition + } + | MsgJoystickButton + { msgJBWhen :: Double + , msgJBWhich :: Int32 + , msgJBButton :: Word8 + , msgJBState :: SDL.JoyButtonState + } + | MsgJoystickDevice + { msgJDWhen :: Double + , msgJDWhich :: Int32 + , msgJDConnection :: SDL.JoyDeviceConnection + } + 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 diff --git a/src/Affection/Subsystems.hs b/src/Affection/Subsystems.hs index ce9929f..e48fa3e 100644 --- a/src/Affection/Subsystems.hs +++ b/src/Affection/Subsystems.hs @@ -6,3 +6,4 @@ import Affection.Subsystems.Class as S import Affection.Subsystems.AffectionKeyboard as S import Affection.Subsystems.AffectionWindow as S import Affection.Subsystems.AffectionMouse as S +import Affection.Subsystems.AffectionJoystick as S diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs new file mode 100644 index 0000000..9951d1b --- /dev/null +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -0,0 +1,94 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +module Affection.Subsystems.AffectionJoystick where + +import Affection.MessageBus +import Affection.Subsystems.Class +import Affection.Types +import Affection.Util +import Affection.Logging + +import Control.Monad (filterM) +import Control.Monad.IO.Class (liftIO) +import Control.Concurrent.STM + +import qualified Data.Vector as V + +import Foreign.C.Types (CInt(..)) + +import qualified SDL + +consumeSDLJoystickEvents + :: forall am us. (Participant am us, Mesg am us ~ JoystickMessage) + => am + -> [SDL.EventPayload] + -> Affection us [SDL.EventPayload] +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) + +joystickAutoConnect :: JoystickMessage -> Affection us (Maybe SDL.Joystick) +joystickAutoConnect (MsgJoystickDevice _ which SDL.JoyDeviceAdded) = liftIO $ do + [descr] <- V.toList <$> + (return . V.filter (\(SDL.JoystickDevice _ id) -> id == CInt which) + =<< SDL.availableJoysticks) + logIO Verbose $ "Connecting Joystick " ++ show which + Just <$> SDL.openJoystick descr +joystickAutoConnect _ = return Nothing + +joystickAutoDisconnect :: [SDL.Joystick] -> JoystickMessage -> Affection us [SDL.Joystick] +joystickAutoDisconnect js (MsgJoystickDevice _ which SDL.JoyDeviceRemoved) = + liftIO $ do + [disc] <- filterM (\j -> return . (which ==) =<< SDL.getJoystickID j) js + logIO Verbose $ "Disconnecting Joystick " ++ show which + SDL.closeJoystick disc + filterM (\j -> return . (which /=) =<< SDL.getJoystickID j) js +joystickAutoDisconnect js _ = return js From 6b1e7b557a11f6bafda5af10622dbf8584871ac9 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 10 Jan 2018 16:58:23 +0100 Subject: [PATCH 208/261] documentation --- src/Affection/Subsystems/AffectionJoystick.hs | 8 +++++--- src/Affection/Subsystems/AffectionKeyboard.hs | 8 +++++--- src/Affection/Subsystems/AffectionMouse.hs | 8 +++++--- src/Affection/Subsystems/AffectionWindow.hs | 8 +++++--- src/Affection/Subsystems/Class.hs | 3 +++ 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index 9951d1b..30d9578 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -20,11 +20,13 @@ 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 - -> [SDL.EventPayload] - -> Affection us [SDL.EventPayload] + => am -- ^ The message system participant + -> [SDL.EventPayload] -- ^ Incoming events + -> Affection us [SDL.EventPayload] -- ^ Leftover SDL events consumeSDLJoystickEvents am = doConsume where doConsume diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index d73ec1e..9d23df9 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -14,11 +14,13 @@ 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 :: forall ak us. (Participant ak us, Mesg ak us ~ KeyboardMessage) - => ak - -> [SDL.EventPayload] - -> Affection us [SDL.EventPayload] + => ak -- ^ The message system participant + -> [SDL.EventPayload] -- ^ Incoming events + -> Affection us [SDL.EventPayload] -- ^ Leftover SDL Events consumeSDLKeyboardEvents ak = doConsume where doConsume [] = return [] diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index 7ff73c8..b1ede2c 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -16,11 +16,13 @@ import Linear.Affine (unP) 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) - => am - -> [SDL.EventPayload] - -> Affection us [SDL.EventPayload] + => am -- ^ The message system participant + -> [SDL.EventPayload] -- ^ Incoming events + -> Affection us [SDL.EventPayload] -- ^ Leftover SDL events consumeSDLMouseEvents am = doConsume where doConsume diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 6e28ff6..e2e3bd5 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -16,11 +16,13 @@ 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 :: forall aw us. (Participant aw us, Mesg aw us ~ WindowMessage) - => aw - -> [SDL.EventPayload] - -> Affection us [SDL.EventPayload] + => aw -- ^ The message system participant + -> [SDL.EventPayload] -- ^ Incoming events + -> Affection us [SDL.EventPayload] -- ^ Leftover SDL events consumeSDLWindowEvents aw = doConsume where doConsume diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index 7c52f8e..4509332 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -7,5 +7,8 @@ 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] From 8f662cf6fa0278b2b1b3d7af3d5950af6eb11fc4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 12 Jan 2018 22:08:23 +0100 Subject: [PATCH 209/261] fixing viewport and joysticks --- src/Affection.hs | 2 ++ src/Affection/Subsystems/AffectionJoystick.hs | 13 ++++++--- src/Affection/Util.hs | 27 ++++++++++++++++--- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index aa842a5..61235fe 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -4,6 +4,7 @@ module Affection ( withAffection , get , put + , liftIO , module A ) where @@ -14,6 +15,7 @@ import System.Clock import Control.Monad.Loops import Control.Monad.State +import Control.Monad.IO.Class (liftIO) import Foreign.C.Types (CInt(..)) diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index 30d9578..64ad018 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -82,15 +82,20 @@ joystickAutoConnect (MsgJoystickDevice _ which SDL.JoyDeviceAdded) = liftIO $ do [descr] <- V.toList <$> (return . V.filter (\(SDL.JoystickDevice _ id) -> id == CInt which) =<< SDL.availableJoysticks) - logIO Verbose $ "Connecting Joystick " ++ show which + logIO Verbose $ "Connecting Joystick " ++ show which ++ " " ++ show descr Just <$> SDL.openJoystick descr joystickAutoConnect _ = return Nothing joystickAutoDisconnect :: [SDL.Joystick] -> JoystickMessage -> Affection us [SDL.Joystick] joystickAutoDisconnect js (MsgJoystickDevice _ which SDL.JoyDeviceRemoved) = liftIO $ do - [disc] <- filterM (\j -> return . (which ==) =<< SDL.getJoystickID j) js + joyIds <- mapM SDL.getJoystickID js + logIO Verbose $ "These are the Joysticks connected: " ++ show joyIds + [d] <- filterM (\j -> return . (which ==) =<< SDL.getJoystickID j) js + logIO Verbose $ "disconnected joysticks: " ++ show d logIO Verbose $ "Disconnecting Joystick " ++ show which - SDL.closeJoystick disc - filterM (\j -> return . (which /=) =<< SDL.getJoystickID j) js + SDL.closeJoystick d + njoys <- filterM (\j -> return $ d /= j) js + logIO Verbose $ "returning joysticks: " ++ show njoys + return njoys joystickAutoDisconnect js _ = return js diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 879aa3b..16fbbf8 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -1,10 +1,12 @@ -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 @@ -63,10 +65,29 @@ toggleScreen = do 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 () From 39eaf0eb91266181d590621ca64e91363a82755a Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 18 Feb 2018 04:30:53 +0100 Subject: [PATCH 210/261] adding mouseButtonEventMotion --- src/Affection/MessageBus/Message/MouseMessage.hs | 3 ++- src/Affection/Subsystems/AffectionMouse.hs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index 5833ae1..1ed6300 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -21,6 +21,7 @@ data MouseMessage | MsgMouseButton { msgMBWhen :: Double , msgMBWindow :: Maybe SDL.Window + , msgMBMotion :: SDL.InputMotion , msgMBWhich :: SDL.MouseDevice , msgMBButton :: SDL.MouseButton , msgMBClicks :: Word8 @@ -37,5 +38,5 @@ data MouseMessage instance Message MouseMessage where msgTime (MsgMouseMotion t _ _ _ _ _) = t - msgTime (MsgMouseButton t _ _ _ _ _) = t + msgTime (MsgMouseButton t _ _ _ _ _ _) = t msgTime (MsgMouseWheel t _ _ _ _) = t diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index b1ede2c..ace9010 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -46,6 +46,7 @@ consumeSDLMouseEvents am = doConsume partEmit am (MsgMouseButton ts (SDL.mouseButtonEventWindow dat) + (SDL.mouseButtonEventMotion dat) (SDL.mouseButtonEventWhich dat) (SDL.mouseButtonEventButton dat) (SDL.mouseButtonEventClicks dat) From 0f2a7a549b99f5c34009b4b8edd9f47cc5cd565f Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 9 Mar 2018 17:26:59 +0100 Subject: [PATCH 211/261] logo! --- README.md | 2 +- affection.svg | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 affection.svg diff --git a/README.md b/README.md index e86b62d..4664f5f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Affection +# ![Affection logo](./affection.svg) A simple Game Engine in Haskell using SDL diff --git a/affection.svg b/affection.svg new file mode 100644 index 0000000..0db7fb8 --- /dev/null +++ b/affection.svg @@ -0,0 +1,93 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + From c57046ca3b170ad7a66ca8b8d04b5aa2c195f81b Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 17 Mar 2018 02:49:16 +0100 Subject: [PATCH 212/261] fix sdl2 dependency --- affection.cabal | 2 +- src/Affection/MessageBus/Message/WindowMessage.hs | 3 ++- src/Affection/Subsystems/AffectionWindow.hs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/affection.cabal b/affection.cabal index ecd9b6e..99c68d3 100644 --- a/affection.cabal +++ b/affection.cabal @@ -101,7 +101,7 @@ library 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 diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index d857ed2..6e5356d 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -35,6 +35,7 @@ data WindowMessage | MsgWindowSizeChange { msgWSCWhen :: Double , msgWSCWindow :: SDL.Window + , msgWRNewSize :: V2 Int32 } | MsgWindowMinimize { msgWMinWhen :: Double @@ -77,7 +78,7 @@ instance Message WindowMessage where 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 diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index e2e3bd5..2ec60ae 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -48,8 +48,8 @@ consumeSDLWindowEvents aw = doConsume 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) From 33c99b8888328e4ed17f5c65ac49f5eab2645549 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 27 Mar 2018 23:23:13 +0200 Subject: [PATCH 213/261] qualified gl --- src/Affection.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 61235fe..c948832 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -30,7 +30,7 @@ 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. @@ -136,9 +136,9 @@ withAffection AffectionConfig{..} = do -- 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 + liftIO GL.flush -- handle all new draw requests ad2 <- get -- actual drawing From 38e9d3b517abdd4398003e4be67c4656780ed596 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 27 Mar 2018 23:31:52 +0200 Subject: [PATCH 214/261] nix expression --- affection.nix | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 affection.nix diff --git a/affection.nix b/affection.nix new file mode 100644 index 0000000..9085818 --- /dev/null +++ b/affection.nix @@ -0,0 +1,18 @@ +{ mkDerivation, base, bytestring, clock, containers, glib, linear +, monad-loops, monad-parallel, mtl, OpenGL, sdl2, stdenv, stm, text +, uuid, vector +}: +mkDerivation { + pname = "affection"; + version = "0.0.0.9"; + src = ./.; + isLibrary = true; + isExecutable = true; + libraryHaskellDepends = [ + base bytestring clock containers glib linear monad-loops + monad-parallel mtl OpenGL sdl2 stm text uuid vector + ]; + homepage = "https://github.com/nek0/affection#readme"; + description = "A simple Game Engine using SDL"; + license = stdenv.lib.licenses.lgpl3; +} From 5275fc2606cddd4ae5dd6260cd40e7138f610b66 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 28 Mar 2018 02:12:35 +0200 Subject: [PATCH 215/261] nixify --- shell.nix | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 shell.nix diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..30c2334 --- /dev/null +++ b/shell.nix @@ -0,0 +1,68 @@ +{ nixpkgs ? import {}, compiler ? "default", doBenchmark ? false }: + +let + + inherit (nixpkgs) pkgs; + + nanovgNeko = with haskellPackages; callPackage ({ + mkDerivation, base, bytestring, c2hs, containers, glew, hspec, + inline-c, libGL, libGLU, QuickCheck, text, vector, pkgconfig }: + mkDerivation { + pname = "nanovg"; + version = "0.5.2.0"; + #src = pkgs.fetchFromGitHub { + # owner = "nek0"; + # repo = "nanovg-hs"; + # rev = "21868dc2c8e6eae8e7c0962d22890a8f2522cab0"; + # sha256 = "0diyyajniyxzmg5za34crwwlajf40x8bplja6mbgv2syax5d446d"; + # fetchSubmodules = true; + #}; + src = ../nanovg-hs; + revision = "1"; + isLibrary = true; + isExecutable = true; + doCheck = false; + libraryHaskellDepends = [ base bytestring containers text vector ]; + librarySystemDepends = [ glew libGL libGLU pkgconfig ]; + libraryToolDepends = [ c2hs ]; + testHaskellDepends = [ base containers hspec inline-c QuickCheck ]; + homepage = "https://github.com/cocreature/nanovg-hs"; + description = "Haskell bindings for nanovg"; + license = pkgs.stdenv.lib.licenses.isc; + hydraPlatforms = pkgs.stdenv.lib.platforms.none; + }) { inherit (pkgs) glew; inherit (pkgs) libGL; inherit (pkgs) libGLU; inherit (pkgs) pkgconfig; }; + + f = { mkDerivation, base, bytestring, clock, containers, deepseq + , glib, linear, matrix, monad-loops, monad-parallel, mtl + , OpenGL, random, sdl2, 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 sdl2 stm text uuid vector + ]; + executableHaskellDepends = [ + base containers deepseq linear matrix nanovgNeko OpenGL random sdl2 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 {}); + +in + + if pkgs.lib.inNixShell then drv.env else drv From 2b77d448accb401fbd42b64a11ffd34e720f6d41 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 3 Jun 2018 02:41:49 +0200 Subject: [PATCH 216/261] use strict state monads --- src/Affection.hs | 2 +- src/Affection/Types.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index c948832..37cd4bd 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -14,7 +14,7 @@ import qualified 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(..)) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 350c285..ec83dee 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -40,7 +40,7 @@ import Data.Map.Strict as M -- 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) From 62921052df9dd68c97fd4c48a08ea81e029ac839 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 16 Jun 2018 19:34:42 +0200 Subject: [PATCH 217/261] set sharable contexts --- src/Affection.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Affection.hs b/src/Affection.hs index 37cd4bd..3bbf0d9 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -10,6 +10,8 @@ module Affection import SDL (($=)) import qualified SDL +import qualified SDL.Raw.Video as SDL (glSetAttribute) +import qualified SDL.Raw.Enum as SDL import System.Clock @@ -83,6 +85,7 @@ withAffection AffectionConfig{..} = do -- 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 From 37bec202feb51da413d11318ef34a4b49e415970 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 18 Jun 2018 01:09:01 +0200 Subject: [PATCH 218/261] destroy context --- src/Affection.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Affection.hs b/src/Affection.hs index 3bbf0d9..1957a7c 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -160,6 +160,7 @@ withAffection AffectionConfig{..} = do liftIO $ logIO Debug "Loop ended. Cleaning" cleanUp $ userState nState liftIO $ logIO Debug "Destroying Window" + SDL.glDeleteContext context SDL.destroyWindow window SDL.quit liftIO $ logIO Debug "This is the end" From 34bcb88e2e980a0ae6ac872ec6a87ee782f2d4de Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 18 Jun 2018 20:10:13 +0200 Subject: [PATCH 219/261] update nanovg dependency --- affection.cabal | 2 +- shell.nix | 27 ++++++++++----------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/affection.cabal b/affection.cabal index 99c68d3..dfeec64 100644 --- a/affection.cabal +++ b/affection.cabal @@ -150,7 +150,7 @@ executable example01 , containers , linear , matrix - , nanovg + , nanovg >= 0.6.0.0 , deepseq else buildable: False diff --git a/shell.nix b/shell.nix index 30c2334..eb3df2a 100644 --- a/shell.nix +++ b/shell.nix @@ -4,33 +4,26 @@ let inherit (nixpkgs) pkgs; - nanovgNeko = with haskellPackages; callPackage ({ - mkDerivation, base, bytestring, c2hs, containers, glew, hspec, - inline-c, libGL, libGLU, QuickCheck, text, vector, pkgconfig }: + nanovgNeko = with haskellPackages; callPackage ( + { mkDerivation, base, bytestring, c2hs, containers, GLEW, glew + , hspec, inline-c, libGL, libGLU, QuickCheck, stdenv, text, vector + }: mkDerivation { pname = "nanovg"; - version = "0.5.2.0"; - #src = pkgs.fetchFromGitHub { - # owner = "nek0"; - # repo = "nanovg-hs"; - # rev = "21868dc2c8e6eae8e7c0962d22890a8f2522cab0"; - # sha256 = "0diyyajniyxzmg5za34crwwlajf40x8bplja6mbgv2syax5d446d"; - # fetchSubmodules = true; - #}; - src = ../nanovg-hs; - revision = "1"; + version = "0.6.0.0"; + sha256 = "e35a2772f2a2e71916013cab70eeb8eedd6f66b5490ddf7ce237c8c76895d496"; isLibrary = true; isExecutable = true; doCheck = false; libraryHaskellDepends = [ base bytestring containers text vector ]; - librarySystemDepends = [ glew libGL libGLU pkgconfig ]; + 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 = pkgs.stdenv.lib.licenses.isc; - hydraPlatforms = pkgs.stdenv.lib.platforms.none; - }) { inherit (pkgs) glew; inherit (pkgs) libGL; inherit (pkgs) libGLU; inherit (pkgs) pkgconfig; }; + license = stdenv.lib.licenses.isc; + }) {}; f = { mkDerivation, base, bytestring, clock, containers, deepseq , glib, linear, matrix, monad-loops, monad-parallel, mtl From c1dba6d1371e2103d01cd7ea0ecdb230a8c5e343 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 18 Jun 2018 20:12:36 +0200 Subject: [PATCH 220/261] fix deps --- shell.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell.nix b/shell.nix index eb3df2a..7004496 100644 --- a/shell.nix +++ b/shell.nix @@ -5,7 +5,7 @@ let inherit (nixpkgs) pkgs; nanovgNeko = with haskellPackages; callPackage ( - { mkDerivation, base, bytestring, c2hs, containers, GLEW, glew + { mkDerivation, base, bytestring, c2hs, containers, glew , hspec, inline-c, libGL, libGLU, QuickCheck, stdenv, text, vector }: mkDerivation { @@ -16,7 +16,7 @@ let isExecutable = true; doCheck = false; libraryHaskellDepends = [ base bytestring containers text vector ]; - librarySystemDepends = [ GLEW libGL libGLU ]; + librarySystemDepends = [ glew libGL libGLU ]; libraryPkgconfigDepends = [ glew ]; libraryToolDepends = [ c2hs ]; testHaskellDepends = [ base containers hspec inline-c QuickCheck ]; From a6c314f3ac1b5fd9ccc2fbe8fe54b153b27c84c9 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 3 Jul 2018 23:52:29 +0200 Subject: [PATCH 221/261] quitting SDL properly causes problems sometimes.... --- src/Affection.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Affection.hs b/src/Affection.hs index 1957a7c..b8114db 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -162,5 +162,5 @@ withAffection AffectionConfig{..} = do liftIO $ logIO Debug "Destroying Window" SDL.glDeleteContext context SDL.destroyWindow window - SDL.quit + -- SDL.quit liftIO $ logIO Debug "This is the end" From fdd0bba64743cdb34807a396a18e05bf3714857e Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 17 Sep 2018 13:15:41 +0200 Subject: [PATCH 222/261] update logo --- affection.svg | 49 +++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/affection.svg b/affection.svg index 0db7fb8..c8adad4 100644 --- a/affection.svg +++ b/affection.svg @@ -9,13 +9,13 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" id="svg2" - height="20.887682" + height="20.634701" width="106.91407" sodipodi:docname="affection.svg" inkscape:export-filename="/home/nek0/Obrázky/avatars/affection.png" inkscape:export-xdpi="298.74066" inkscape:export-ydpi="298.74066" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + inkscape:window-y="29" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> image/svg+xml - + @@ -56,38 +60,47 @@ + style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Didact Gothic';-inkscape-font-specification:'Didact Gothic Medium';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.15064645" + inkscape:connector-curvature="0" /> + id="path843" + inkscape:connector-curvature="0" /> + id="path845" + inkscape:connector-curvature="0" /> + id="path847" + inkscape:connector-curvature="0" /> + id="path849" + inkscape:connector-curvature="0" /> + id="path851" + inkscape:connector-curvature="0" /> + id="path853" + inkscape:connector-curvature="0" /> + id="path855" + inkscape:connector-curvature="0" /> + id="path859" + inkscape:connector-curvature="0" />
From 2e09f5f15c915b33c0f41057827ee0196e33b60e Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Sep 2018 00:13:52 +0200 Subject: [PATCH 223/261] resized logo --- affection.svg | 55 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/affection.svg b/affection.svg index 0db7fb8..22cbb76 100644 --- a/affection.svg +++ b/affection.svg @@ -9,13 +9,13 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" id="svg2" - height="20.887682" - width="106.91407" + height="30" + width="155.43826" sodipodi:docname="affection.svg" inkscape:export-filename="/home/nek0/Obrázky/avatars/affection.png" inkscape:export-xdpi="298.74066" inkscape:export-ydpi="298.74066" - inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> + inkscape:version="0.92.3 (2405546, 2018-03-11)"> + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> image/svg+xml - + + style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Didact Gothic';-inkscape-font-specification:'Didact Gothic Medium';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.15064645" + inkscape:connector-curvature="0" /> + id="path843" + inkscape:connector-curvature="0" /> + id="path845" + inkscape:connector-curvature="0" /> + id="path847" + inkscape:connector-curvature="0" /> + id="path849" + inkscape:connector-curvature="0" /> + id="path851" + inkscape:connector-curvature="0" /> + id="path853" + inkscape:connector-curvature="0" /> + id="path855" + inkscape:connector-curvature="0" /> + id="path859" + inkscape:connector-curvature="0" /> From 8d20ab193bd9a0fa5ced11f7393bb01f4351c5a8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Sep 2018 00:17:59 +0200 Subject: [PATCH 224/261] pre-release cleanup part I --- src/Affection/Actor.hs | 62 ---------- src/Affection/Animation.hs | 49 -------- src/Affection/Draw.hs | 240 ------------------------------------- src/Affection/Particle.hs | 163 ------------------------- src/Affection/Property.hs | 46 ------- 5 files changed, 560 deletions(-) delete mode 100644 src/Affection/Actor.hs delete mode 100644 src/Affection/Animation.hs delete mode 100644 src/Affection/Draw.hs delete mode 100644 src/Affection/Particle.hs delete mode 100644 src/Affection/Property.hs diff --git a/src/Affection/Actor.hs b/src/Affection/Actor.hs deleted file mode 100644 index 0f98598..0000000 --- a/src/Affection/Actor.hs +++ /dev/null @@ -1,62 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} - --- | This module implements the Actor, a Datastructure binding a 'G.GeglNode' --- to a game asset -module Affection.Actor - ( Actor(..) - , ActorProperty(..) - , updateProperties - , applyProperties - , getProperty - ) where - -import qualified GEGL as G - -import qualified Data.Map.Strict as M -import Data.List -import qualified Control.Monad.Parallel as MP - -import Affection.Types - -data (Show a, Ord a) => Actor a = Actor - { actorProperties :: [ActorProperty a] - , actorNodes :: M.Map a G.GeglNode - , actorFlange :: G.GeglNode - } deriving (Show, Eq) - -data (Show a, Ord a) => ActorProperty a = ActorProperty - { apName :: String - , apValue :: G.PropertyValue - , apMapping :: Maybe (a, String) - } deriving (Show, Eq) - -updateProperties :: (Show a, Ord a) => [G.Property] -> Actor a -> Actor a -updateProperties ps act@Actor{..} = - act - { actorProperties = - foldl (\acc (G.Property name val) -> newProp name val acc) actorProperties ps - } - where - newProp name val acc = nubBy (\a b -> apName a == apName b) $ (getProp name) - { apValue = val - } : acc - getProp n = getProp' n actorProperties - getProp' n [] = error $ "no ActorProperty found: " ++ n - getProp' n (p:ps) - | apName p == n = p - | otherwise = getProp' n ps - -applyProperties :: (Show a, Ord a) => Actor a -> Affection us () -applyProperties Actor{..} = - MP.mapM_ (\ActorProperty{..} -> - maybe (return ()) (\m -> - liftIO $ G.gegl_node_set (actorNodes M.! fst m) $ G.Operation "" $ - (G.Property (snd m) apValue) : [] - ) apMapping - ) actorProperties - -getProperty :: (Show a, Ord a) => String -> Actor a -> Maybe G.PropertyValue -getProperty name act = - case find (\a -> name == apName a) $ actorProperties act of - Just p -> Just $ apValue p - Nothing -> Nothing diff --git a/src/Affection/Animation.hs b/src/Affection/Animation.hs deleted file mode 100644 index 87d2e8a..0000000 --- a/src/Affection/Animation.hs +++ /dev/null @@ -1,49 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} - -module Affection.Animation - ( SpriteAnimation(..) - , runAnimation - ) where - -import Affection.Actor -import Affection.Types -import Affection.Property - -import qualified GEGL as G - -import Control.Monad.State - -data SpriteAnimation = SpriteAnimation - { sanimCurrentFrame :: Int - , sanimSprites :: [FilePath] - , sanimFrameDuration :: Double - , sanimLoop :: Bool - , sanimLastChange :: Double - , sanimPropName :: String - } - -runAnimation - :: (Show a, Ord a) - => Actor a - -> SpriteAnimation - -> Affection us (SpriteAnimation, Actor a) -runAnimation act anim@SpriteAnimation{..} = do - ad <- get - let elapsed = elapsedTime ad - if elapsed - sanimLastChange > sanimFrameDuration - then do - let nframe = - if sanimCurrentFrame + 1 > length sanimSprites - then - if sanimLoop then 1 else sanimCurrentFrame - else sanimCurrentFrame + 1 - nact = - updateProperties - (props $ prop sanimPropName $ sanimSprites !! (nframe - 1)) - act - return (anim - { sanimCurrentFrame = nframe - , sanimLastChange = elapsed - }, nact) - else - return (anim, act) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs deleted file mode 100644 index e533f7e..0000000 --- a/src/Affection/Draw.hs +++ /dev/null @@ -1,240 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} - --- | Module for drawing primitives -module Affection.Draw - ( drawRect - , clear - , handleDrawRequest - , invalidateDrawRequest - , process - , present - , render - , clearArea - ) where - -import Affection.Types - -import Data.Maybe (maybe) -import Data.ByteString (ByteString) - -import Foreign -import Foreign.C.Types -import Foreign.Marshal.Alloc (malloc, free) - -import Control.Monad.State (get, put) -import Control.Monad (when, unless) - -import System.Glib.GObject - -import qualified SDL - --- import qualified BABL as B --- import qualified GEGL as G - -import Debug.Trace - -drawRect - :: G.GeglNode -- ^ Target Node - -> G.Color -- ^ Color to draw in - -> DrawType -- ^ Draw type - -> G.GeglRectangle -- ^ Dimensions of Rectangle - -> G.GeglBuffer -- ^ Final Buffer - -> Affection us () -drawRect node color Fill rect@GeglRectangle{..} buf = do - ad <- get - tempRoot <- liftIO G.gegl_node_new - opNode <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation "gegl:rectangle" - [ G.Property "x" $ G.PropertyDouble $ fromIntegral rectangleX - , G.Property "y" $ G.PropertyDouble $ fromIntegral rectangleY - , G.Property "width" $ G.PropertyDouble $ fromIntegral rectangleWidth - , G.Property "height" $ G.PropertyDouble $ fromIntegral rectangleHeight - , G.Property "color" $ G.PropertyColor color - ] - diw <- liftIO $ G.gegl_node_connect_to opNode "output" node "input" - unless diw $ error "Affection.Draw.drawRect: connect failed" - put $ ad - { drawStack = DrawRequest rect buf (Kill (Just tempRoot)) : drawStack ad - } - --- | Process a node graph -process - :: G.GeglNode - -> Affection us () -process = liftIO . G.gegl_node_process - --- | Update of a specific region on the texture -present - :: G.GeglRectangle -- ^ Area to be updated - -> G.GeglBuffer -- ^ Target buffer - -> Bool -- ^ Shall the 'DrawRequest' persist? - -> Affection us () -present rect buf kill = do - ad <- get - let k = if not kill then Kill Nothing else Persist - put ad - { drawStack = DrawRequest rect buf k : drawStack ad - } - --- | Render the Texture or a clipping thereof. The clipping will be stretched --- to fit the render target. -render - :: Maybe G.GeglRectangle - -- ^ Area of the texture to render. - -- Pass 'Nothing' to render the whole texture. - -> Maybe G.GeglRectangle - -- ^ Area of render target to draw to. - -- Pass 'Nothing' to render to the whole render target. - -> Affection us () -render msrc mtgt = - do - AffectionData{..} <- get - SDL.copy - windowRenderer - drawTexture - src - tgt - where - toSdlRect (G.GeglRectangle x y w h) = SDL.Rectangle - (SDL.P $ SDL.V2 (CInt $ fromIntegral x) (CInt $ fromIntegral y)) - (SDL.V2 (CInt $ fromIntegral w) (CInt $ fromIntegral h)) - src = maybe Nothing (Just . toSdlRect) msrc - tgt = maybe Nothing (Just . toSdlRect) mtgt - -putToTexture - :: G.GeglRectangle - -> Int - -> Int - -> DrawRequest - -> Affection us () -putToTexture realRect stride cpp DrawRequest{..} = do - ad <- get - destRect <- return $ - SDL.Rectangle - (SDL.P $ SDL.V2 - (CInt $ fromIntegral $ rectangleX realRect) - (CInt $ fromIntegral $ rectangleY realRect) - ) - (SDL.V2 - (CInt $ fromIntegral $ rectangleWidth realRect) - (CInt $ fromIntegral $ rectangleHeight realRect) - ) - (destPtr, destStride) <- SDL.lockTexture - (drawTexture ad) - (Just destRect) - liftIO $ G.gegl_buffer_get - requestBuffer - (Just realRect) - 1 - (Just $ drawFormat ad) - destPtr - stride - G.GeglAbyssNone - SDL.unlockTexture $ drawTexture ad - --- | function for handling 'DrawRequest's and updating the output -handleDrawRequest - -- :: Ptr a -- ^ Pixel buffer to blit to - -- -> B.BablFormatPtr -- ^ format to blit in - :: Int -- ^ Stride - -> Int -- ^ Components per Pixel - -> DrawRequest -- ^ 'DrawRequest' to handle - -> Affection us (Maybe DrawRequest) -handleDrawRequest stride cpp dr@DrawRequest{..} = do - ad <- get - -- let surf = drawSurface ad - mrealRect <- liftIO $ G.gegl_rectangle_intersect - requestArea - (uncurry (G.GeglRectangle 0 0) (drawDimensions ad)) - maybe (return()) (\realRect -> - -- putToSurface pixels realRect stride cpp dr - putToTexture realRect stride cpp dr - ) mrealRect - case requestPersist of - Persist -> - return Nothing - Kill _ -> - return $ Just dr - --- | clear a previously drawn area -invalidateDrawRequest - -- :: Ptr a -- ^ Pixel buffer to blit to - -- -> B.BablFormatPtr -- ^ format to blit in - :: Int -- ^ Stride - -> Int -- ^ Components per Pixel - -> DrawRequest -- ^ Drawrequest to invalidate - -> Affection us () -invalidateDrawRequest stride cpp dr@DrawRequest{..} = do - ad <- get - mrealRect <- liftIO $ G.gegl_rectangle_intersect - requestArea - (uncurry (G.GeglRectangle 0 0) (drawDimensions ad)) - maybe (return()) (\realRect -> do - liftIO $ clearArea requestBuffer realRect - -- putToSurface pixels realRect stride cpp dr - putToTexture realRect stride cpp dr - ) mrealRect - case requestPersist of - Kill (Just victim) -> - liftIO $ G.gegl_node_drop victim - _ -> - return () - -- liftIO $ SDL.updateWindowSurface $ drawWindow ad - --- | compute color for a single pixel -colorize - :: (G.ComponentValue, G.ComponentValue, G.ComponentValue, G.ComponentValue) -- ^ Pixel information in buffer - -> G.Color -- ^ Color to draw over - -> (G.ComponentValue, G.ComponentValue, G.ComponentValue, G.ComponentValue) -- ^ Resulting colour -colorize (rr, rg, rb, ra) col = - let (G.CVdouble (CDouble br)) = rr - (G.CVdouble (CDouble bg)) = rg - (G.CVdouble (CDouble bb)) = rb - (G.CVdouble (CDouble ba)) = ra - (cr, cg, cb) = case col of - G.RGBA r g b _ -> (r, g, b) - G.RGB r g b -> (r, g, b) - ca = case col of - G.RGBA _ _ _ a -> a - G.RGB{} -> 1 - alpha = ca - dst_a = ba - da = alpha + dst_a * (1 - alpha) - a_term = dst_a * (1 - alpha) - red = cr * alpha + br * a_term - gre = cg * alpha + bg * a_term - blu = cb * alpha + bb * a_term - in - ( G.CVdouble $ CDouble $ red / da - , G.CVdouble $ CDouble $ gre / da - , G.CVdouble $ CDouble $ blu / da - , G.CVdouble $ CDouble ca - ) - -unsafeColorize col = - let - (r, g, b) = case col of - G.RGBA cr cg cb _ -> (cr, cg, cb) - G.RGB cr cg cb -> (cr, cg, cb) - a = case col of - G.RGBA _ _ _ ca -> ca - G.RGB{} -> 1 - in - ( G.CVdouble $ CDouble r - , G.CVdouble $ CDouble g - , G.CVdouble $ CDouble b - , G.CVdouble $ CDouble a - ) - --- | Clear a specified area of a buffer from all data -clearArea - :: G.GeglBuffer -- ^ Target buffer - -> G.GeglRectangle -- ^ Area to clear - -> IO () -clearArea = G.gegl_buffer_clear - --- | Clear the whole drawing area -clear :: G.GeglBuffer -> Affection us () -clear buffer = do - ad <- get - let (w, h) = drawDimensions ad - liftIO $ clearArea buffer (GeglRectangle 0 0 w h) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs deleted file mode 100644 index 637709f..0000000 --- a/src/Affection/Particle.hs +++ /dev/null @@ -1,163 +0,0 @@ -{-# LANGUAGE BangPatterns #-} - --- | This module introduces a simple particle system to Affection -module Affection.Particle - ( updateParticle - , drawParticles - , updateParticleSystem - , drawParticleSystem - , insertParticle - ) where - -import Affection.Types - -import Control.Monad -import Control.Monad.State (get) - -import Data.Maybe - -import qualified GEGL as G - -import Debug.Trace - --- This function updates particles through a specified function. Particle ageing --- and death is being handled by 'updateParticles' itself and does not need to --- bother you. -updateParticle - :: Double - -- ^ Elapsed time in seconds - -> (Double -> Particle -> Affection us Particle) - -- ^ Update function for each 'Particle' - -> [Particle] - -- ^ List of 'Particle's to be processed - -> Affection us [Particle] - -- ^ processed 'Particle's -updateParticle time func l = - catMaybes <$> mapM (\p -> do - now <- elapsedTime <$> get - if particleCreation p + particleTimeToLive p < now - then do - dropParticle p - return Nothing - else do - np <- func time p - return $ Just np - ) l - -- updateParticle' time func l - where - updateParticle' _ _ [] = return [] - updateParticle' dt fun [p] = do - now <- elapsedTime <$> get - if particleCreation p + particleTimeToLive p < now - then do - dropParticle p - return [] - else - (: []) <$> func time p - updateParticle' dt fun (p:ps) = do - now <- elapsedTime <$> get - if particleCreation p + particleTimeToLive p < now - then do - dropParticle p - updateParticle' dt fun ps - else do - np <- fun dt p - (np :) <$> updateParticle' dt fun ps - dropParticle p = do - mproducer <- liftIO $ G.gegl_node_get_producer - (particleStackCont p) - "input" - maybe (return ()) (\(producer, padname) -> do - consumers <- liftIO $ G.gegl_node_get_consumers - (particleStackCont p) - "output" - liftIO $ mapM_ (uncurry $ G.gegl_node_connect_to - producer - padname - ) consumers - ) mproducer - liftIO $ G.gegl_node_drop $ particleRootNode p - --- | Get the next living particle from a list -nextLiving - :: [Maybe Particle] - -> Maybe Particle -nextLiving [] = Nothing -nextLiving acc = case catMaybes acc of - [] -> Nothing - ps -> Just $ head ps - -drawParticles - :: (Particle -> Affection us ()) - -> [Particle] - -> Affection us () -drawParticles = mapM_ - -updateParticleSystem - :: ParticleSystem - -> Double - -> (Double -> Particle -> Affection us Particle) - -> Affection us ParticleSystem -updateParticleSystem sys sec upd = do - !x <- updateParticle sec upd (partStorList $ partSysParts sys) - if not $ null x - then do - return sys - { partSysParts = (partSysParts sys) - { partStorList = x - } - } - else do - return sys - { partSysParts = ParticleStorage - { partStorList = [] - , partStorLatest = Nothing - } - } - -drawParticleSystem - :: ParticleSystem - -> (G.GeglBuffer -> G.GeglNode -> Particle -> Affection us ()) - -> Affection us () -drawParticleSystem sys draw = - if not (null parts) - then do - liftIO $ G.gegl_node_link (particleStackCont $ head parts) (partSysNode sys) - mapM_ (draw (partSysBuffer sys) (partSysNode sys)) parts - else do - _ <- liftIO $ G.gegl_node_disconnect (partSysNode sys) "input" - return () - where - parts = partStorList (partSysParts sys) - --- | Function for inserting a new 'Particle' into its 'PartileSystem' -insertParticle - :: ParticleSystem -- ^ 'ParticleSystem' to insert into - -> Particle -- ^ 'Particle' to insert - -> Affection us ParticleSystem -- ^ resulting new 'ParticleSystem' -insertParticle ps p = do - now <- elapsedTime <$> get - let newList = chronoInsert now (partStorList $ partSysParts ps) p - liftIO $ G.gegl_node_link_many (reverse $ map particleStackCont newList) - return ps - { partSysParts = (partSysParts ps) - { partStorLatest = Just p - , partStorList = newList - } - } - --- | Insert a 'Particle' into its chronologically ordered list -chronoInsert - :: Double -- ^ Time in seconds from beginning of program execution - -> [Particle] -- ^ List to insert to - -> Particle -- ^ 'Particle' to insert - -> [Particle] -- ^ Resulting list -chronoInsert now [] np = [np] -chronoInsert now [p] np = - if now + particleTimeToLive p < (particleCreation np + particleTimeToLive np) - then p : [np] - else np : [p] -chronoInsert now l@(p:ps) np = - if now + particleTimeToLive p < (particleCreation np + particleTimeToLive np) - then p : chronoInsert now ps np - else np : l diff --git a/src/Affection/Property.hs b/src/Affection/Property.hs deleted file mode 100644 index 78e209f..0000000 --- a/src/Affection/Property.hs +++ /dev/null @@ -1,46 +0,0 @@ -{-# LANGUAGE TypeSynonymInstances #-} -{-# LANGUAGE FlexibleInstances #-} -module Affection.Property - ( Props - , prop - , props - ) where - -import qualified GEGL as G -import qualified BABL as B - -import Control.Monad.State.Lazy -import Foreign.Ptr (Ptr) - -type Props a = State [G.Property] a - -props :: Props a -> [G.Property] -props = flip execState [] - -prop :: IsPropertyValue v => String -> v -> Props () -prop k v = do - ps <- get - put $ G.Property k (toPropertyValue v) : ps - -class IsPropertyValue v where - toPropertyValue :: v -> G.PropertyValue - -instance IsPropertyValue Int where - toPropertyValue = G.PropertyInt - -instance IsPropertyValue String where - toPropertyValue = G.PropertyString - -instance IsPropertyValue Double where - toPropertyValue = G.PropertyDouble - -instance IsPropertyValue G.Color where - toPropertyValue = G.PropertyColor - -instance IsPropertyValue B.PixelFormat where - toPropertyValue = G.PropertyFormat -instance IsPropertyValue G.GeglBuffer where - toPropertyValue = G.PropertyBuffer - -instance IsPropertyValue (Ptr ()) where - toPropertyValue = G.PropertyPointer From c804a2e013ee3e5a9b7ed07ea195b716f2cac8d6 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Sep 2018 07:02:33 +0200 Subject: [PATCH 225/261] pre-release cleanup part II --- shell.nix | 28 ++- src/Affection.hs | 38 +--- src/Affection/Logging.hs | 24 ++- src/Affection/MessageBus/Class.hs | 9 +- src/Affection/MessageBus/Message/Class.hs | 4 +- .../MessageBus/Message/JoystickMessage.hs | 60 +++--- .../MessageBus/Message/KeyboardMessage.hs | 21 ++- .../MessageBus/Message/MouseMessage.hs | 53 +++--- .../MessageBus/Message/WindowMessage.hs | 98 +++++----- src/Affection/MessageBus/Util.hs | 3 - src/Affection/MouseInteractable.hs | 42 ----- src/Affection/StateMachine.hs | 6 + src/Affection/Subsystems/AffectionJoystick.hs | 16 +- src/Affection/Types.hs | 171 ++---------------- 14 files changed, 215 insertions(+), 358 deletions(-) delete mode 100644 src/Affection/MessageBus/Util.hs delete mode 100644 src/Affection/MouseInteractable.hs diff --git a/shell.nix b/shell.nix index 7004496..00f03df 100644 --- a/shell.nix +++ b/shell.nix @@ -25,9 +25,31 @@ let license = stdenv.lib.licenses.isc; }) {}; + 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 , glib, linear, matrix, monad-loops, monad-parallel, mtl - , OpenGL, random, sdl2, stdenv, stm, text, uuid, vector + , OpenGL, random, stdenv, stm, text, uuid, vector }: mkDerivation { pname = "affection"; @@ -38,10 +60,10 @@ let isExecutable = true; libraryHaskellDepends = [ base bytestring clock containers glib linear monad-loops - monad-parallel mtl OpenGL sdl2 stm text uuid vector + monad-parallel mtl OpenGL sdl2Nek0 stm text uuid vector ]; executableHaskellDepends = [ - base containers deepseq linear matrix nanovgNeko OpenGL random sdl2 stm + base containers deepseq linear matrix nanovgNeko OpenGL random sdl2Nek0 stm ]; homepage = "https://github.com/nek0/affection#readme"; description = "A simple Game Engine using SDL"; diff --git a/src/Affection.hs b/src/Affection.hs index b8114db..a77e4e2 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -59,32 +59,6 @@ withAffection AffectionConfig{..} = do 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 @@ -92,7 +66,7 @@ withAffection AffectionConfig{..} = do Just (cw, ch) -> (cw, ch) Nothing -> (fromIntegral rw, fromIntegral rh) SDL.setWindowMode window initScreenMode - SDL.swapInterval $= SDL.SynchronizedUpdates -- <- causes Problems with windows + -- SDL.swapInterval $= SDL.SynchronizedUpdates -- <- causes Problems with windows liftIO $ logIO Debug "Getting Time" -- get current time execTime <- getTime Monotonic @@ -134,7 +108,7 @@ withAffection AffectionConfig{..} = do } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents - -- mapM_ eventLoop evs + -- handle events eventLoop evs -- execute user defined update loop unless (pausedTime ad) (updateLoop dt) @@ -142,12 +116,8 @@ withAffection AffectionConfig{..} = do liftIO $ GL.clear [GL.ColorBuffer, GL.DepthBuffer, GL.StencilBuffer] drawLoop liftIO GL.flush - -- handle all new draw requests - ad2 <- get - -- actual drawing + -- 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) ( @@ -162,5 +132,5 @@ withAffection AffectionConfig{..} = do 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" diff --git a/src/Affection/Logging.hs b/src/Affection/Logging.hs index b450774..e64ed7c 100644 --- a/src/Affection/Logging.hs +++ b/src/Affection/Logging.hs @@ -1,15 +1,23 @@ {-# LANGUAGE CPP #-} module Affection.Logging where +-- ^ This module defines the logging capability of Affection, whis is derived +-- from "Debug.Trace". 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 @@ log Error s = trace ("ERROR: " ++ s) #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 diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 93d826e..16ca2bc 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -3,11 +3,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ExistentialQuantification #-} -module Affection.MessageBus.Class - ( Participant(..) - , genUUID - , UUID - ) where +module Affection.MessageBus.Class where import Affection.MessageBus.Message import Affection.Types @@ -19,8 +15,9 @@ import Data.UUID.V4 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 - type Mesg prt us :: * + type Mesg prt us :: * -- ^ Message datatype -- | Function to get the list of subscribers from the participant partSubscribers diff --git a/src/Affection/MessageBus/Message/Class.hs b/src/Affection/MessageBus/Message/Class.hs index bcb2651..3da6039 100644 --- a/src/Affection/MessageBus/Message/Class.hs +++ b/src/Affection/MessageBus/Message/Class.hs @@ -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 diff --git a/src/Affection/MessageBus/Message/JoystickMessage.hs b/src/Affection/MessageBus/Message/JoystickMessage.hs index efb3311..eb5c0e2 100644 --- a/src/Affection/MessageBus/Message/JoystickMessage.hs +++ b/src/Affection/MessageBus/Message/JoystickMessage.hs @@ -1,7 +1,8 @@ module Affection.MessageBus.Message.JoystickMessage ( JoystickMessage(..) + -- | Vector export + , Linear.V2 -- | SDL exports - , SDL.V2 , SDL.JoyHatPosition , SDL.JoyButtonState , SDL.JoyDeviceConnection @@ -20,41 +21,42 @@ import qualified SDL import Linear (V2(..)) +-- Datatype for handling all possible joystick events handed over from sdl2 data JoystickMessage - = MsgJoystickAxis - { msgJAWhen :: Double - , msgJAWhich :: Int32 - , msgJAAxis :: Word8 - , msgJAValue :: Int16 + = MsgJoystickAxis -- ^ Movement of a Joystick axis + { msgJAWhen :: Double -- ^ Time of event + , msgJAWhich :: Int32 -- ^ Joystick identifier + , msgJAAxis :: Word8 -- ^ Axis identifier + , msgJAValue :: Int16 -- ^ Axis value } - | MsgJoystickBall - { msgJBWhen :: Double - , msgJBWhich :: Int32 - , msgJBBall :: Word8 - , msgJBRelMotion :: SDL.V2 Int16 + | MsgJoystickBall -- ^ Movement of a joystick ball controller + { msgJBWhen :: Double -- ^ Time of event + , msgJBWhich :: Int32 -- ^ Joystick identifier + , msgJBBall :: Word8 -- ^ Ball identifier + , msgJBRelMotion :: V2 Int16 -- ^ Motion relative to previous position } - | MsgJoystickHat - { msgJHWhen :: Double - , msgJHWhich :: Int32 - , msgJHHat :: Word8 - , msgJHPosition :: SDL.JoyHatPosition + | MsgJoystickHat -- ^ Movement of joystick hat controller + { msgJHWhen :: Double -- Time of event + , msgJHWhich :: Int32 -- Joystick identifier + , msgJHHat :: Word8 -- Hat identifier + , msgJHPosition :: SDL.JoyHatPosition -- New hat position } - | MsgJoystickButton - { msgJBWhen :: Double - , msgJBWhich :: Int32 - , msgJBButton :: Word8 - , msgJBState :: SDL.JoyButtonState + | MsgJoystickButton -- ^ Joystick button event + { msgJBWhen :: Double -- Time of event + , msgJBWhich :: Int32 -- Joystick identifier + , msgJBButton :: Word8 -- Button identifier + , msgJBState :: SDL.JoyButtonState -- New Button state } - | MsgJoystickDevice - { msgJDWhen :: Double - , msgJDWhich :: Int32 - , msgJDConnection :: SDL.JoyDeviceConnection + | MsgJoystickDevice -- ^ Joystick device event + { 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 (MsgJoystickAxis t _ _ _) = t + msgTime (MsgJoystickBall t _ _ _) = t + msgTime (MsgJoystickHat t _ _ _) = t msgTime (MsgJoystickButton t _ _ _) = t - msgTime (MsgJoystickDevice t _ _) = t + msgTime (MsgJoystickDevice t _ _) = t diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs index c2af0f1..30edb13 100644 --- a/src/Affection/MessageBus/Message/KeyboardMessage.hs +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -1,16 +1,23 @@ -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 - = MsgKeyboardEvent - { msgKbdWhen :: Double - , msgKbdWindow :: Maybe SDL.Window - , msgKbdKeyMotion :: SDL.InputMotion - , msgKbdKeyRepeat :: Bool - , msgKbdKeysym :: SDL.Keysym + = MsgKeyboardEvent -- ^ Arbitrary Keyboard event + { 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' } deriving (Show) diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index 1ed6300..0435650 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -1,4 +1,12 @@ -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 @@ -9,30 +17,31 @@ import qualified SDL import Linear (V2(..)) +-- Datatype for handling mouse events handed down from SDL2 data MouseMessage - = MsgMouseMotion - { msgMMWhen :: Double - , msgMMWindow :: Maybe SDL.Window - , msgMMWhich :: SDL.MouseDevice - , msgMMState :: [SDL.MouseButton] - , msgMMPos :: V2 Int32 - , msgMMRelMotion :: V2 Int32 + = MsgMouseMotion -- ^ Mouse motion event + { 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 } - | MsgMouseButton - { msgMBWhen :: Double - , msgMBWindow :: Maybe SDL.Window - , msgMBMotion :: SDL.InputMotion - , msgMBWhich :: SDL.MouseDevice - , msgMBButton :: SDL.MouseButton - , msgMBClicks :: Word8 - , msgMBPos :: V2 Int32 + | MsgMouseButton -- ^ Mouse button event + { 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 } - | MsgMouseWheel - { msgMWWhen :: Double - , msgMWWhindow :: Maybe SDL.Window - , msgMWWhich :: SDL.MouseDevice - , msgMWPos :: V2 Int32 - , msgMWDIrection :: SDL.MouseScrollDirection + | MsgMouseWheel -- ^ Mouse wheel event + { 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 } deriving (Show) diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index 6e5356d..806b46c 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -1,4 +1,8 @@ -module Affection.MessageBus.Message.WindowMessage where +module Affection.MessageBus.Message.WindowMessage + ( WindowMessage(..) + -- | SDL reexports + , SDL.Window + ) where import Affection.MessageBus.Message.Class @@ -8,66 +12,66 @@ import qualified SDL import Linear (V2(..)) +-- | Datatype for handling Window events handed down rom SDL2 data WindowMessage - -- = MsgEngineReady Double - = MsgWindowShow - { msgWSWhen :: Double - , msgWSWindow :: SDL.Window + = MsgWindowShow -- ^ Window show event + { msgWSWhen :: Double -- ^ Message time + , msgWSWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowHide - { msgWHWhen :: Double - , msgWHWindow :: SDL.Window + | MsgWindowHide -- ^ Window hide event + { msgWHWhen :: Double -- ^ Message time + , msgWHWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowExpose - { msgWEWhen :: Double - , msgWEWindow :: SDL.Window + | MsgWindowExpose -- ^ Window expose event + { msgWEWhen :: Double -- ^ Message time + , msgWEWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowMove - { msgWMWhen :: Double - , msgWMWindow :: SDL.Window - , msgWMNewPos :: V2 Int32 + | MsgWindowMove -- ^ Window move event + { msgWMWhen :: Double -- ^ Message time + , msgWMWindow :: SDL.Window -- ^ Window identifier + , msgWMNewPos :: V2 Int32 -- ^ New absolute window position } - | MsgWindowResize - { msgWRWhen :: Double - , msgWRWindow :: SDL.Window - , msgWRNewSize :: V2 Int32 + | MsgWindowResize -- ^ Window resize event + { msgWRWhen :: Double -- ^ Message time + , msgWRWindow :: SDL.Window -- ^ Window identifier + , msgWRNewSize :: V2 Int32 -- ^ New absolute window size } - | MsgWindowSizeChange - { msgWSCWhen :: Double - , msgWSCWindow :: SDL.Window - , msgWRNewSize :: V2 Int32 + | MsgWindowSizeChange -- ^ Window size change event + { msgWSCWhen :: Double -- ^ Message time + , msgWSCWindow :: SDL.Window -- ^ Window identifier + , msgWSCNewSize :: V2 Int32 -- ^ New absolute window size } - | MsgWindowMinimize - { msgWMinWhen :: Double - , msgWMinWindow :: SDL.Window + | MsgWindowMinimize -- ^ Window minimize event + { msgWMinWhen :: Double -- ^ Message time + , msgWMinWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowMaximize - { msgWMaxWhen :: Double - , msgWMaxWindow :: SDL.Window + | MsgWindowMaximize -- ^ Window maximize event + { msgWMaxWhen :: Double -- ^ Message time + , msgWMaxWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowRestore - { msgWRestWhen :: Double - , msgWRestWindow :: SDL.Window + | MsgWindowRestore -- ^ Window restore event + { msgWRestWhen :: Double -- ^ Message time + , msgWRestWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowGainMouseFocus - { msgWGMFWhen :: Double - , msgWGMFWindow :: SDL.Window + | MsgWindowGainMouseFocus -- ^ Window gain mouse focus event + { msgWGMFWhen :: Double -- ^ Message Time + , msgWGMFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowLoseMouseFocus - { msgWLMFWhen :: Double - , msgWLMFWindow :: SDL.Window + | MsgWindowLoseMouseFocus -- ^ Window lose mouse focus event + { msgWLMFWhen :: Double -- ^ Message Time + , msgWLMFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowGainKeyboardFocus - { msgWGKFWhen :: Double - , msgWGKFWindow :: SDL.Window + | MsgWindowGainKeyboardFocus -- ^ Window gain keyboard focus event + { msgWGKFWhen :: Double -- ^ Message time + , msgWGKFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowLoseKeyboardFocus - { msgWLKFWhen :: Double - , msgWLKFWindow :: SDL.Window + | MsgWindowLoseKeyboardFocus -- ^ Window lose keyboard focus event + { msgWLKFWhen :: Double -- ^ Message time + , msgWLKFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowClose - { msgWCWhen :: Double - , msgWCWindow :: SDL.Window + | MsgWindowClose -- ^ Window close event + { msgWCWhen :: Double -- ^ Message time + , msgWCWindow :: SDL.Window -- ^ Window identifier } deriving (Show) diff --git a/src/Affection/MessageBus/Util.hs b/src/Affection/MessageBus/Util.hs deleted file mode 100644 index b2cae3e..0000000 --- a/src/Affection/MessageBus/Util.hs +++ /dev/null @@ -1,3 +0,0 @@ -module Affection.MessageBus.Util where - --- zuru zuru diff --git a/src/Affection/MouseInteractable.hs b/src/Affection/MouseInteractable.hs deleted file mode 100644 index e3a6436..0000000 --- a/src/Affection/MouseInteractable.hs +++ /dev/null @@ -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 diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs index abbe3d9..c1b5413 100644 --- a/src/Affection/StateMachine.hs +++ b/src/Affection/StateMachine.hs @@ -6,9 +6,15 @@ import Affection.Types 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 () diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index 64ad018..69691b6 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -77,7 +77,13 @@ consumeSDLJoystickEvents am = doConsume doConsume es _ -> fmap (e :) (doConsume es) -joystickAutoConnect :: JoystickMessage -> Affection us (Maybe SDL.Joystick) +-- | 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 <$> (return . V.filter (\(SDL.JoystickDevice _ id) -> id == CInt which) @@ -86,7 +92,13 @@ joystickAutoConnect (MsgJoystickDevice _ which SDL.JoyDeviceAdded) = liftIO $ do Just <$> SDL.openJoystick descr joystickAutoConnect _ = return Nothing -joystickAutoDisconnect :: [SDL.Joystick] -> JoystickMessage -> Affection us [SDL.Joystick] +-- | 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 diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index ec83dee..0785d43 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -1,34 +1,20 @@ {-# 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.InitFlags(..) + , SDL.Window(..) + , SDL.GLContext(..) + ) where import qualified SDL.Init as SDL import qualified SDL.Video as SDL @@ -36,17 +22,11 @@ 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.Strict import qualified Control.Monad.Parallel as MP import System.Clock (TimeSpec) --- import Control.Monad.Reader - --- import Control.Concurrent.MVar import Foreign.Ptr (Ptr) @@ -85,16 +65,11 @@ data InitComponents -- | 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 @@ -102,25 +77,9 @@ data AffectionData us = AffectionData , 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 +91,4 @@ instance MP.MonadParallel m => MP.MonadParallel (AffectionState us m) 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 --- } From 937535057c203ec7ce76d357375aa378979e7c6e Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Sep 2018 16:10:36 +0200 Subject: [PATCH 226/261] more cleanup --- affection.cabal | 2 -- src/Affection/Logging.hs | 4 ++-- src/Affection/MessageBus/Message/JoystickMessage.hs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/affection.cabal b/affection.cabal index dfeec64..378b5c5 100644 --- a/affection.cabal +++ b/affection.cabal @@ -68,10 +68,8 @@ library , 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 diff --git a/src/Affection/Logging.hs b/src/Affection/Logging.hs index e64ed7c..f18b30e 100644 --- a/src/Affection/Logging.hs +++ b/src/Affection/Logging.hs @@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} -module Affection.Logging where --- ^ This module defines the logging capability of Affection, whis is derived +-- | This module defines the logging capability of Affection, whis is derived -- from "Debug.Trace". +module Affection.Logging where import Debug.Trace diff --git a/src/Affection/MessageBus/Message/JoystickMessage.hs b/src/Affection/MessageBus/Message/JoystickMessage.hs index eb5c0e2..645f4d3 100644 --- a/src/Affection/MessageBus/Message/JoystickMessage.hs +++ b/src/Affection/MessageBus/Message/JoystickMessage.hs @@ -21,7 +21,7 @@ import qualified SDL import Linear (V2(..)) --- Datatype for handling all possible joystick events handed over from sdl2 +-- | Datatype for handling all possible joystick events handed over from sdl2 data JoystickMessage = MsgJoystickAxis -- ^ Movement of a Joystick axis { msgJAWhen :: Double -- ^ Time of event From 6bb007615856abd5c433327be5556e0a8721f94f Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Sep 2018 16:10:36 +0200 Subject: [PATCH 227/261] more cleanup --- affection.cabal | 2 - examples/example00/Main.hs | 20 +-- examples/example01/Main.hs | 152 ++++++++---------- examples/example01/Types.hs | 10 +- src/Affection.hs | 8 +- src/Affection/Logging.hs | 4 +- src/Affection/MessageBus.hs | 1 - src/Affection/MessageBus/Class.hs | 6 +- .../MessageBus/Message/JoystickMessage.hs | 12 +- .../MessageBus/Message/MouseMessage.hs | 4 +- .../MessageBus/Message/WindowMessage.hs | 2 +- src/Affection/Subsystems/AffectionJoystick.hs | 8 +- src/Affection/Subsystems/AffectionKeyboard.hs | 4 - src/Affection/Subsystems/AffectionMouse.hs | 4 - src/Affection/Subsystems/AffectionWindow.hs | 4 - src/Affection/Subsystems/Class.hs | 1 - src/Affection/Types.hs | 19 +-- src/Affection/Util.hs | 10 +- 18 files changed, 114 insertions(+), 157 deletions(-) diff --git a/affection.cabal b/affection.cabal index dfeec64..378b5c5 100644 --- a/affection.cabal +++ b/affection.cabal @@ -68,10 +68,8 @@ library , 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 diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs index ae1f9e0..c496e67 100644 --- a/examples/example00/Main.hs +++ b/examples/example00/Main.hs @@ -1,13 +1,14 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} -import Affection -import qualified SDL +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 @@ -16,10 +17,10 @@ data StateData = StateData } data Subsystems = Subsystems - { subWindow :: Window - , subMouse :: Mouse - , subKeyboard :: Keyboard - , subJoystick :: Joystick + { subWindow :: Main.Window + , subMouse :: Main.Mouse + , subKeyboard :: Main.Keyboard + , subJoystick :: Main.Joystick } newtype Window = Window (TVar [(UUID, WindowMessage -> Affection StateData ())]) @@ -50,8 +51,8 @@ generalUnSubscribe generalUnSubscribe t uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) -instance Participant Window StateData where - type Mesg Window StateData = WindowMessage +instance Participant Main.Window StateData where + type Mesg Main.Window StateData = WindowMessage partSubscribers (Window t) = generalSubscribers t @@ -59,7 +60,7 @@ instance Participant Window StateData where partUnSubscribe (Window t) = generalUnSubscribe t -instance SDLSubsystem Window StateData where +instance SDLSubsystem Main.Window StateData where consumeSDLEvents = consumeSDLWindowEvents instance Participant Mouse StateData where @@ -176,7 +177,6 @@ handle es = do =<< consumeSDLEvents c =<< consumeSDLEvents d es mapM_ (\e -> liftIO $ logIO Verbose $ "LEFTOVER: " ++ show e) leftovers - return () update _ = return () diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index ae9fa98..4f14c51 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -152,89 +152,77 @@ handle es = do update :: Double -> Affection UserData () update _ = do ud <- getAffection - -- now <- getElapsedTime - when (True) $ do - 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 - then if 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 return (0, foodMat ud M.! (y, x), 1) - else if life == 3 - then if 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 - ) - 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 - } + 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 (\a acc -> acc + a) 0 mat + res = foldr (flip (+)) 0 mat draw :: Affection UserData () draw = do @@ -250,10 +238,10 @@ draw = do -- logIO A.Debug $ show mult beginPath ctx rect ctx (fromIntegral $ x * 10) (fromIntegral $ y * 10) 10 10 - if (mult == 1) - then do + if mult == 1 + then fillColor ctx (rgba 255 255 255 255) - else do + else fillColor ctx (rgba 0 (fromIntegral $ 25 * (foodMat ud M.! (x+1, y+1))) 0 255) fill ctx ) [0..3599] diff --git a/examples/example01/Types.hs b/examples/example01/Types.hs index 9fea586..3be0da5 100644 --- a/examples/example01/Types.hs +++ b/examples/example01/Types.hs @@ -22,16 +22,16 @@ data UserData = UserData } data Subsystems = Subsystems - { subWindow :: Window - , subKeyboard :: Keyboard + { subWindow :: Types.Window + , subKeyboard :: Types.Keyboard } newtype Window = Window (TVar [(UUID, WindowMessage -> Affection UserData ())]) newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection UserData ())]) -instance Participant Window UserData where - type Mesg Window UserData = WindowMessage +instance Participant Types.Window UserData where + type Mesg Types.Window UserData = WindowMessage partSubscribers (Window t) = do subTups <- liftIO $ readTVarIO t @@ -41,7 +41,7 @@ instance Participant Window UserData where partUnSubscribe (Window t) = generalUnSubscribe t -instance SDLSubsystem Window UserData where +instance SDLSubsystem Types.Window UserData where consumeSDLEvents = consumeSDLWindowEvents instance Participant Keyboard UserData where diff --git a/src/Affection.hs b/src/Affection.hs index a77e4e2..1daf842 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -21,11 +21,8 @@ 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 @@ -88,10 +85,7 @@ withAffection AffectionConfig{..} = do (_, nState) <- runStateT ( A.runState $ do liftIO $ logIO Debug "Starting Loop" preLoop - whileM_ (do - current <- get - return $ not $ A.quitEvent current - ) + whileM_ (not . A.quitEvent current <$> get) (do -- get state ad <- get diff --git a/src/Affection/Logging.hs b/src/Affection/Logging.hs index e64ed7c..f18b30e 100644 --- a/src/Affection/Logging.hs +++ b/src/Affection/Logging.hs @@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} -module Affection.Logging where --- ^ This module defines the logging capability of Affection, whis is derived +-- | This module defines the logging capability of Affection, whis is derived -- from "Debug.Trace". +module Affection.Logging where import Debug.Trace diff --git a/src/Affection/MessageBus.hs b/src/Affection/MessageBus.hs index d22edbb..6ae83e4 100644 --- a/src/Affection/MessageBus.hs +++ b/src/Affection/MessageBus.hs @@ -5,6 +5,5 @@ module Affection.MessageBus import Affection.MessageBus.Class as M import Affection.MessageBus.Message as M -import Affection.MessageBus.Util as M import Affection.MessageBus.Message as Msg diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 16ca2bc..ffa99a6 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -3,7 +3,11 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ExistentialQuantification #-} -module Affection.MessageBus.Class where +module Affection.MessageBus.Class + ( Participant(..) + , genUUID + , UUID + ) where import Affection.MessageBus.Message import Affection.Types diff --git a/src/Affection/MessageBus/Message/JoystickMessage.hs b/src/Affection/MessageBus/Message/JoystickMessage.hs index eb5c0e2..13fe3f2 100644 --- a/src/Affection/MessageBus/Message/JoystickMessage.hs +++ b/src/Affection/MessageBus/Message/JoystickMessage.hs @@ -7,21 +7,21 @@ module Affection.MessageBus.Message.JoystickMessage , SDL.JoyButtonState , SDL.JoyDeviceConnection -- | Number exports - , Word8(..) - , Int16(..) - , Int32(..) + , Word8 + , Int16 + , Int32 ) where import Affection.MessageBus.Message.Class -import Data.Word (Word8(..)) -import Data.Int (Int32(..), Int16(..)) +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 +-- | Datatype for handling all possible joystick events handed over from sdl2 data JoystickMessage = MsgJoystickAxis -- ^ Movement of a Joystick axis { msgJAWhen :: Double -- ^ Time of event diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index 0435650..37abc84 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -10,8 +10,8 @@ module Affection.MessageBus.Message.MouseMessage import Affection.MessageBus.Message.Class -import Data.Word (Word8(..)) -import Data.Int (Int32(..)) +import Data.Word (Word8) +import Data.Int (Int32) import qualified SDL diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index 806b46c..107c7b2 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -6,7 +6,7 @@ module Affection.MessageBus.Message.WindowMessage import Affection.MessageBus.Message.Class -import Data.Int (Int32(..)) +import Data.Int (Int32) import qualified SDL diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index 69691b6..217be1d 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -5,14 +5,12 @@ module Affection.Subsystems.AffectionJoystick where import Affection.MessageBus -import Affection.Subsystems.Class import Affection.Types import Affection.Util import Affection.Logging import Control.Monad (filterM) import Control.Monad.IO.Class (liftIO) -import Control.Concurrent.STM import qualified Data.Vector as V @@ -86,8 +84,8 @@ joystickAutoConnect -- ^ Returns a joystick descriptor, if successful joystickAutoConnect (MsgJoystickDevice _ which SDL.JoyDeviceAdded) = liftIO $ do [descr] <- V.toList <$> - (return . V.filter (\(SDL.JoystickDevice _ id) -> id == CInt which) - =<< SDL.availableJoysticks) + (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 @@ -103,7 +101,7 @@ 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 -> return . (which ==) =<< SDL.getJoystickID j) js + [d] <- filterM ((which ==) <$> SDL.getJoystickID) js logIO Verbose $ "disconnected joysticks: " ++ show d logIO Verbose $ "Disconnecting Joystick " ++ show which SDL.closeJoystick d diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index 9d23df9..c2a1b58 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -7,10 +7,6 @@ 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 diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index ace9010..01861f3 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -5,13 +5,9 @@ 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 diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 2ec60ae..a9c2622 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -9,10 +9,6 @@ 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 diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index 4509332..d9504c5 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -3,7 +3,6 @@ module Affection.Subsystems.Class where import Affection.Types -import Affection.MessageBus import qualified SDL diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 0785d43..32210de 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -1,26 +1,25 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-} module Affection.Types - ( Affection(..) + ( Affection , AffectionConfig(..) , AffectionData(..) - , AffectionStateInner(..) + , AffectionStateInner , AffectionState(..) , InitComponents(..) - , Angle(..) + , Angle -- | SDL reexports , SDL.WindowConfig(..) , SDL.WindowMode(..) , SDL.EventPayload(..) - , SDL.InitFlags(..) - , SDL.Window(..) - , SDL.GLContext(..) + , 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 Control.Monad.IO.Class import Control.Monad.State.Strict @@ -28,10 +27,6 @@ import qualified Control.Monad.Parallel as MP import System.Clock (TimeSpec) -import Foreign.Ptr (Ptr) - -import Affection.MessageBus.Message - -- | Configuration for the aplication. needed at startup. data AffectionConfig us = AffectionConfig { initComponents :: InitComponents @@ -71,8 +66,6 @@ data AffectionData us = AffectionData , glContext :: SDL.GLContext -- ^ OpenGL rendering context , screenMode :: SDL.WindowMode -- ^ current screen mode , 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) diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 16fbbf8..e5bcab9 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -19,9 +19,7 @@ preHandleEvents 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 @@ -40,12 +38,10 @@ delaySec dur = SDL.delay (fromIntegral $ dur * 1000) -- | Get time since start but always the same in the current tick. getElapsedTime :: Affection us Double -getElapsedTime = - elapsedTime <$> get +getElapsedTime = gets elapsedTime getDelta :: Affection us Double -getDelta = - deltaTime <$> get +getDelta = gets deltaTime quit :: Affection us () quit = do From 3935b7ddb9689d55e39249c670b3e65c87204b28 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Sep 2018 17:12:18 +0200 Subject: [PATCH 228/261] make haddockable --- .../MessageBus/Message/JoystickMessage.hs | 37 ++++++----- .../MessageBus/Message/KeyboardMessage.hs | 3 +- .../MessageBus/Message/MouseMessage.hs | 9 ++- .../MessageBus/Message/WindowMessage.hs | 65 +++++++++++-------- 4 files changed, 68 insertions(+), 46 deletions(-) diff --git a/src/Affection/MessageBus/Message/JoystickMessage.hs b/src/Affection/MessageBus/Message/JoystickMessage.hs index 13fe3f2..45fb34e 100644 --- a/src/Affection/MessageBus/Message/JoystickMessage.hs +++ b/src/Affection/MessageBus/Message/JoystickMessage.hs @@ -23,34 +23,39 @@ import Linear (V2(..)) -- | Datatype for handling all possible joystick events handed over from sdl2 data JoystickMessage - = MsgJoystickAxis -- ^ Movement of a Joystick axis + -- | Movement of a Joystick axis + = MsgJoystickAxis { msgJAWhen :: Double -- ^ Time of event , msgJAWhich :: Int32 -- ^ Joystick identifier , msgJAAxis :: Word8 -- ^ Axis identifier , msgJAValue :: Int16 -- ^ Axis value } - | MsgJoystickBall -- ^ Movement of a joystick ball controller + -- | 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 } - | MsgJoystickHat -- ^ Movement of joystick hat controller - { msgJHWhen :: Double -- Time of event - , msgJHWhich :: Int32 -- Joystick identifier - , msgJHHat :: Word8 -- Hat identifier - , msgJHPosition :: SDL.JoyHatPosition -- New hat 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 } - | MsgJoystickButton -- ^ Joystick button event - { msgJBWhen :: Double -- Time of event - , msgJBWhich :: Int32 -- Joystick identifier - , msgJBButton :: Word8 -- Button identifier - , msgJBState :: SDL.JoyButtonState -- New Button state + -- | Joystick button event + | MsgJoystickButton + { msgJBWhen :: Double -- ^ Time of event + , msgJBWhich :: Int32 -- ^ Joystick identifier + , msgJBButton :: Word8 -- ^ Button identifier + , msgJBState :: SDL.JoyButtonState -- ^ New Button state } - | MsgJoystickDevice -- ^ Joystick device event - { msgJDWhen :: Double -- Time of event - , msgJDWhich :: Int32 -- Joystick identifier - , msgJDConnection :: SDL.JoyDeviceConnection -- Connection status + -- | Joystick device event + | MsgJoystickDevice + { msgJDWhen :: Double -- ^ Time of event + , msgJDWhich :: Int32 -- ^ Joystick identifier + , msgJDConnection :: SDL.JoyDeviceConnection -- ^ Connection status } deriving (Show) diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs index 30edb13..9caef1f 100644 --- a/src/Affection/MessageBus/Message/KeyboardMessage.hs +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -12,7 +12,8 @@ import qualified SDL -- | Dataatype for handling all keyboard events haded down from SDL2 data KeyboardMessage - = MsgKeyboardEvent -- ^ Arbitrary Keyboard event + -- | Arbitrary Keyboard event + = MsgKeyboardEvent { msgKbdWhen :: Double -- ^ Message time , msgKbdWindow :: Maybe SDL.Window -- ^ Affected Window , msgKbdKeyMotion :: SDL.InputMotion -- ^ Input motion of button (pressed/released) diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index 37abc84..a13baff 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -19,7 +19,8 @@ import Linear (V2(..)) -- Datatype for handling mouse events handed down from SDL2 data MouseMessage - = MsgMouseMotion -- ^ Mouse motion event + -- | Mouse motion event + = MsgMouseMotion { msgMMWhen :: Double -- ^ Message time , msgMMWindow :: Maybe SDL.Window -- ^ Focused window (if any) , msgMMWhich :: SDL.MouseDevice -- ^ Mouse device identifier @@ -27,7 +28,8 @@ data MouseMessage , msgMMPos :: V2 Int32 -- ^ Absolute mouse positiom , msgMMRelMotion :: V2 Int32 -- ^ Mouse movement relative to previous position } - | MsgMouseButton -- ^ Mouse button event + -- | Mouse button event + | MsgMouseButton { msgMBWhen :: Double -- ^ Message time , msgMBWindow :: Maybe SDL.Window -- ^ Focused window (if any) , msgMBMotion :: SDL.InputMotion -- ^ Button's input motion @@ -36,7 +38,8 @@ data MouseMessage , msgMBClicks :: Word8 -- ^ Number of clicks , msgMBPos :: V2 Int32 -- ^ Absolute mouse position } - | MsgMouseWheel -- ^ Mouse wheel event + -- | Mouse wheel event + | MsgMouseWheel { msgMWWhen :: Double -- ^ Message time , msgMWWhindow :: Maybe SDL.Window -- ^ Focused window (if any) , msgMWWhich :: SDL.MouseDevice -- ^ Mouse device identifier diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index 107c7b2..994f023 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -14,69 +14,82 @@ import Linear (V2(..)) -- | Datatype for handling Window events handed down rom SDL2 data WindowMessage - = MsgWindowShow -- ^ Window show event + -- | Window show event + = MsgWindowShow { msgWSWhen :: Double -- ^ Message time , msgWSWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowHide -- ^ Window hide event + -- | Window hide event + | MsgWindowHide { msgWHWhen :: Double -- ^ Message time , msgWHWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowExpose -- ^ Window expose event + -- | Window expose event + | MsgWindowExpose { msgWEWhen :: Double -- ^ Message time , msgWEWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowMove -- ^ Window move event + -- | Window move event + | MsgWindowMove { msgWMWhen :: Double -- ^ Message time , msgWMWindow :: SDL.Window -- ^ Window identifier , msgWMNewPos :: V2 Int32 -- ^ New absolute window position } - | MsgWindowResize -- ^ Window resize event + -- | Window resize event + | MsgWindowResize { msgWRWhen :: Double -- ^ Message time , msgWRWindow :: SDL.Window -- ^ Window identifier , msgWRNewSize :: V2 Int32 -- ^ New absolute window size } - | MsgWindowSizeChange -- ^ Window size change event - { msgWSCWhen :: Double -- ^ Message time - , msgWSCWindow :: SDL.Window -- ^ Window identifier - , msgWSCNewSize :: V2 Int32 -- ^ New absolute window size + -- | Window size change event + | MsgWindowSizeChange + { msgWSCWhen :: Double -- ^ Message time + , msgWSCWindow :: SDL.Window -- ^ Window identifier + , msgWSCNewSize :: V2 Int32 -- ^ New absolute window size } - | MsgWindowMinimize -- ^ Window minimize event - { msgWMinWhen :: Double -- ^ Message time + -- | Window minimize event + | MsgWindowMinimize + { msgWMinWhen :: Double -- ^ Message time , msgWMinWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowMaximize -- ^ Window maximize event - { msgWMaxWhen :: Double -- ^ Message time + -- | Window maximize event + | MsgWindowMaximize + { msgWMaxWhen :: Double -- ^ Message time , msgWMaxWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowRestore -- ^ Window restore event - { msgWRestWhen :: Double -- ^ Message time + -- | Window restore event + | MsgWindowRestore + { msgWRestWhen :: Double -- ^ Message time , msgWRestWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowGainMouseFocus -- ^ Window gain mouse focus event - { msgWGMFWhen :: Double -- ^ Message Time + -- | Window gain mouse focus event + | MsgWindowGainMouseFocus + { msgWGMFWhen :: Double -- ^ Message Time , msgWGMFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowLoseMouseFocus -- ^ Window lose mouse focus event - { msgWLMFWhen :: Double -- ^ Message Time + -- | Window lose mouse focus event + | MsgWindowLoseMouseFocus + { msgWLMFWhen :: Double -- ^ Message Time , msgWLMFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowGainKeyboardFocus -- ^ Window gain keyboard focus event - { msgWGKFWhen :: Double -- ^ Message time + -- | Window gain keyboard focus event + | MsgWindowGainKeyboardFocus + { msgWGKFWhen :: Double -- ^ Message time , msgWGKFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowLoseKeyboardFocus -- ^ Window lose keyboard focus event - { msgWLKFWhen :: Double -- ^ Message time + -- | Window lose keyboard focus event + | MsgWindowLoseKeyboardFocus + { msgWLKFWhen :: Double -- ^ Message time , msgWLKFWindow :: SDL.Window -- ^ Window identifier } - | MsgWindowClose -- ^ Window close event - { msgWCWhen :: Double -- ^ Message time + -- | Window close event + | MsgWindowClose + { msgWCWhen :: Double -- ^ Message time , msgWCWindow :: SDL.Window -- ^ Window identifier } deriving (Show) instance Message WindowMessage where - -- msgTime (MsgEngineReady t) = t msgTime (MsgWindowShow t _) = t msgTime (MsgWindowHide t _) = t msgTime (MsgWindowExpose t _) = t From f822b5cabb2050c8a06a7c396a4f258c03545ef1 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Sep 2018 17:27:35 +0200 Subject: [PATCH 229/261] finishing cleanup --- src/Affection.hs | 2 +- src/Affection/MessageBus/Class.hs | 3 ++- src/Affection/Subsystems/AffectionJoystick.hs | 4 ++-- src/Affection/Util.hs | 4 +++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 1daf842..bcfc44c 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -85,7 +85,7 @@ withAffection AffectionConfig{..} = do (_, nState) <- runStateT ( A.runState $ do liftIO $ logIO Debug "Starting Loop" preLoop - whileM_ (not . A.quitEvent current <$> get) + whileM_ (not . A.quitEvent <$> get) (do -- get state ad <- get diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index ffa99a6..bca01e1 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -21,7 +21,8 @@ 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 - type Mesg prt us :: * -- ^ Message datatype + -- | Message datatype + type Mesg prt us :: * -- | Function to get the list of subscribers from the participant partSubscribers diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index 217be1d..e9e1fae 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -95,13 +95,13 @@ joystickAutoConnect _ = return Nothing joystickAutoDisconnect :: [SDL.Joystick] -- ^ List of Joystick descriptors -> JoystickMessage -- ^ Any 'JoystickMessage' will do, but listens - -- specifically to 'MsgJoystickDevice' messages + -- 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 ((which ==) <$> SDL.getJoystickID) js + [d] <- filterM (\j -> fmap (== which) (SDL.getJoystickID j)) js logIO Verbose $ "disconnected joysticks: " ++ show d logIO Verbose $ "Disconnecting Joystick " ++ show which SDL.closeJoystick d diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index e5bcab9..f8e13a7 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -12,7 +12,7 @@ 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 @@ -40,9 +40,11 @@ delaySec dur = SDL.delay (fromIntegral $ dur * 1000) getElapsedTime :: Affection us Double getElapsedTime = gets elapsedTime +-- | Get delta time (time elapsed from last frame) getDelta :: Affection us Double getDelta = gets deltaTime +-- | Quit the engine loop quit :: Affection us () quit = do ad <- get From fa2501021c50054e80465f4300ea2371d6d85db2 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 10 Oct 2018 14:13:21 +0200 Subject: [PATCH 230/261] fix joystick disconnect crash --- src/Affection/Subsystems/AffectionJoystick.hs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index e9e1fae..05d40ca 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -101,11 +101,16 @@ 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 + 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 + SDL.closeJoystick (head d) + njoys <- filterM (\j -> return $ head d /= j) js + logIO Verbose $ "returning joysticks: " ++ show njoys + return njoys + else do + logIO Error $ "Error while disconnecting Joystick" + return js joystickAutoDisconnect js _ = return js From 6f05969fab759a9852b47ccaf63e1b8fae04525a Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 25 Dec 2018 18:12:14 +0100 Subject: [PATCH 231/261] modernize example01 --- examples/example01/Main.hs | 82 ++++++++++++++++++++----------------- examples/example01/Types.hs | 1 - 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 4f14c51..08fe6df 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -75,7 +75,6 @@ load = do , foodMat = fromList 60 60 (repeat 10) , timeMat = M.zero 60 60 , nano = nanoCtx - , lastUpdate = 0 } pre :: Affection UserData () @@ -84,26 +83,33 @@ pre = do _ <- 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) windowResize + _ <- partSubscribe (subWindow $ subsystems sd) (fitViewport (600/600)) now <- getElapsedTime putAffection sd - { lastUpdate = floor now - } + +toggleFullScreen :: KeyboardMessage -> Affection UserData () +toggleFullScreen (MsgKeyboardEvent _ _ SDL.Pressed False sym) + | SDL.keysymKeycode sym == SDL.KeycodeF11 = toggleScreen + | otherwise = return () +toggleFullScreen _ = return () exitOnEsc :: KeyboardMessage -> Affection UserData () -exitOnEsc (MsgKeyboardEvent _ _ _ _ sym) = +exitOnEsc (MsgKeyboardEvent _ _ SDL.Pressed False sym) = case SDL.keysymKeycode sym of SDL.KeycodeEscape -> do liftIO $ logIO A.Debug "Yo dog I heard..." quit _ -> return () +exitOnEsc _ = return () reloadOnR :: KeyboardMessage -> Affection UserData () reloadOnR (MsgKeyboardEvent _ _ _ _ sym) = case SDL.keysymKeycode sym of SDL.KeycodeR -> reload _ -> return () +reloadOnR _ = return () reload :: Affection UserData () reload = do @@ -115,7 +121,6 @@ reload = do { lifeMat = fullMatrix , foodMat = fromList 60 60 (repeat 10) , timeMat = M.zero 60 60 - , lastUpdate = floor now } showFPS :: KeyboardMessage -> Affection UserData () @@ -125,6 +130,7 @@ showFPS (MsgKeyboardEvent _ _ _ _ sym) = dt <- getDelta liftIO $ logIO A.Debug $ "FPS: " ++ show (1 / dt) _ -> return () +showFPS _ = return () exitOnWindowClose :: WindowMessage -> Affection UserData () exitOnWindowClose wm = @@ -133,15 +139,7 @@ exitOnWindowClose wm = 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 () +exitOnWindowClose _ = return () handle :: [SDL.EventPayload] -> Affection UserData () handle es = do @@ -157,57 +155,66 @@ update _ = do 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 (lifeMat ud) <|> submatrix 60 60 1 2 (lifeMat ud)) <-> - submatrix 1 2 60 60 (lifeMat ud) <|> submatrix 1 2 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 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) + (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 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) + (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 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) + (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) 60 60 (lifeMat ud)) <|> - submatrix (y - 1) (y + 1) 1 2 (lifeMat ud) + (submatrix (y - 1) (y + 1) 1 2 (lifeMat ud)) | y == 1 = - submatrix 60 60 (x - 1) (x + 1) (lifeMat ud) + (submatrix 60 60 (x - 1) (x + 1) (lifeMat ud)) <-> - submatrix 1 2 (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) 59 60 (lifeMat ud)) <|> - submatrix (y - 1) (y + 1) 1 1 (lifeMat ud) + (submatrix (y - 1) (y + 1) 1 1 (lifeMat ud)) | y == 60 = - submatrix 59 60 (x -1 ) (x + 1) (lifeMat ud) + (submatrix 59 60 (x -1 ) (x + 1) (lifeMat ud)) <-> - submatrix 1 1 (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) + (submatrix (y - 1) (y + 1) (x - 1) (x + 1) (lifeMat ud)) 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 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 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) 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) + else min 10 (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) + newFoodMat = fromList 60 60 (map (\(_, x, _) -> x) newList) + newTimeMat = fromList 60 60 (map (\(_, _, x) -> x) newList) if newLifeMat == M.zero 60 60 then reload @@ -216,7 +223,6 @@ update _ = do { lifeMat = newLifeMat , foodMat = newFoodMat , timeMat = newTimeMat - -- , lastUpdate = floor now } countLife :: Matrix Word -> Word diff --git a/examples/example01/Types.hs b/examples/example01/Types.hs index 3be0da5..217edfa 100644 --- a/examples/example01/Types.hs +++ b/examples/example01/Types.hs @@ -18,7 +18,6 @@ data UserData = UserData , timeMat :: Matrix Word , subsystems :: Subsystems , nano :: Context - , lastUpdate :: Int } data Subsystems = Subsystems From 91c70c6ec62e407bd6d0233fbc2c64bafc3b6cdc Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 26 Dec 2018 23:19:20 +0100 Subject: [PATCH 232/261] add new example --- affection.cabal | 23 ++++ examples/example01/Main.hs | 2 +- examples/example02/Main.hs | 260 ++++++++++++++++++++++++++++++++++++ examples/example02/Types.hs | 77 +++++++++++ 4 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 examples/example02/Main.hs create mode 100644 examples/example02/Types.hs diff --git a/affection.cabal b/affection.cabal index 378b5c5..a5d0918 100644 --- a/affection.cabal +++ b/affection.cabal @@ -152,3 +152,26 @@ executable example01 , deepseq else buildable: False + +-- Another small game of life implementation +executable example02 + hs-source-dirs: examples/example02 + 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 diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 08fe6df..9dfd6c2 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -221,7 +221,7 @@ update _ = do else putAffection ((newLifeMat, newFoodMat, newTimeMat) `deepseq` ud) { lifeMat = newLifeMat - , foodMat = newFoodMat + --, foodMat = newFoodMat , timeMat = newTimeMat } diff --git a/examples/example02/Main.hs b/examples/example02/Main.hs new file mode 100644 index 0000000..7a32bb2 --- /dev/null +++ b/examples/example02/Main.hs @@ -0,0 +1,260 @@ +{-# 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 + +maxFood :: Word +maxFood = 255 + +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 maxFood) + , timeMat = M.zero 60 60 + , nano = nanoCtx + } + +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 + +toggleFullScreen :: KeyboardMessage -> Affection UserData () +toggleFullScreen (MsgKeyboardEvent _ _ SDL.Pressed False sym) + | SDL.keysymKeycode sym == SDL.KeycodeF11 = toggleScreen + | otherwise = return () +toggleFullScreen _ = return () + +exitOnEsc :: KeyboardMessage -> Affection UserData () +exitOnEsc (MsgKeyboardEvent _ _ SDL.Pressed False sym) = + case SDL.keysymKeycode sym of + SDL.KeycodeEscape -> do + liftIO $ logIO A.Debug "Yo dog I heard..." + quit + _ -> return () +exitOnEsc _ = return () + +reloadOnR :: KeyboardMessage -> Affection UserData () +reloadOnR (MsgKeyboardEvent _ _ _ _ sym) = + case SDL.keysymKeycode sym of + SDL.KeycodeR -> reload + _ -> return () +reloadOnR _ = 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 maxFood) + , timeMat = M.zero 60 60 + } + +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 () +showFPS _ = return () + +exitOnWindowClose :: WindowMessage -> Affection UserData () +exitOnWindowClose wm = + case wm of + MsgWindowClose _ _ -> do + liftIO $ logIO A.Debug "I heard another one..." + quit + _ -> return () +exitOnWindowClose _ = 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 + 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) + 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..3599] + let newLifeMat = fromList 60 60 (map (\(x, _, _) -> x) newList) + newFoodMat = fromList 60 60 (map (\(_, x, _) -> x) newList) + 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 + } + +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 $ (255 `div` maxFood) * (foodMat ud M.! (x+1, y+1))) 0 255) + fill ctx + ) [0..3599] + restore (nano ud) + endFrame (nano ud) + +clean _ = return () diff --git a/examples/example02/Types.hs b/examples/example02/Types.hs new file mode 100644 index 0000000..217edfa --- /dev/null +++ b/examples/example02/Types.hs @@ -0,0 +1,77 @@ +{-# 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 + } + +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 From 486a7ef01236d79ea77d3a31f58ba32e97ba1e68 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 6 Jan 2019 03:14:29 +0100 Subject: [PATCH 233/261] fix build system --- shell.nix | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/shell.nix b/shell.nix index 00f03df..e616402 100644 --- a/shell.nix +++ b/shell.nix @@ -11,7 +11,7 @@ let mkDerivation { pname = "nanovg"; version = "0.6.0.0"; - sha256 = "e35a2772f2a2e71916013cab70eeb8eedd6f66b5490ddf7ce237c8c76895d496"; + src = ../nanovg-hs; isLibrary = true; isExecutable = true; doCheck = false; @@ -25,6 +25,46 @@ let 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 @@ -48,7 +88,7 @@ let }) {}; f = { mkDerivation, base, bytestring, clock, containers, deepseq - , glib, linear, matrix, monad-loops, monad-parallel, mtl + , linear, matrix, monad-loops, monad-parallel, mtl , OpenGL, random, stdenv, stm, text, uuid, vector }: mkDerivation { From 510e7e922dfc50198881ad35bee4bf146f07bbe8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 30 Jan 2019 12:00:28 +0100 Subject: [PATCH 234/261] make affection multi-window capable --- examples/example00/Main.hs | 13 +++++++++---- examples/example01/Main.hs | 17 +++++++++++------ examples/example02/Main.hs | 17 +++++++++++------ src/Affection.hs | 34 +++++++++++++++++----------------- src/Affection/Types.hs | 24 +++++++++++++++++++----- src/Affection/Util.hs | 4 ++-- 6 files changed, 69 insertions(+), 40 deletions(-) diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs index c496e67..52f1bd9 100644 --- a/examples/example00/Main.hs +++ b/examples/example00/Main.hs @@ -105,11 +105,16 @@ main = do let conf = AffectionConfig { initComponents = All , windowTitle = "affection: example00" - , windowConfig = SDL.defaultWindow - { SDL.windowOpenGL = Just SDL.defaultOpenGL - { SDL.glProfile = SDL.Core SDL.Normal 3 3 + , windowConfigs = + [ + ( 0 + , SDL.defaultWindow + { SDL.windowOpenGL = Just SDL.defaultOpenGL + { SDL.glProfile = SDL.Core SDL.Normal 3 3 + } } - } + ) + ] , initScreenMode = SDL.Windowed , canvasSize = Nothing , loadState = load diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 9dfd6c2..5c1fabe 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -36,13 +36,18 @@ main = do let conf = AffectionConfig { initComponents = All , windowTitle = "affection: example01" - , windowConfig = SDL.defaultWindow - { SDL.windowOpenGL = Just SDL.defaultOpenGL - { SDL.glProfile = SDL.Core SDL.Normal 3 3 + , windowConfigs = + [ + ( 0 + , SDL.defaultWindow + { SDL.windowOpenGL = Just SDL.defaultOpenGL + { SDL.glProfile = SDL.Core SDL.Normal 3 3 + } + , SDL.windowInitialSize = SDL.V2 600 600 + , SDL.windowResizable = True } - , SDL.windowInitialSize = SDL.V2 600 600 - , SDL.windowResizable = True - } + ) + ] , initScreenMode = SDL.Windowed , canvasSize = Nothing , loadState = load diff --git a/examples/example02/Main.hs b/examples/example02/Main.hs index 7a32bb2..028e4ad 100644 --- a/examples/example02/Main.hs +++ b/examples/example02/Main.hs @@ -39,13 +39,18 @@ main = do let conf = AffectionConfig { initComponents = All , windowTitle = "affection: example01" - , windowConfig = SDL.defaultWindow - { SDL.windowOpenGL = Just SDL.defaultOpenGL - { SDL.glProfile = SDL.Core SDL.Normal 3 3 + , windowConfigs = + [ + ( 0 + , SDL.defaultWindow + { SDL.windowOpenGL = Just SDL.defaultOpenGL + { SDL.glProfile = SDL.Core SDL.Normal 3 3 + } + , SDL.windowInitialSize = SDL.V2 600 600 + , SDL.windowResizable = True } - , SDL.windowInitialSize = SDL.V2 600 600 - , SDL.windowResizable = True - } + ) + ] , initScreenMode = SDL.Windowed , canvasSize = Nothing , loadState = load diff --git a/src/Affection.hs b/src/Affection.hs index bcfc44c..3d79aae 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -53,16 +53,19 @@ withAffection AffectionConfig{..} = do when (renderQuality /= SDL.ScaleLinear) $ logIO Warn "Linear texture filtering not enabled!" -- construct window - liftIO $ logIO Debug "Creating Window" - window <- SDL.createWindow windowTitle windowConfig - SDL.showWindow window + liftIO $ logIO Debug "Creating Window(s)" + windows <- zip (map fst windowConfigs) <$> + mapM + (\wc -> SDL.createWindow windowTitle (snd wc)) + windowConfigs + mapM_ (SDL.showWindow . snd) windows _ <- 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 + 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 -- SDL.swapInterval $= SDL.SynchronizedUpdates -- <- causes Problems with windows liftIO $ logIO Debug "Getting Time" -- get current time @@ -71,11 +74,8 @@ withAffection AffectionConfig{..} = do initContainer <- (\x -> AffectionData { quitEvent = False , userState = x - , drawWindow = window - , glContext = context - , drawDimensions = case canvasSize of - Just (cw, ch) -> (cw, ch) - Nothing -> (w, h) + , drawWindows = windows + , glContext = contexts , screenMode = initScreenMode , elapsedTime = 0 , deltaTime = 0 @@ -111,7 +111,7 @@ withAffection AffectionConfig{..} = do drawLoop liftIO GL.flush -- actual displaying of newly drawn frame - SDL.glSwapWindow window + mapM_ (SDL.glSwapWindow . snd) windows -- save new time ad3 <- get when (sysTime ad == sysTime ad3) ( @@ -124,7 +124,7 @@ withAffection AffectionConfig{..} = do liftIO $ logIO Debug "Loop ended. Cleaning" cleanUp $ userState nState liftIO $ logIO Debug "Destroying Window" - SDL.glDeleteContext context - SDL.destroyWindow window + mapM_ (SDL.glDeleteContext . snd) contexts + mapM_ (SDL.destroyWindow . snd) windows -- SDL.quit -- <- This causes segfaults depending on hardware liftIO $ logIO Debug "This is the end" diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 32210de..9cc11e7 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -33,8 +33,13 @@ data AffectionConfig us = AffectionConfig -- ^ SDL components to initialize at startup , windowTitle :: T.Text -- ^ Window title - , windowConfig :: SDL.WindowConfig - -- ^ Window configuration + , windowConfigs :: + [ + ( Word -- ^ Window identifier + , SDL.WindowConfig -- ^ Window config for given window + ) + ] + -- ^ Window configurations , canvasSize :: Maybe (Int, Int) -- ^ size of the texture canvas , initScreenMode :: SDL.WindowMode @@ -62,10 +67,19 @@ data InitComponents data AffectionData us = AffectionData { quitEvent :: Bool -- ^ Loop breaker. , userState :: us -- ^ State data provided by user - , drawWindow :: SDL.Window -- ^ SDL window - , glContext :: SDL.GLContext -- ^ OpenGL rendering context + , 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 - , drawDimensions :: (Int, Int) -- ^ Dimensions of target surface , 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) diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index f8e13a7..6c35fdf 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -57,10 +57,10 @@ toggleScreen = do ad <- get newMode <- case screenMode ad of SDL.Windowed -> do - SDL.setWindowMode (drawWindow ad) SDL.FullscreenDesktop + mapM_ (flip SDL.setWindowMode SDL.FullscreenDesktop . snd) (drawWindows ad) return SDL.FullscreenDesktop SDL.FullscreenDesktop -> do - SDL.setWindowMode (drawWindow ad) SDL.Windowed + mapM_ (flip SDL.setWindowMode SDL.Windowed . snd) (drawWindows ad) return SDL.Windowed x -> do liftIO $ logIO Warn ("Unexpected Screen mode: " ++ show x) From b7e776f8239daa5f6a06562ebf36431e021ca4e9 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 23 Apr 2019 23:52:01 +0200 Subject: [PATCH 235/261] hey! I'm ignoring you! --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index df67c56..fa507cf 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,8 @@ dist/ *.hp *.ps *.swp +.ghc* +cabal.project.local* +dist*/ +report.html +*.bak From 2ac3b007a95db454500e7992b967402a203fd179 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 29 Apr 2019 18:38:52 +0200 Subject: [PATCH 236/261] name module --- examples/example00/Main.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs index c496e67..30d8303 100644 --- a/examples/example00/Main.hs +++ b/examples/example00/Main.hs @@ -1,6 +1,8 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} +module Main where + import Affection as A import Control.Concurrent.STM From 8ff4b216c31d18e45ea4ba0980e649b40ae5f720 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 28 Oct 2019 16:55:26 +0100 Subject: [PATCH 237/261] bump sdl2 version --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index a5d0918..b0276a8 100644 --- a/affection.cabal +++ b/affection.cabal @@ -99,7 +99,7 @@ library ghc-options: -Wall -- Other library packages from which modules are imported. build-depends: base >=4.9 && < 5 - , sdl2 >= 2.4 && < 2.5 + , sdl2 >= 2.5 && < 2.6 , linear , text , mtl From 25fd9acffd0438c9fb90e395cba53aaae896625e Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 28 Oct 2019 17:10:56 +0100 Subject: [PATCH 238/261] push examples to new sdl version --- examples/example00/Main.hs | 6 ++++-- examples/example01/Main.hs | 5 +++-- examples/example02/Main.hs | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs index a56f688..31b3a49 100644 --- a/examples/example00/Main.hs +++ b/examples/example00/Main.hs @@ -12,6 +12,7 @@ import Control.Monad (when) import qualified SDL hiding (Window(..)) import Data.Maybe (isJust, fromJust) +import Data.String data StateData = StateData { sdSubs :: Subsystems @@ -111,7 +112,7 @@ main = do [ ( 0 , SDL.defaultWindow - { SDL.windowOpenGL = Just SDL.defaultOpenGL + { SDL.windowGraphicsContext = SDL.OpenGLContext SDL.defaultOpenGL { SDL.glProfile = SDL.Core SDL.Normal 3 3 } } @@ -183,7 +184,8 @@ handle es = do =<< 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 () diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 5c1fabe..28a5dd9 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -14,6 +14,7 @@ import Control.DeepSeq (deepseq) import Data.Matrix as M import qualified Data.Set as S +import Data.String import System.Random (randomRIO) @@ -40,7 +41,7 @@ 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 @@ -133,7 +134,7 @@ 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 () diff --git a/examples/example02/Main.hs b/examples/example02/Main.hs index 028e4ad..30454ed 100644 --- a/examples/example02/Main.hs +++ b/examples/example02/Main.hs @@ -14,6 +14,7 @@ import Control.DeepSeq (deepseq) import Data.Matrix as M import qualified Data.Set as S +import Data.String import System.Random (randomRIO) @@ -43,7 +44,7 @@ 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 @@ -136,7 +137,7 @@ 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 () From d73609297c3896da536cd8d7bb4f16e3faf395a3 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 28 Oct 2019 17:11:27 +0100 Subject: [PATCH 239/261] move logging to text --- src/Affection/Logging.hs | 22 ++++++++++--------- src/Affection/MessageBus/Class.hs | 4 +++- src/Affection/Subsystems/AffectionJoystick.hs | 13 ++++++----- src/Affection/Util.hs | 4 +++- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Affection/Logging.hs b/src/Affection/Logging.hs index f18b30e..6155d2b 100644 --- a/src/Affection/Logging.hs +++ b/src/Affection/Logging.hs @@ -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 () diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index bca01e1..6f82190 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -17,6 +17,8 @@ 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 @@ -57,7 +59,7 @@ class (Message (Mesg prt us), Show (Mesg prt us)) => Participant prt us where -- ^ The 'Message' to emit -> Affection us () 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 diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index 05d40ca..5bef780 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -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(..)) @@ -86,7 +87,8 @@ 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 @@ -100,15 +102,16 @@ joystickAutoDisconnect 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" diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 6c35fdf..dff005e 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -10,6 +10,8 @@ import qualified Graphics.Rendering.OpenGL as GL import System.Clock +import Data.String (fromString) + import Control.Monad.State -- | Prehandle SDL events @@ -63,7 +65,7 @@ toggleScreen = do mapM_ (flip SDL.setWindowMode SDL.Windowed . snd) (drawWindows ad) return SDL.Windowed x -> do - liftIO $ logIO Warn ("Unexpected Screen mode: " ++ show x) + liftIO $ logIO Warn ("Unexpected Screen mode: " <> fromString (show x)) return x now <- liftIO $ getTime Monotonic put ad From 6d4f7d3227e7765158c97bfd676a207ff98dfde0 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 20 Dec 2019 11:51:02 +0100 Subject: [PATCH 240/261] modernization --- affection.cabal | 2 +- default.nix | 18 +++++++ shell.nix | 123 +----------------------------------------------- 3 files changed, 21 insertions(+), 122 deletions(-) create mode 100644 default.nix diff --git a/affection.cabal b/affection.cabal index b0276a8..bd5ffc5 100644 --- a/affection.cabal +++ b/affection.cabal @@ -106,7 +106,7 @@ library , monad-loops , monad-parallel , containers - , clock + , clock >= 0.8 , glib , bytestring , OpenGL diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..4a89a74 --- /dev/null +++ b/default.nix @@ -0,0 +1,18 @@ +{ pkgs ? import (import (builtins.fetchTarball https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz)) +, haskellCompiler ? "ghc865" +}: + pkgs.haskell-nix.cabalProject { + src = pkgs.haskell-nix.haskellLib.cleanGit { src = ./.; }; + ghc = pkgs.buildPackages.pkgs.haskell-nix.compiler.${haskellCompiler}; + # pkg-def-extras = [ + # # Additional packages ontop of all those listed in `cabal.project` + # ]; + # modules = [ + # # Specific package overrides would go here for example: + # packages.cbors.package.ghcOptions = "-Werror"; + # packages.cbors.patches = [ ./one.patch ]; + # packages.cbors.flags.optimize-gmp = false; + # # It may be better to set flags in `cabal.project` instead + # # (`plan-to-nix` will include them as defaults). + # ]; + } diff --git a/shell.nix b/shell.nix index e616402..4d9f121 100644 --- a/shell.nix +++ b/shell.nix @@ -1,123 +1,4 @@ -{ nixpkgs ? import {}, compiler ? "default", doBenchmark ? false }: - 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 {}); - + hsPkgs = import ./default.nix {}; in - - if pkgs.lib.inNixShell then drv.env else drv + hsPkgs.affection.components.all From 2dda8f9db080b84071d35d6c6649f318fbb7ec85 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 20 Dec 2019 11:53:13 +0100 Subject: [PATCH 241/261] drawback --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index bd5ffc5..6467a7c 100644 --- a/affection.cabal +++ b/affection.cabal @@ -106,7 +106,7 @@ library , monad-loops , monad-parallel , containers - , clock >= 0.8 + , clock >= 0.7 , glib , bytestring , OpenGL From 9ece773de8eaa67c90ca7126ed271cef9894c44e Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 20 Dec 2019 11:54:33 +0100 Subject: [PATCH 242/261] twiddling the clock --- affection.cabal | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/affection.cabal b/affection.cabal index 6467a7c..d24e422 100644 --- a/affection.cabal +++ b/affection.cabal @@ -89,11 +89,11 @@ library -- 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 @@ -106,7 +106,7 @@ library , monad-loops , monad-parallel , containers - , clock >= 0.7 + , clock >= 0.8 , glib , bytestring , OpenGL From 18775515d8d569c36148c02a134e1f538981b1ff Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 5 Jan 2020 02:37:44 +0100 Subject: [PATCH 243/261] take .gitignore into account --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index 4a89a74..225fc5f 100644 --- a/default.nix +++ b/default.nix @@ -2,7 +2,7 @@ , haskellCompiler ? "ghc865" }: pkgs.haskell-nix.cabalProject { - src = pkgs.haskell-nix.haskellLib.cleanGit { src = ./.; }; + src = pkgs.haskell-nix.haskellLib.cleanGit { src = pkgs.nix-gitignore.gitignoreSource [] ./.; }; ghc = pkgs.buildPackages.pkgs.haskell-nix.compiler.${haskellCompiler}; # pkg-def-extras = [ # # Additional packages ontop of all those listed in `cabal.project` From e4103e11da33d2b093a623b9163384833b6b6cc1 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 01:27:44 +0200 Subject: [PATCH 244/261] modernize --- affection.cabal | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/affection.cabal b/affection.cabal index d24e422..ff8e8d6 100644 --- a/affection.cabal +++ b/affection.cabal @@ -25,7 +25,7 @@ extra-source-files: ChangeLog.md -- README. -- extra-source-files: -cabal-version: >=1.10 +cabal-version: >=2.0 source-repository head type: git @@ -67,6 +67,7 @@ library exposed-modules: Affection , Affection.Logging , Affection.Types + , Affection.Class , Affection.StateMachine , Affection.Util , Affection.MessageBus @@ -84,6 +85,8 @@ library , Affection.Subsystems.AffectionMouse , Affection.Subsystems.AffectionJoystick default-extensions: OverloadedStrings + , TypeFamilies + , AllowAmbiguousTypes -- Modules included in this library but not exported. -- other-modules: @@ -99,7 +102,7 @@ library ghc-options: -Wall -- Other library packages from which modules are imported. build-depends: base >=4.9 && < 5 - , sdl2 >= 2.5 && < 2.6 + , sdl2 ^>= 2.5 , linear , text , mtl @@ -125,7 +128,7 @@ executable example00 if flag(examples) build-depends: base >=4.9 && < 5 , affection - , sdl2 + , sdl2 ^>= 2.5 , stm else buildable: False @@ -141,7 +144,7 @@ executable example01 if flag(examples) build-depends: base >=4.9 && < 5 , affection - , sdl2 + , sdl2 ^>= 2.5 , stm , OpenGL , random @@ -164,7 +167,7 @@ executable example02 if flag(examples) build-depends: base >=4.9 && < 5 , affection - , sdl2 + , sdl2 ^>= 2.5 , stm , OpenGL , random From 65d47f7477a81d6cb6c4be0fe9b2ada50bceb071 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 01:28:37 +0200 Subject: [PATCH 245/261] back to old build system --- shell.nix | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 2 deletions(-) diff --git a/shell.nix b/shell.nix index 4d9f121..6d4b133 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,125 @@ +{ nixpkgs ? import {}, compiler ? "default", doBenchmark ? false }: + let - hsPkgs = import ./default.nix {}; + + inherit (nixpkgs) pkgs; + + nanovg = 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"; + sha256 = "e35a2772f2a2e71916013cab70eeb8eedd6f66b5490ddf7ce237c8c76895d496"; + revision = "1"; + editedCabalFile = "1lhfsq50r9bdby7mfscw8ih3wsz2m19lr2a2z9b6diaf5kz76vij"; + 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, gtk2hs-buildtools + }: + mkDerivation { + pname = "glib"; + version = "0.13.8.0"; + sha256 = "9cce6fb44e3c30de2bd330a6902955e3a5fce7b6e9ce41a4afb70e3a7d6e61a2"; + 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; + # }) {}; + + sdl2 = with haskellPackages; callPackage( + { mkDerivation, base, bytestring, deepseq, exceptions, linear, SDL2 + , StateVar, stdenv, text, transformers, vector, weigh + }: + mkDerivation { + pname = "sdl2"; + version = "2.5.1.0"; + sha256 = "fc1d3d4276b2c4f835a5311075abb240385aae7edad8760f6bda55ed8271ed20"; + 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 ]; + description = "Both high- and low-level bindings to the SDL library (version 2.0.6+)."; + license = stdenv.lib.licenses.bsd3; + doCheck = false; + }) {}; + + 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 sdl2 stm text uuid vector + ]; + executableHaskellDepends = [ + base containers deepseq linear matrix nanovg OpenGL random sdl2 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 {}); + in - hsPkgs.affection.components.all + + if pkgs.lib.inNixShell then drv.env else drv From 13f67c6cd107323ff286bbc73a835895f0b580f1 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 01:36:12 +0200 Subject: [PATCH 246/261] start with Affectionate --- src/Affection.hs | 58 +++++++++++++++++++++++------------------- src/Affection/Types.hs | 44 ++++++++++---------------------- src/Affection/Util.hs | 23 ++--------------- 3 files changed, 48 insertions(+), 77 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 3d79aae..7ad556e 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -1,5 +1,8 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ExplicitForAll #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE RankNTypes #-} module Affection ( withAffection , get @@ -22,6 +25,7 @@ import Control.Monad.IO.Class (liftIO) import Foreign.C.Types (CInt(..)) 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 @@ -34,7 +38,8 @@ 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. + :: (Affectionate us) + => AffectionConfig us -- ^ Configuration of the Game and its engine. -> IO () withAffection AffectionConfig{..} = do liftIO $ logIO Debug "Affection starting" @@ -54,38 +59,39 @@ withAffection AffectionConfig{..} = do logIO Warn "Linear texture filtering not enabled!" -- construct window liftIO $ logIO Debug "Creating Window(s)" - windows <- zip (map fst windowConfigs) <$> + windows <- zip3 (map (\(x,_,_) -> x) windowConfigs) <$> mapM - (\wc -> SDL.createWindow windowTitle (snd wc)) - windowConfigs - mapM_ (SDL.showWindow . snd) windows + (\wc -> SDL.createWindow windowTitle ((\(_,y,_) -> y) wc)) + windowConfigs <*> + pure (map (\(_,_,z) -> z) windowConfigs) + + mapM_ (SDL.showWindow . (\(_,y,_) -> y)) windows _ <- SDL.glSetAttribute SDL.SDL_GL_SHARE_WITH_CURRENT_CONTEXT 1 - contexts <- zip (map fst windows) <$> mapM (SDL.glCreateContext . snd) windows + contexts <- zip (map (\(x,_,_) -> x) windows) <$> + mapM (SDL.glCreateContext . (\(_,y,_) -> y)) 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 + mapM_ (\w -> flip SDL.setWindowMode ((\(_,_,z) -> z) w) ((\(_,y,_) -> y) w)) windows -- SDL.swapInterval $= SDL.SynchronizedUpdates -- <- causes Problems with windows liftIO $ logIO Debug "Getting Time" -- get current time execTime <- 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 + gameData <- loadState :: forall us. IO (Data us) + let initContainer = AffectionData + { drawWindows = windows + , glContext = contexts + , elapsedTime = 0 + , deltaTime = 0 + , sysTime = execTime + , pausedTime = False + } (_, nState) <- runStateT ( A.runState $ do liftIO $ logIO Debug "Starting Loop" - preLoop - whileM_ (not . A.quitEvent <$> get) + preLoop gameData + whileM_ (hasNextStep gameData) (do -- get state ad <- get @@ -103,15 +109,15 @@ withAffection AffectionConfig{..} = do -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents -- handle events - eventLoop evs + handleEvents gameData evs -- execute user defined update loop - unless (pausedTime ad) (updateLoop dt) + unless (pausedTime ad) (update gameData dt) -- execute user defined draw loop liftIO $ GL.clear [GL.ColorBuffer, GL.DepthBuffer, GL.StencilBuffer] - drawLoop + draw gameData liftIO GL.flush -- actual displaying of newly drawn frame - mapM_ (SDL.glSwapWindow . snd) windows + mapM_ (SDL.glSwapWindow . (\(_,y,_) -> y)) windows -- save new time ad3 <- get when (sysTime ad == sysTime ad3) ( @@ -122,9 +128,9 @@ withAffection AffectionConfig{..} = do ) ) initContainer liftIO $ logIO Debug "Loop ended. Cleaning" - cleanUp $ userState nState + cleanUp gameData liftIO $ logIO Debug "Destroying Window" mapM_ (SDL.glDeleteContext . snd) contexts - mapM_ (SDL.destroyWindow . snd) windows + mapM_ (SDL.destroyWindow . (\(_,y,_) -> y)) windows -- SDL.quit -- <- This causes segfaults depending on hardware liftIO $ logIO Debug "This is the end" diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 9cc11e7..f883a3d 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -35,27 +35,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. @@ -65,18 +50,17 @@ data InitComponents -- | 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 :: + { drawWindows :: [ - ( Word -- ^ Window identifier - , SDL.Window -- ^ Window linked with identifier + ( Word -- --^ Window identifier + , SDL.Window -- --^ Window linked with identifier + , SDL.WindowMode -- -- ^ current screen mode ) ] -- ^ SDL windows , glContext :: [ - ( Word -- ^ Window identifier - , SDL.GLContext -- ^ Associated OpenGL context + ( Word -- --^ Window identifier + , SDL.GLContext -- --^ Associated OpenGL context ) ] -- ^ OpenGL rendering contexts , screenMode :: SDL.WindowMode -- ^ current screen mode @@ -87,14 +71,14 @@ data AffectionData us = AffectionData } -- | Inner 'StateT' monad for the update state -type AffectionStateInner us a = StateT us a +type AffectionStateInner sd a = StateT sd a -- | 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) -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 diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index dff005e..8fb64fa 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -19,19 +19,6 @@ 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 = 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 @@ -46,12 +33,6 @@ getElapsedTime = gets elapsedTime getDelta :: Affection us 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 () @@ -59,10 +40,10 @@ toggleScreen = do ad <- get newMode <- case screenMode ad of SDL.Windowed -> do - mapM_ (flip SDL.setWindowMode SDL.FullscreenDesktop . snd) (drawWindows ad) + mapM_ (flip SDL.setWindowMode SDL.FullscreenDesktop . (\(_,y,_) -> y)) (drawWindows ad) return SDL.FullscreenDesktop SDL.FullscreenDesktop -> do - mapM_ (flip SDL.setWindowMode SDL.Windowed . snd) (drawWindows ad) + mapM_ (flip SDL.setWindowMode SDL.Windowed . (\(_,y,_) -> y)) (drawWindows ad) return SDL.Windowed x -> do liftIO $ logIO Warn ("Unexpected Screen mode: " <> fromString (show x)) From 786bfe1b210587538d4e05b0303e341dbdfe96cd Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 05:17:44 +0200 Subject: [PATCH 247/261] get Affectionate class to work --- src/Affection.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 7ad556e..bb44eac 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -3,6 +3,7 @@ {-# LANGUAGE ExplicitForAll #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeApplications #-} module Affection ( withAffection , get @@ -38,7 +39,7 @@ import qualified Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) -- | Main function which bootstraps everything else. withAffection - :: (Affectionate us) + :: forall us. (Affectionate us) => AffectionConfig us -- ^ Configuration of the Game and its engine. -> IO () withAffection AffectionConfig{..} = do @@ -79,7 +80,7 @@ withAffection AffectionConfig{..} = do -- get current time execTime <- getTime Monotonic liftIO $ logIO Debug "Loading initial data container" - gameData <- loadState :: forall us. IO (Data us) + gameData <- loadState @us let initContainer = AffectionData { drawWindows = windows , glContext = contexts From 44ec3cc8e03fef4a73b809eae72e757d25875fbe Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 05:26:49 +0200 Subject: [PATCH 248/261] comments and formatting --- src/Affection.hs | 84 ++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index bb44eac..c589c36 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -80,7 +80,9 @@ withAffection AffectionConfig{..} = do -- get current time execTime <- getTime Monotonic liftIO $ logIO Debug "Loading initial data container" + -- construct game data object from provided Affectionate instance gameData <- loadState @us + -- build state container let initContainer = AffectionData { drawWindows = windows , glContext = contexts @@ -89,45 +91,51 @@ withAffection AffectionConfig{..} = do , sysTime = execTime , pausedTime = False } - (_, nState) <- runStateT ( A.runState $ do - liftIO $ logIO Debug "Starting Loop" - preLoop gameData - whileM_ (hasNextStep gameData) + -- initialize and run state + (_, nState) <- runStateT + (A.runState (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 - handleEvents gameData evs - -- execute user defined update loop - unless (pausedTime ad) (update gameData dt) - -- execute user defined draw loop - liftIO $ GL.clear [GL.ColorBuffer, GL.DepthBuffer, GL.StencilBuffer] - draw gameData - liftIO GL.flush - -- actual displaying of newly drawn frame - mapM_ (SDL.glSwapWindow . (\(_,y,_) -> y)) windows - -- save new time - ad3 <- get - when (sysTime ad == sysTime ad3) ( - put ad3 - { sysTime = now - } - ) - ) - ) initContainer + liftIO $ logIO Debug "Starting Loop" + -- run preLoop function from Affectionate + preLoop gameData + 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 + 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 . (\(_,y,_) -> y)) windows + -- -- save new time + -- ad3 <- get + -- when (sysTime ad == sysTime ad3) ( + -- put ad3 + -- { sysTime = now + -- } + -- ) + ) + ) + ) initContainer liftIO $ logIO Debug "Loop ended. Cleaning" cleanUp gameData liftIO $ logIO Debug "Destroying Window" From 8af8193a0aad17149e2613ea6208da020ee9f049 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 05:44:19 +0200 Subject: [PATCH 249/261] remove userstate reference from state monad --- src/Affection/MessageBus/Class.hs | 18 +++++++++--------- src/Affection/StateMachine.hs | 12 ++++++------ src/Affection/Subsystems/AffectionJoystick.hs | 10 +++++----- src/Affection/Subsystems/AffectionKeyboard.hs | 4 ++-- src/Affection/Subsystems/AffectionMouse.hs | 6 +++--- src/Affection/Subsystems/AffectionWindow.hs | 8 ++++---- src/Affection/Subsystems/Class.hs | 2 +- src/Affection/Types.hs | 4 ++-- src/Affection/Util.hs | 10 +++++----- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 6f82190..9a16f94 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -22,25 +22,25 @@ 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 @@ -49,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: " <> 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 diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs index c1b5413..1f14abf 100644 --- a/src/Affection/StateMachine.hs +++ b/src/Affection/StateMachine.hs @@ -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 a where -- | State load routine - smLoad :: a -> Affection us () + smLoad :: a -> Affection () -- | state update routine - smUpdate :: a -> Double -> Affection us () + smUpdate :: a -> Double -> Affection () -- | State event handler routine - smEvent :: a -> [SDL.EventPayload] -> Affection us () + smEvent :: a -> [SDL.EventPayload] -> Affection () -- | State draw routine - smDraw :: a -> Affection us () + smDraw :: a -> Affection () -- | State clean routine - smClean :: a -> Affection us () + smClean :: a -> Affection () diff --git a/src/Affection/Subsystems/AffectionJoystick.hs b/src/Affection/Subsystems/AffectionJoystick.hs index 5bef780..9d75258 100644 --- a/src/Affection/Subsystems/AffectionJoystick.hs +++ b/src/Affection/Subsystems/AffectionJoystick.hs @@ -22,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 @@ -81,7 +81,7 @@ 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 <$> @@ -98,7 +98,7 @@ 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 diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index c2a1b58..a78ccda 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -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 [] diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index 01861f3..6f6a599 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -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 diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index a9c2622..f59e00c 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -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 diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index d9504c5..f004eee 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -10,4 +10,4 @@ import qualified 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] + consumeSDLEvents :: s -> [SDL.EventPayload] -> Affection [SDL.EventPayload] diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index f883a3d..949315c 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -49,7 +49,7 @@ data InitComponents | Only [SDL.InitFlag] -- | Main type for defining the look, feel and action of the whole application. -data AffectionData us = AffectionData +data AffectionData = AffectionData { drawWindows :: [ ( Word -- --^ Window identifier @@ -80,6 +80,6 @@ newtype AffectionState sd m a = AffectionState instance MP.MonadParallel m => MP.MonadParallel (AffectionState sd m) -type Affection us a = AffectionState (AffectionData us) IO a +type Affection a = AffectionState AffectionData IO a type Angle = Double diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 8fb64fa..04d68ff 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -15,7 +15,7 @@ import Data.String (fromString) 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 @@ -26,16 +26,16 @@ 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 -- | Toggle the Screen mode between 'SDL.Windowed' and 'SDL.FullscreenDesktop'. -- Pauses the Engine in the process. -toggleScreen :: Affection us () +toggleScreen :: Affection () toggleScreen = do ad <- get newMode <- case screenMode ad of @@ -59,7 +59,7 @@ 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 From c8d3ad6c5683d09639c097e6cac80cfd5ec674f5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 05:44:58 +0200 Subject: [PATCH 250/261] add class --- src/Affection/Class.hs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/Affection/Class.hs diff --git a/src/Affection/Class.hs b/src/Affection/Class.hs new file mode 100644 index 0000000..bb09229 --- /dev/null +++ b/src/Affection/Class.hs @@ -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 From 7df3bb8cf64e4a42bae6f6e92a18f890c5ec6736 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 06:23:22 +0200 Subject: [PATCH 251/261] make example00 affectionate and working! --- examples/example00/Main.hs | 158 +++++++++++++++--------------- src/Affection/Subsystems/Class.hs | 2 +- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs index 31b3a49..6986905 100644 --- a/examples/example00/Main.hs +++ b/examples/example00/Main.hs @@ -6,8 +6,9 @@ module Main where import Affection as A import Control.Concurrent.STM +import Control.Concurrent.MVar import Control.Monad.IO.Class (liftIO) -import Control.Monad (when) +import Control.Monad import qualified SDL hiding (Window(..)) @@ -16,9 +17,19 @@ 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 @@ -26,36 +37,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 @@ -63,11 +51,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 @@ -75,11 +63,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 @@ -87,11 +75,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 @@ -99,7 +87,7 @@ instance Participant Joystick StateData where partUnSubscribe (Joystick t) = generalUnSubscribe t -instance SDLSubsystem Joystick StateData where +instance SDLSubsystem Joystick where consumeSDLEvents = consumeSDLJoystickEvents main :: IO () @@ -116,17 +104,10 @@ main = do { 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 @@ -137,49 +118,45 @@ 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 break (MsgKeyboardEvent _ _ _ _ sym) = case SDL.keysymKeycode sym of SDL.KeycodeQ -> do liftIO $ logIO Debug "Yo dog I heard..." - quit + void $ liftIO $ swapMVar break False _ -> return () -exitOnWindowClose :: WindowMessage -> Affection StateData () -exitOnWindowClose wm = +exitOnWindowClose :: MVar Bool -> WindowMessage -> Affection () +exitOnWindowClose break wm = case wm of MsgWindowClose _ _ -> do liftIO $ logIO Debug "I heard another one..." - quit + void $ liftIO $ swapMVar break 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 @@ -187,8 +164,31 @@ handle es = do mapM_ (\e -> liftIO $ logIO Verbose $ "LEFTOVER: " <> fromString (show e)) leftovers -update _ = return () +update _ _ = return () -draw = return () +draw _ = return () 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)) diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index f004eee..ec55ae3 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -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 [SDL.EventPayload] From 9ac25d5fcaf94d2d38b9a0647ae9ad43d23b68cc Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 08:23:03 +0200 Subject: [PATCH 252/261] fix dt computation --- examples/example01/Main.hs | 46 ++++++++++++++++++++++---------------- src/Affection.hs | 12 +++++----- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 28a5dd9..8864712 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -8,6 +8,7 @@ import qualified SDL import qualified Graphics.Rendering.OpenGL as GL import Control.Concurrent.STM +import Control.Concurrent.MVar import Control.Monad.IO.Class (liftIO) import Control.Monad (when) import Control.DeepSeq (deepseq) @@ -28,6 +29,15 @@ import Foreign.C.Types (CInt(..)) 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 @@ -47,16 +57,9 @@ main = do , 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 } withAffection conf @@ -71,23 +74,28 @@ load = do 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 +pre :: UserData -> Affection () +pre ud = do + _ <- partSubscribe (subKeyboard $ subsystems sd) (exitOnEsc (doNextStep ud)) + _ <- partSubscribe (subKeyboard $ subsystems sd) (reloadOnR ud) _ <- partSubscribe (subKeyboard $ subsystems sd) showFPS _ <- partSubscribe (subKeyboard $ subsystems sd) toggleFullScreen _ <- partSubscribe (subWindow $ subsystems sd) exitOnWindowClose diff --git a/src/Affection.hs b/src/Affection.hs index c589c36..3acbcb1 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -127,12 +127,12 @@ withAffection AffectionConfig{..} = do -- actual displaying of newly drawn frame mapM_ (SDL.glSwapWindow . (\(_,y,_) -> y)) windows -- -- save new time - -- ad3 <- get - -- when (sysTime ad == sysTime ad3) ( - -- put ad3 - -- { sysTime = now - -- } - -- ) + ad3 <- get + when (sysTime ad == sysTime ad3) ( + put ad3 + { sysTime = now + } + ) ) ) ) initContainer From a7eac3e392ab04632afab0c58e2239b1a8e0fc06 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 08:23:30 +0200 Subject: [PATCH 253/261] make example01 affectionate and working --- examples/example01/Main.hs | 164 ++++++++++++++++++------------------ examples/example01/Types.hs | 37 ++++---- 2 files changed, 100 insertions(+), 101 deletions(-) diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 8864712..167fd6f 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -10,7 +10,7 @@ import qualified Graphics.Rendering.OpenGL as GL import Control.Concurrent.STM import Control.Concurrent.MVar import Control.Monad.IO.Class (liftIO) -import Control.Monad (when) +import Control.Monad import Control.DeepSeq (deepseq) import Data.Matrix as M @@ -60,7 +60,7 @@ main = do , SDL.Windowed ) ] - } + } :: AffectionConfig UserData withAffection conf load :: IO UserData @@ -76,7 +76,7 @@ load = do -- logIO A.Debug $ prettyMatrix fullMatrix empty1 <- newTVarIO [] -- ([] :: [(UUID, WindowMessage -> Affection ())]) empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection ())]) - step <- NewMVar True + step <- newMVar True (\life food time -> UserData { subsystems = Subsystems (Window empty1) @@ -94,50 +94,45 @@ load = do pre :: UserData -> Affection () pre ud = do - _ <- partSubscribe (subKeyboard $ subsystems sd) (exitOnEsc (doNextStep ud)) - _ <- partSubscribe (subKeyboard $ subsystems sd) (reloadOnR ud) - _ <- 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 + 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 | 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 () +reloadOnR _ _ = return () -reload :: Affection UserData () -reload = do - ud <- getAffection +reload :: UserData -> Affection () +reload ud = do 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 - } + 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 @@ -146,84 +141,87 @@ showFPS (MsgKeyboardEvent _ _ _ _ sym) = _ -> 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 () +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 dt = 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 = + | life == 0 && pastLife 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 + , 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 ) | 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 + , 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) @@ -231,22 +229,22 @@ 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 +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) @@ -254,7 +252,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 @@ -262,7 +260,7 @@ 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) diff --git a/examples/example01/Types.hs b/examples/example01/Types.hs index 217edfa..e56d7ae 100644 --- a/examples/example01/Types.hs +++ b/examples/example01/Types.hs @@ -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 From d6804f95fbb6df77ee4b4c0ab383e43732989a81 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 11:00:49 +0200 Subject: [PATCH 254/261] update screenmode toggle function --- examples/example01/Main.hs | 2 +- src/Affection/Util.hs | 46 ++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 167fd6f..760de35 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -103,7 +103,7 @@ pre ud = do 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 () diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 04d68ff..d0c5e0b 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -11,6 +11,7 @@ import qualified Graphics.Rendering.OpenGL as GL import System.Clock import Data.String (fromString) +import Data.List (find) import Control.Monad.State @@ -35,24 +36,35 @@ getDelta = gets deltaTime -- | Toggle the Screen mode between 'SDL.Windowed' and 'SDL.FullscreenDesktop'. -- Pauses the Engine in the process. -toggleScreen :: Affection () -toggleScreen = do +toggleScreen :: Word -> Affection () +toggleScreen windowIdent = do ad <- get - newMode <- case screenMode ad of - SDL.Windowed -> do - mapM_ (flip SDL.setWindowMode SDL.FullscreenDesktop . (\(_,y,_) -> y)) (drawWindows ad) - return SDL.FullscreenDesktop - SDL.FullscreenDesktop -> do - mapM_ (flip SDL.setWindowMode SDL.Windowed . (\(_,y,_) -> y)) (drawWindows ad) - return SDL.Windowed - x -> do - liftIO $ logIO Warn ("Unexpected Screen mode: " <> fromString (show x)) - return x - now <- liftIO $ getTime Monotonic - put ad - { sysTime = now - , screenMode = newMode - } + let mwindow = find (\(ident, _, _) -> ident == windowIdent) (drawWindows ad) + case mwindow of + Just (ident, window, mode) -> do + newMode <- case mode of + SDL.FullscreenDesktop -> do + SDL.setWindowMode window SDL.Windowed + return SDL.Windowed + SDL.Windowed -> do + SDL.setWindowMode window SDL.FullscreenDesktop + return SDL.FullscreenDesktop + x -> do + liftIO $ logIO Warn ("Unexpected window mode: " <> fromString (show x)) + return x + now <- liftIO $ getTime Monotonic + put ad + { sysTime = now + , drawWindows = map + (\e@(lid, win, mod) -> + if lid == ident + then (lid, win, newMode) + else e + ) + (drawWindows ad) + } + Nothing -> do + liftIO $ logIO Warn ("No window found with ident " <> fromString (show windowIdent)) -- | Fit the GL Viewport to Window size fitViewport From 8785405420f751c106060622d58b7c10ba6dc182 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 13:06:10 +0200 Subject: [PATCH 255/261] removed unneeded code --- examples/example01/Main.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 760de35..419f0f8 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -125,7 +125,6 @@ reloadOnR _ _ = return () reload :: UserData -> Affection () reload ud = do - now <- getElapsedTime randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599] let fullMatrix = fromList 60 60 randList void $ liftIO $ swapMVar (lifeMat ud) fullMatrix From f52e94e82ab2524b94294e62d1e3b61727db3085 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 13:06:36 +0200 Subject: [PATCH 256/261] made example02 affectionate and working! --- examples/example02/Main.hs | 207 ++++++++++++++++++------------------ examples/example02/Types.hs | 37 +++---- 2 files changed, 124 insertions(+), 120 deletions(-) diff --git a/examples/example02/Main.hs b/examples/example02/Main.hs index 30454ed..2cd33a2 100644 --- a/examples/example02/Main.hs +++ b/examples/example02/Main.hs @@ -8,8 +8,9 @@ import qualified SDL import qualified Graphics.Rendering.OpenGL as GL import Control.Concurrent.STM +import Control.Concurrent.MVar import Control.Monad.IO.Class (liftIO) -import Control.Monad (when) +import Control.Monad import Control.DeepSeq (deepseq) import Data.Matrix as M @@ -28,6 +29,15 @@ import Foreign.C.Types (CInt(..)) 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 @@ -50,17 +60,10 @@ main = do , 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 @@ -74,65 +77,64 @@ load = do 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 break -> 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 = break } + ) + <$> 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 () +reloadOnR _ __ = return () -reload :: Affection UserData () -reload = do - ud <- getAffection - now <- getElapsedTime +reload :: UserData -> Affection () +reload ud = do randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599] 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 @@ -141,84 +143,86 @@ showFPS (MsgKeyboardEvent _ _ _ _ sym) = _ -> 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 () +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 = + | life == 0 && pastLife 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 + , 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 ) | 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 + , 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) @@ -226,30 +230,29 @@ 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) + mult = pastLife M.! (x + 1, y + 1) -- logIO A.Debug $ show mult beginPath ctx rect ctx (fromIntegral $ x * 10) (fromIntegral $ y * 10) 10 10 @@ -257,7 +260,7 @@ draw = do 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) diff --git a/examples/example02/Types.hs b/examples/example02/Types.hs index 217edfa..e56d7ae 100644 --- a/examples/example02/Types.hs +++ b/examples/example02/Types.hs @@ -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 From 8b26781217d1374527af41e0b1096ea710367a9e Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 13:08:27 +0200 Subject: [PATCH 257/261] bump version --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index ff8e8d6..4a324e3 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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. From bc08ca55a44d0d50401669431ee249429a6c50a8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 13:40:57 +0200 Subject: [PATCH 258/261] hunt warnings --- examples/example00/Main.hs | 14 ++++++++------ examples/example01/Main.hs | 27 +++++---------------------- examples/example02/Main.hs | 32 ++++++++------------------------ src/Affection.hs | 11 ++--------- src/Affection/Types.hs | 1 - src/Affection/Util.hs | 2 +- 6 files changed, 24 insertions(+), 63 deletions(-) diff --git a/examples/example00/Main.hs b/examples/example00/Main.hs index 6986905..3f8acfb 100644 --- a/examples/example00/Main.hs +++ b/examples/example00/Main.hs @@ -7,10 +7,9 @@ import Affection as A import Control.Concurrent.STM import Control.Concurrent.MVar -import Control.Monad.IO.Class (liftIO) import Control.Monad -import qualified SDL hiding (Window(..)) +import qualified SDL import Data.Maybe (isJust, fromJust) import Data.String @@ -129,19 +128,19 @@ pre sd = do return () exitOnQ :: MVar Bool -> KeyboardMessage -> Affection () -exitOnQ break (MsgKeyboardEvent _ _ _ _ sym) = +exitOnQ nextStep (MsgKeyboardEvent _ _ _ _ sym) = case SDL.keysymKeycode sym of SDL.KeycodeQ -> do liftIO $ logIO Debug "Yo dog I heard..." - void $ liftIO $ swapMVar break False + void $ liftIO $ swapMVar nextStep False _ -> return () exitOnWindowClose :: MVar Bool -> WindowMessage -> Affection () -exitOnWindowClose break wm = +exitOnWindowClose nextStep wm = case wm of MsgWindowClose _ _ -> do liftIO $ logIO Debug "I heard another one..." - void $ liftIO $ swapMVar break False + void $ liftIO $ swapMVar nextStep False _ -> return () joyConnectDisconnect :: MVar [SDL.Joystick] -> JoystickMessage -> Affection () @@ -164,10 +163,13 @@ handle sd es = do mapM_ (\e -> liftIO $ logIO Verbose $ "LEFTOVER: " <> fromString (show e)) leftovers +update :: StateData -> Double -> Affection () update _ _ = return () +draw :: StateData -> Affection () draw _ = return () +clean :: StateData -> IO () clean _ = return () generalSubscribers diff --git a/examples/example01/Main.hs b/examples/example01/Main.hs index 419f0f8..87484cd 100644 --- a/examples/example01/Main.hs +++ b/examples/example01/Main.hs @@ -3,13 +3,10 @@ {-# LANGUAGE ForeignFunctionInterface #-} import Affection as A -import SDL (($=)) import qualified SDL -import qualified Graphics.Rendering.OpenGL as GL import Control.Concurrent.STM import Control.Concurrent.MVar -import Control.Monad.IO.Class (liftIO) import Control.Monad import Control.DeepSeq (deepseq) @@ -21,8 +18,6 @@ import System.Random (randomRIO) import NanoVG hiding (V2(..)) -import Linear - import Foreign.C.Types (CInt(..)) -- internal imports @@ -69,7 +64,7 @@ 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 @@ -121,11 +116,10 @@ reloadOnR ud (MsgKeyboardEvent _ _ _ _ sym) = case SDL.keysymKeycode sym of SDL.KeycodeR -> reload ud _ -> return () -reloadOnR _ _ = return () reload :: UserData -> Affection () reload ud = do - randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599] + randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0 .. (3599 :: Int)] let fullMatrix = fromList 60 60 randList void $ liftIO $ swapMVar (lifeMat ud) fullMatrix void $ liftIO $ swapMVar (foodMat ud) (fromList 60 60 (repeat 10)) @@ -138,7 +132,6 @@ showFPS (MsgKeyboardEvent _ _ _ _ sym) = dt <- getDelta liftIO $ logIO A.Debug $ "FPS: " <> fromString (show (1 / dt)) _ -> return () -showFPS _ = return () exitOnWindowClose :: MVar Bool -> WindowMessage -> Affection () exitOnWindowClose step wm = @@ -147,7 +140,6 @@ exitOnWindowClose step wm = liftIO $ logIO A.Debug "I heard another one..." void $ liftIO $ swapMVar step False _ -> return () -exitOnWindowClose _ _ = return () handle :: UserData -> [SDL.EventPayload] -> Affection () handle ud es = do @@ -156,7 +148,7 @@ handle ud es = do return () update :: UserData -> Double -> Affection () -update ud dt = do +update ud _ = do -- liftIO $ logIO A.Debug ("FPS: " <> fromString (show (1/dt))) pastLife <- liftIO $ readMVar (lifeMat ud) pastFood <- liftIO $ readMVar (foodMat ud) @@ -200,15 +192,6 @@ update ud dt = do | otherwise = (submatrix (y - 1) (y + 1) (x - 1) (x + 1) pastLife) life = countLife subm - ret - | life == 0 && pastLife M.! (y, x) == 0 = - ( 0 - , 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 - ) - | otherwise = (1, 1, 1) 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) @@ -216,7 +199,7 @@ update ud dt = do else if life == 3 && pastFood M.! (y, x) > 0 then return (1, (pastFood M.! (y, x)) - 1, 0) else return - ( 0 + ( (0 :: Word) , if pastTime M.! (y, x) > 10 then min 10 ((pastFood M.! (y, x)) + 1) else min 10 (pastFood M.! (y, x)) @@ -233,7 +216,6 @@ update ud dt = 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)) @@ -265,4 +247,5 @@ draw ud = do restore (nano ud) endFrame (nano ud) +clean :: UserData -> IO () clean _ = return () diff --git a/examples/example02/Main.hs b/examples/example02/Main.hs index 2cd33a2..7fc079e 100644 --- a/examples/example02/Main.hs +++ b/examples/example02/Main.hs @@ -3,13 +3,10 @@ {-# LANGUAGE ForeignFunctionInterface #-} import Affection as A -import SDL (($=)) import qualified SDL -import qualified Graphics.Rendering.OpenGL as GL import Control.Concurrent.STM import Control.Concurrent.MVar -import Control.Monad.IO.Class (liftIO) import Control.Monad import Control.DeepSeq (deepseq) @@ -21,8 +18,6 @@ import System.Random (randomRIO) import NanoVG hiding (V2(..)) -import Linear - import Foreign.C.Types (CInt(..)) -- internal imports @@ -72,14 +67,14 @@ 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 ())]) empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection ())]) - (\life food time break -> UserData + (\life food time nextStep -> UserData { subsystems = Subsystems (Window empty1) (Keyboard empty3) @@ -87,7 +82,7 @@ load = do , foodMat = food , timeMat = time , nano = nanoCtx - , doNextStep = break + , doNextStep = nextStep } ) <$> newMVar fullMatrix @@ -124,11 +119,10 @@ reloadOnR ud (MsgKeyboardEvent _ _ _ _ sym) = case SDL.keysymKeycode sym of SDL.KeycodeR -> reload ud _ -> return () -reloadOnR _ __ = return () reload :: UserData -> Affection () reload ud = do - randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0..3599] + randList <- liftIO $ mapM (\_ -> randomRIO (0,1)) [0 .. (3599 :: Int)] let fullMatrix = fromList 60 60 randList void $ liftIO $ swapMVar (lifeMat ud) fullMatrix void $ liftIO $ swapMVar (foodMat ud) (fromList 60 60 (repeat maxFood)) @@ -141,7 +135,6 @@ showFPS (MsgKeyboardEvent _ _ _ _ sym) = dt <- getDelta liftIO $ logIO A.Debug $ "FPS: " <> fromString (show (1 / dt)) _ -> return () -showFPS _ = return () exitOnWindowClose :: MVar Bool -> WindowMessage -> Affection () exitOnWindowClose step wm = @@ -150,7 +143,6 @@ exitOnWindowClose step wm = liftIO $ logIO A.Debug "I heard another one..." void $ liftIO $ swapMVar step False _ -> return () -exitOnWindowClose _ _ = return () handle :: UserData -> [SDL.EventPayload] -> Affection () handle ud es = do @@ -202,15 +194,6 @@ update ud _ = do | otherwise = (submatrix (y - 1) (y + 1) (x - 1) (x + 1) pastLife) life = countLife subm - ret - | life == 0 && pastLife M.! (y, x) == 0 = - ( 0 - , 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 - ) - | otherwise = (1, 1, 1) 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) @@ -218,7 +201,7 @@ update ud _ = do else if life == 3 && pastFood M.! (y, x) > 0 then return (1, (pastFood M.! (y, x)) - 1, 0) else return - ( 0 + ( 0 :: Word , if pastTime M.! (y, x) > 10 then min maxFood ((pastFood M.! (y, x)) + 1) else min maxFood (pastFood M.! (y, x)) @@ -252,11 +235,11 @@ draw ud = liftIO $ do let x = coord `mod` 60 y = coord `div` 60 ctx = nano ud - mult = pastLife 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 @@ -266,4 +249,5 @@ draw ud = liftIO $ do restore (nano ud) endFrame (nano ud) +clean :: UserData -> IO () clean _ = return () diff --git a/src/Affection.hs b/src/Affection.hs index 3acbcb1..2b1f908 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -21,9 +21,6 @@ import System.Clock import Control.Monad.Loops import Control.Monad.State.Strict -import Control.Monad.IO.Class (liftIO) - -import Foreign.C.Types (CInt(..)) import Affection.Types as A import Affection.Class as A @@ -70,10 +67,6 @@ withAffection AffectionConfig{..} = do _ <- SDL.glSetAttribute SDL.SDL_GL_SHARE_WITH_CURRENT_CONTEXT 1 contexts <- zip (map (\(x,_,_) -> x) windows) <$> mapM (SDL.glCreateContext . (\(_,y,_) -> y)) 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_ (\w -> flip SDL.setWindowMode ((\(_,_,z) -> z) w) ((\(_,y,_) -> y) w)) windows -- SDL.swapInterval $= SDL.SynchronizedUpdates -- <- causes Problems with windows liftIO $ logIO Debug "Getting Time" @@ -92,7 +85,7 @@ withAffection AffectionConfig{..} = do , pausedTime = False } -- initialize and run state - (_, nState) <- runStateT + (_, _) <- runStateT (A.runState (do liftIO $ logIO Debug "Starting Loop" @@ -126,7 +119,7 @@ withAffection AffectionConfig{..} = do liftIO GL.flush -- actual displaying of newly drawn frame mapM_ (SDL.glSwapWindow . (\(_,y,_) -> y)) windows - -- -- save new time + -- save new time ad3 <- get when (sysTime ad == sysTime ad3) ( put ad3 diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 949315c..a7d8eb4 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -63,7 +63,6 @@ data AffectionData = AffectionData , 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) diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index d0c5e0b..763f0ee 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -56,7 +56,7 @@ toggleScreen windowIdent = do put ad { sysTime = now , drawWindows = map - (\e@(lid, win, mod) -> + (\e@(lid, win, _) -> if lid == ident then (lid, win, newMode) else e From 0aa971a343213e3269ddf1744a07c4795f1ec5df Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 13:41:47 +0200 Subject: [PATCH 259/261] add project file --- cabal.project | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 cabal.project diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..aac861d --- /dev/null +++ b/cabal.project @@ -0,0 +1,4 @@ +constraints: affection +verbose +profiling: true +packages: + ./. From 3b9a366830568ef738690c133a9fff0758618f96 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 13:42:30 +0200 Subject: [PATCH 260/261] update notes --- notes/TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes/TODO.md b/notes/TODO.md index db88493..6a15c72 100644 --- a/notes/TODO.md +++ b/notes/TODO.md @@ -2,7 +2,7 @@ Implement following things in approximately this Order: -* Message bus +* Message bus -> DONE * Console * GUI * Framework From fc6bfa1ca5307e915d44eeac507ccc56ba01b487 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 4 May 2020 21:31:42 +0200 Subject: [PATCH 261/261] fix state machine --- src/Affection/StateMachine.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs index 1f14abf..7d9ab55 100644 --- a/src/Affection/StateMachine.hs +++ b/src/Affection/StateMachine.hs @@ -7,14 +7,14 @@ import Affection.Types import qualified SDL -- | Typeclass for simple scaffolding of a state machine -class StateMachine a where +class StateMachine us a where -- | State load routine - smLoad :: a -> Affection () + smLoad :: a -> us -> Affection () -- | state update routine - smUpdate :: a -> Double -> Affection () + smUpdate :: a -> us -> Double -> Affection () -- | State event handler routine - smEvent :: a -> [SDL.EventPayload] -> Affection () + smEvent :: a -> us -> [SDL.EventPayload] -> Affection () -- | State draw routine - smDraw :: a -> Affection () + smDraw :: a -> us -> Affection () -- | State clean routine - smClean :: a -> Affection () + smClean :: a -> us -> Affection ()

YCQ{RH8F);*IXg=9#7?XpgyhEQE8btc7kCcvH$i0?afTT59u`<- zpQLQYZO^QSfW^&TXv6o!07Ff1LT#9!%V?sQxyw@~Y97$)KLIV5+MfhyXfH~YG}*0k zn`y}Tl-8)}*9W|}DbkDzE3*z`ph5xMzq_l>$>LYC>6L;;(0j|=HOXe!PvgPDx z>Mx+M$CGvnF#8xH9HzCgocdn!uE<{{%lD$;sJq-nV=GkBF@O|(7MCoyj#pham3JEG zm+1GE#k(tan)ibcYroFnvbzLnkg)+5=Nk>?6>l}0|?JcvN^Du|@h@pkuB~;K0=ZWo5dN15- z{{UF=ZzE%J?Z&ZPk1JBCXCM9ySRNz?)8z8SW*C zl}|z$o>gEDsH1aAwlfM;0X2-9MUaaq@B~A0U~$RVVdt;iub(%F9^E9gFQ z)XFpYPK7z=GTk!1#=>)n`tE_6$ECq!H`**0Yfz9%GYp+WX}Z`RXjN*RNT~|xRrw{` zTcGzpk-#G1kYZS4+eFWVaI{h3CR$)n;ghT*9jQ$#be3)(`!Mv=l5BN_TkIxl*!7CB zs;Zcc9`HYT!=P&#&g2u|!_$V;$rxs~9n*aL!l$>(e=r`9DjvA`yPnEQl^FMtmRVtgfb9v|XwD%a%Z+k% zAv??WmTv=`qZwx6SluHmvd?obnB}9`6Nw)-6KcTuVQRv47ItCit?3mlCb=YXcS2Rb zh|+-;7re5q>O$j`6ceBW+blvw*mU|sBSOB9+l8%qn3lXt8lIB!)pyn)fNsI1)YD-S zRH%_7z+vEP!B08eE-gD{BNTRw-<+Wpo*F38GCG#X|jA@0JZ0H;F7Bj+Bo7^1Ujtxh;d`S6 zuqlAMl=i-6-!9OV>mox^70BAT%-u(Ra)y8^_&p(XYF~4#Q!tAtKyt+PDNuBLF$MJX zc}FaCcxhvj>6J^mw8}!*<$saBgYbR_;qND|-rP+mNO0WcnVde%sxtFRMnG|yQKh(1 zFQ6$>Vb&am79A^!lf zq9E6@m4MpE*GLRR&zcWDkju2_KW{T8nim*4`4E;Y$d)*FtqY?IJqqF%pgN;5FEyW- zej(uwTAoC}cy%D!uQkKl`kg#U)(K`FlBGev18H*VI>^|x#1pq zMrQ7MvqoKN4MoHtftc#lb;rbajM(_nyb1pR8k%HxT;!KRYa7h#(@JlMjowMS4=G-w zH4i}ps$e^rLITi0^b*yCokX@bnT$9&qtm-qYopi5w%C>{z z7Vi{=m_e0@bY%Xp%!G*qII)59loMDuoz|DJDAIODC+E-ZmZqS)Ge(^VK6s%7kV16= zb(`Jh6wP6K**()EQYSp6igDgB!}!F3hcu@9!RS+cM|P_9gwB*q(wA;MFujEWgScN{Rzd@Wikwxn^ND0FctdD7FgO zb0&K2exUb{P|eqnVf2JRItV=p&~FB_)ERyFjlP;=Ub49jZm;GSN$DThwA`GpLTm)} zq5Xy6b2I2!yS=tOC#^jZLs@(}_&@uv3clln!|Z;OMmx--oO54*aMWhgRN5PTr_A|` z(sG>mE@H&OGkJmw!f6>yz^W==LVE(ccVqXveUC-NEybMKG8f(?b(#kbO@B0Bvi zxWXTw9;RT9+v-e(&RG8dzVWt~XV@R?X;k#hUb2RO=BBrXX#CP6CSLTRZrCPiSSG`Xl1F99?X8B2ozy=^fc zwNGlFW2B+H!YW|73~jk1LrZHjp_QEwDj$!$_&)=H7b8G2%vGNM0Lh^M9Q0p66op@A zhdGQlWDdzN4g5gtz z19ObTJPXz(a`J&5wV=mO+`ijaPCG(qW<@z7tov2Rw$iA3Q17`B-?=jykZJ+Jgjf>H zwyaU7S*KScETPTnp2j2?JOjWpcnI#-wAbP`Lcz{Qy-#+0&6SN-cnP~5(V#xrhOs@N z^8*i3r$mDie_OkJHjsY@;1=Mx}<0`IKm;q1G*4pO&38>O}6##(T39n7fl<;$zPHPFkMP z>S(JrweH`&dB30lsuFYNx#f%n!|s_-Mu{Xdbu^a8qf$~;puiCu0mRgB zUL(M{d>6taeTS=p%{f=)aL= zhh=&bF2UC)rhQ;KtXbCf=gtOIE{pky3;=YCE?*I^9=QJi?y-0VesZ>qC)nj0j*^&j z(E&S5$5CbEaH$6m{rtchE6?X(+1 zp|LM&2E^96Bll?Q?mwO;5Yj#6L2}wBCIi)pSEw#+Qg1W&5Q&XI@)>laKmQTGS@?U`|gaoi<2<#p+!wvl~H%ri5_e4=HYW-Nf zQRJd7JB|=&N!oLH1A;t&oaD1sJkTdN9!m~LIRWBxvev$&Sa2s#1e1nICjkEd&^R6p z(1;&0UPiTlF`uvjkJ4bdkK=KS7p&wX%13c{mC-y+2vfXsseDaD_=?-f6SLfb`3br_ zM{+$Hn%MP9Z`7ESyJlD7ydm~1(aYI`&Ig<=yt8;k711<;b{%cd_3H#&!9{GaucqEDuOW_2fCkNY*oqA6&z=>!l4BdQLpXij5B z4Cf5TtyBBpxTc6g1ULu_d&HFn;|#2l9s$sRdY)z=H5#Iw24rjELrX!Fw;^PhK_KxW#<(@-isyRXXkB%I0>Z~=PreFq6S>t+4Yst6igli z7`QQ^0@B)jX$}d!Ov(2%wn9V-`MSL$r!GdLZaJ4}a2#OTJ1fn{%(7eRPcQOUtLH1f z8cL!qf}@#$y-M*!5Rk!~Dz`1hx+`r32r|$L-)HIA$_aCU?!K$0e z%;mr!Mr9pl@x)n7`|r#OpE(QZ{fA?jWezRanni6f9A7xFtjyNq_CS^{K^N1Pa$w!) zsfkeDZg5++zfC8x`^}m9!=JCo`JN2dT5q8KTY^>7!%g!eewO-Ll2PiFYTEg4sF1**;dsGm&ztq5x%^Jr8nmZVkTMnm_{2=W! zyU%Hklr+-geHP6R#9z^)jqrNlzMosmr>r0a3;`%!Y^WRYbqCsaZo9Pe6DYPRmk7C4 z?;)3@+l9rK??ldZJ*J;|slc6=n=|fRWg90$I#_bEOw31*fcg`AkRVm&=F|zwy0O_E zw}`V!I?1FV_#gNzQuPDU9#A2$*Y@EI;|5!n^4b>HRr-xkfx~pc868LM{==rrPuszC zvR-ELjId8)776>DCdg?mY;;kaB-1^Rkb^ z#L>Mau_188h0c2+rE9F(B)d;(&S!iFR(8v$Da>$4B;gJa;7KHcDjWh1BBe@|DpaXb zrAn1533(P?^W_vl_CwUF9*iyNO-}{Qcy4Xkknp;E8kxYuQvy=_Ih0a~JX?F$GuF#XXGca~J#z6t9wNl)$ZFjuBGm)lb%Qn6z zO)PEnntJ=W^e0&RFdkwyjwV~foV@d%&xBxq^AKeY^AV;Z@jX=2wepHYP(j_ZbPl98 z15xi=vs`7%moJUW;d1`~@D|($A45pZ5oRuUCPJI+%i9i5MlN7uozoISR6NQ~+&IfV8jFP0eiSew_UwjZhjMO)<17qmxeI`~G>A(pIXHwbA}e{*Mpj zw0*amW9YT)VA5*NdBToeCJrxI>!w6UO_nXkxfyM?YrY94S}wO2IGTvWYKAD)@< zn*(u{YEfpF6~V>m0INM{{SMjIbavG#ojKeY5xG&&1l}Fya3@q9>4zp#v%Oz z`|I%z0Qsp3I1#hcKIybLZl~J5Cs#C)8b7fPO5!5f4 zZtV4kX`WJ+@Zbq3LkWk1NhFX!LJ7_XxjK`n9a%fGrifP^)*WJ~sn*dUBVo8sSB3_I z+CG4HBg$)22;fujE&y~eGNN5VJcMB47!j9%W$2w4qe#hoqtO|w zc}+7Od=kIgIm@&YX>?_M;u#8kB`A}u)d!e5ARXXyohU%LV%c+ITqF830o@KAu{S?( ze`S0XQRlyp;#se>opb5Vb(xU&BM1>my(04Ib00E5M4;T(Y#-fvvgYNE$g;Y-!vstDxO z#de0P9z*1nd0sUAf0KIkY=5sYp;nH-5x{i^)%%xMvLz1DN|;UWywQQ#$tcUsc=U}6 zFMG^t8M<5n<*DXX!gJLYoRSh|?ce9_&uF z*y$3J4`j`DYNNKK49z}`8G1((+~rOSE?l{C<;#~YT)A<70~$>c`<*7qw5@fTo=9a-fNn$*R*(NynOJ%`HZqB%~V5C@XK$Y7uqdZ`d6*MN2gn#Y)78~C$?pkucKI4 zK2#)gOWhC2Ee$1&t<3O$L!8<;E4L5H{N4-(lI8Pd9*hk05{~<`)jyClcK+NM2P`}| z5Lp=2*eA0NhxTu0I|NyczsLouo|52AA!sYCHP=|$8B450eV-)u>FIM^vJc^VX@7=Y zqvYYg3f?J!*+A>7ql*W&S#5iV_9Wbcu@jvk7R7Jst20&YRvWtOUXXK~a>GijJjm;8 za>f;XEmY4tfPkI_p`!WDO?wyWUBz{8E(*s-+G`uY#Xjc@_i37FO*>Wua2Ba3-j^xQ^cu&CLd~fh!%4nVb4M#cS+z|c2 zmlDZYv}vzs8oKQ2wDi-&2oTl?5Wa{DfpGeAvCAdP)g85)!T|9r68*shS|YKzV-k{5~%vQ3G2#p`4uqN}`HyYK!=OFmPWepsr%a$1x6c)peShSqN|9V2d+ z*3pXx^~x^j8P)ogOO@P)>fKkdEMBN-^vwlrh_~QYmsj}A$z2?Osel|cSV^p77%wnP z3C`shN4!Fi_nd-kjMiiESQ;hk!W>O{Gk-jUvt;L4(vCAba|t zCxB;C63_rUGe6y74`{rwVvlHp*)rgd24=^@{3V)6^pEW1FCtXCm0tOZWjnNvC|j&j z)>vzSePXSshep_NL8}^8^niLr)O3pD;Q=X&t0*JaQk~uQ8&YWD)?6G?=9$35?HdOP zbCi@FPf}R>^UOc9H(#P{5T&%m^0{aIKqw`uGg!V2Qm{&41OTWDWij5d9jZ9FS%vhK zHo{Bdv1QAQ1prr(7~s+5%Kj4N%aqe7&B%_oEZBXioog+B zWCLw@w_0YA%v+gyLh84sKU@u3>iy?F{v2BaZ6O_Vls3~i-#mFC8U=`}A$SS4l|r-PEy(ja}TQ%+HEt2yI6h?2aEW) zj7fyB%*n$CaTPW#6aKSNwV>JZMJcJ>@hC3!4bsMdx`cHc$*1YO5}BD!xM>fgvkEBZ zl2R4r4PZ77nXEH@_?QfRErnFZZ>4h<%{iqJ;VYHf5a#s6kCdK&(jY(@0(&%85Mqc+ za-Gty_Uuh|!!_v3gg6WPwc~KD-AGfG>H}uBhryv5;!5=(@`|4S0HL1pv2zgJ@;qD? z1X8vC07DYT?KNI?w|{8*stGA)5oRolo9_Y_E?m0y`^2xA!$Z~|Q7iprr%9t%bkeE8 zLCpe<6GbuJYq6vztYPbRs3iwCe%3(Dzb5@6 zpqU-`8Do`jIIv|KOL`BrEpAMAeg?y0`A)lvRUC;_@F5Nv;anT^ytY!jVG`$fGt%0OuO{Agl6zDS-6&zGyB=%pv1A=gl1q(8UNdmEXnm2`W zpcb+$Nxh5ek&OM}%NrIAX`&Za;#7Ec3;GZS)!twg$gcs={{UT~O0c=kxshNKCTLe4 z(>N$KbH7Ob68d1Zza$pU>t==MCXPDgpV2l;y(UbaK`3c1{{Y@q z{D12dPI=*B!uUq3Mmb&L1(IhT+GYJ2{yP(mMyN?-2}I5|fb&})6S31EQR%!6hR(|VLHkJcf*Gw1^Qd7Rc}nVmrMbACQJlB5#5ML{)GljgagKO0k?wJh zsotd_r=A#pqcCc#c4omhdV{r;W5Ps#3LQQs{{SK;#PJ2{3|A!PhnaYPn9oZLr77iQ z4IWl7O@ee`*E&sam^HTsJQcxnS{HjtnSe2J&H+w9Big2^H?-54i`HXI!@L#UOS=C6 zl@!$$C71PJG>T!Ll0R@Kv@hGaGB#_QD=6L*thbZsbR}wV-@ehRjWmszfO$ZwWGLw_ zHB!qHXK3@_ra8=0Lf2;`$T9)#4`pZIT)BKWhMp5gW6EnEN0i8_y%u`_!z5;sm+38%P={%j?)uK z)NZg^)oavwfhB<&3qJ~(bqCy-I8qC1lBG|9Px}HA#mPr|zrz&TA;(7*$KtrL%a;~h zR9?|itJ?zp`b)J;hOSt+3*W2$iDeyc11%%B{R=g1Q$Ez`NlwlBvkCfiqb*d6`b2DP<`~ zarT8zW&*HZIAP?^W#9+{r^-7tufX$jVRT4rr3|`k)FGwsr*bZMV1o5CPDddA#n8uF zX)@hv_WAcE)oR(SoXGC%o8f#kPD1)h^40Ga&U%XWhiX3hO4SvFpy_7+06k)R-VlAu zih1(oaR5@aEp&tz@If3PKQQ&;O#441r|jjJD963Pg{iBw)=J}NglD9FZTYV?Nf z7_ikRw(v|T+5@0f&S@^6E{NP@iYU05dvEqI6ppe7N4))$NtWN- zg-3&cI2V|Hl9tytBC!Np@i^pC)uYlD2{2W3j&yYXQ}n`zLD|1@>qE~ZT5qs?NP&y^ zcwa9wvlF0aZp||Y5tO6%9h``w^B*PDxo9xTy(gfa%%X?gPg!7mYg+^n&S|-dY463L zq!1e<(x~YHZSF!+b@2r?dg|#a>vYonOIo$`>kq3PKcg(spG~G$K}605=+Ba5Z11;t zbtk40YR9`KA73jFZI>oB881L=8#aO#ooNlM)w1`IhHLYV?<5)JlN84w9JHcZD8OQWtZgGUL!L zf0QCB4)G4S-8sEu2<~m90YG353u_B4@pzg;XYPF`(&;L9h|xFFF628#8C2uefSNx6 zZ=)}ny(3b_3&0t2iR<@Ay-bzCb9ucXHYze-mM-QOIu*x8-CMFUNo$KxLD2gMK+FFC z$%=11#}R@ZN}<ulW+Yp$vw|9!z!BPvkANQNCIcUJYI9@=NO7HNI*s60+Cka(;Bt4CR@=PTZk zbkEE5l`M|2N9CTN3-GD-r}CH+cYd!|P*%f5n})ZiJyfOFoo?V3{0`=Ef%gsz|bgdtxYY63XKxjLs$W zn->fSV_884!AE4%S29}RItgEz=B^DwbX2*So^a|rMz>J=L7tbcW%y`b>hH={plb~G zOuMt#C$&XQWM^piWCi$|mhgg|9k8MyDuvVWgut*=&_^RS=l7K5x)pp}%;-#y2GblA z*u3(n<4k)cHKnz`%(2S0x6th|C0f^y#=D8+pP`OB5nNTjrg$-IU>RZ<0=Aw<;L&3A z;x)`5^j1;6_2BLjLwPRW@nS$%Z^{NI;9| zzOY`_?(~0V@Z!fUT`Z1~Y~@a%f1gxg=9>BW{Xdz?62AkDN{K3f;9EJ48x_J;aC$_? zPG`B{8sa{y@+H18Uk-9x02mmC9@@w4wzjLlK4){+$*;m%1@)pF8Z>Wwryxc^K{4Ew zPj?2)*OloO-%|)N(RLxK^&oEp1_U32K~OZZ~=Lofo9N@hVo!BUn9W*udsWqvQQkHV%qgRqY0w9I}ORr`Dz) z?1Sc|iVsKVvjgA1*&b|=6H(nK?fPQ@?nsjZLq<1ndls>#MBmIb>t zAF~vT4JI))Jq_D%D|XES<>!=)nrm_EJEZ_0@>V>=b%Kd}1nNsgHHB(>>z4k?f*-X0 z!$khwU{)Vr=@Qgn9TDNOnB*gInS&gj$dXR#J+FgJzK8tjnJ7IzjYu zl=tB|n%3ZEmMOmg4by?DX>PE>ULR@d7=&lp6xuW5no}E8G@L|5_vD`F=sEY_5T2nZ4HxtIonW*tj4uMAXL&>nbnaxKI2LU#L z&6gsk;J$>qxa8lug=?sv#^crBSMJODCt!R?o%do61UQ%y`b_ejoCt}7!M0*IM5}`h zGLn^;K3y4Z_+rf`cB1O=bn%H{-)-{nbe}=eCBc$ikY=mWEvq%r7XF{)7xn)DAz$r( zAT4-dW^Qx;0B8YHQbGp@-=++4)f^C17&C1?B8ttNN4p0*B7M*FiS>(sh)6J1uNw@z zg|kYP;5$%9BRHdI(0x-g8$|MiVc{s&`%ceTF7?_hXcnv?a3Fpc13|c5VrxN=0dGEa;$N|* zt<{A9z_xTevF|!8aK5|yZSi?=_4G7gWr9)%Op4R;?b`ulLt1BM~R zjohGlVpM4^4p3Tli>W`i@F=Ko!8iCH}lUMI4r%GwC4?#Hp03Zq@ zoo8Do0OHSO9Qu{F)@8ctZybd$pgc-Uz`9)p`1t(2xQ7Gqs^LIL_mJf7{$c~1nB^-mfq>N%4Ao{7Ho7C^gaiJ})AEF?a1a)-X=2(#9dDQ2 z0~uXtT|ZGLpf5EJ#K)HIS}*Uc@#+KQL!-`H-uAnDJ!%N%-W`=1VtChK2+1}Fl*iFA z81P^w=U!0g4_R`=pyY7cS(SyGcZtUnn+#9wldl<|A6d}qT@8n>EBGP4d?&c#Vj^{a zvY09-{i4b@Hf~72@{Xh5FaQHeWrjkwyp=kZ=(9Z1-QtdW8fh)wN{c>bu(C}p;es%G zYY@~cg7k`-ve4x+LrdaZxpCk$PkqM~?ZzxiSanRSv7v{r;4*AhC8K+h%?lkWP?l`> zl#gF%#f7&G2E)N#u(Y6^G{AoHp4}gQ-q6YkP^B7l-YAk|5~^DQtCVboRgB6bS&cB^ z;{0Q zY%WyOHWE#K*uDHqf9yfGtmQojgcp~b4W=V2Bwbz+!Z-#3Z7B3#?B^WMx!Zw+qdg$G zyPPbug{Cf6sHIy@h|k_3n)+0EKj*Bi(db$jZGOVK)5-?5sWRScVKiCqlR+`-kA4U; z+V!nwSZW?$GLhJFWQ%D*6=y1{n9*@mLe(F(fRdRs4 zwhC%fjJV@OR6i0` zL99a|6I43h zB-d+Xw%WzDxp~dJLtntwR(TKHqZ#$6;?qTGH;fJo&StkQ=;wFZVPV|rx*xB0 zBPDHX%jH$Car)zY9snuf$nRg-pd1Ig@RQzzE@NNISVA256K)6LToP0su4g8Q9xhm* z9B%`%6ERiD?F6O#0&611ynkqAO8gE4Ar1qGj~bK7J;_X`>^qo5fmP7Sa_}P3h>TOG#a-!`^q(ttmLQ(gNGTfw=^?S z05!HEPC*Xj%<()&Siptuwn4_S+7c<}=B`U_z2e!furT-1ru=iJ%lnSSsurU>~EwujB{1Mw1N{>ua2Wrs{2wu*TS+ySUU ziU}>;AIerP4U3dieAFzYuz-AXgUFXYvedA{Z7Q{t#YKC+m+cr8&O`2*z_}?xzlUVM zNViF*7`}t4sZy3SLz45%vCe&f7m??a4)M~}iLS-&oYBLsspYIYimVu^m#=;EKeK`% z?^CxCE}VgPx0kEd;gky%i>ls-U2k1w8Zd{BPG1v~&@>*t1~JKKOYN!e9I5Y>ZKS?8 z+R9PqBL(t~X{G+yYeHe8vkOCLxyw5M+;(V%)eCTFb(mAtatQ0JB8yQ5n9z^1RjPdt zpA2%D=n-V~fYXVIhMaO@Vc0>0@(qAs^>?Qdta*mdJfSrpf*b*> z0a__`Nopb06w9tjg^F5wtbzyz(P^)226L%8(!Aou2MMXfGZ=}O46=bm?mwPlg&a0s zG&GCKe&6g|Lx`IDw!L6; zS%5^GJ4Fu1A7I`q;TZRnHz*-2oY%hqFB2p%BS=s&0PY%~O}HS$BLWB36mpYQH3*Ra z=kLMiPiPDO03|2hVun5xp6ws9itMl+k5c}ZEfIwvfKF}p_ugHVO=f027&ty-$oz=V@Dp#yfpiw`e;xQ}#L4y|g>Bd#@8SwwUo+r`7A|FFt|h?)&b;9Ehj>Wq*;t?; zn}ioHgoJN-%#ivx?R6gLIpQ2^U@Cz0KiGRSM>ves&})MGlFcmJVS4PxxJ$-?;r&Dk z(KfoiEa?{cPJCmItYMezuku`tK=%;cYwthAIJ`HwLT?CgAvfT0Wk++aIj~vpGF?^( zGs62;%wMU{oryZrIoElNyciq^aMBRsAp?f|YX1Pz_&1+(-2VXMJI#1x4=__@kuR?n z=9hVs=)J=6D>EkemqxD-$twDjURkvbOGMLI2w7F;pQz@L{%|NwsRoc<|jD=N8)N?EKYZaE}dm(S`mT*N!KNXPW3;?w~n-z zk`dQ{qV@TKC_cB$@7XAM5S`~kc|k_~>xH^!RpjMlZHcBq>U_@CB|e=ZtBpC}h3`47 zHhhQ5RWBJ0v=?gv)IL*^eIyE6D}Sp#YI;Wbz>YyJhoK~MIGme%<1P$ zT<^}T{)DTxXw+I?(l-k8U)DcS=}lBbsTCJz9vO)!A-M@-b7jcWjFiF109DmIAutfx z*)$PgD&Cuz5%&scQJR-&ng=&+o{+1=ONxO*(kctY#$C*GiSMnL+wUz0*9hWViBKOR zBE?e@mFq5Sa$Da;_=ligD7>I(%e_XYaFNZpeWSD!@Pe#r=LCfnODnV3@6ov%;fGeYWaxv=^~P@!lBWcgZVQkV4L#v?5tti2_mu{z6| zyNG1jb2SBk-P^b;)y@q^0BM^xm+vukfDW#hmZ$|CkPZ2@Dz{D{2QiYvEm>n2)86wK zebtwg7Y8!A-)~8FI!ER3@XEN}{A`@#B>S{7r==ex+aUaiJOWmn=eUO8z@{8<1|>|a zU8BWGEpsU#Kk{M&a~EHD=U2QLnP@JP?5O#kvX89KpJcWELzVcSd7PKDSv_T!&%Xtl z9KvfA007a}9ub5XqnC-{{{V z-!25mmeJcAXZrG8$o-`)*8rk_Z!=G*J;-%24?S6D)WM$r{Q;)UKo&p|)2~ueB{~HSQeg zWjHzn*MP5hjzH_Pwoi#^XG+k=EIM4&I{K<+YD%Q_z}L1G~PV(@2#E2jR|t*sqa4=7Xg8F ze^W*=Qd6-v@5HHHq+^oi>yAVrcsfrP6Bu1eXCOzCVgQuFT~|p>Cb7wy=z!MsoYdeS zh}JmSTC~1V`Z`X+1PyoyOWbIY8?wRXz zpf~3qZdW?rcjTe-6McV)i)x=DAEV|Wny!)g4k3oTPBkhJaJO`+le*UZrhT3Po(H79 z7=FJ*Nw%+D#}2*Ud@j(GrqM#0lq(Fc_d&p4`{3NG3}saKjJF* z0BLW;9K7((f)*@Rl|H$bEz4%V+!I*!6Ln*m6fa1RHX@%B>~roTV90@NYvMM2q@RNxwRN8vUfB*dpMn;MHc>*hPl z2dH1}4Aj!(XOzF9{7y5w{^M5R4ckp+vH4S66=VNXjYVz&E7TgYZRsz!y}Eu(V5g_6e6*-`4|sE!Om|nWOZkHUS_Km4*A?`B zV~)Hr{K&nkD=}!`!>W&G1`kpBf)JGQK_I*iu$$q;(;Z5)H;b6C#f19QUsXP{4gF>Y zAi`Bb>+c%B`!H$kF#iCA!LP)z{-R$WGOy(fnXfR^yL-(ky*>C-Mqq~o<;V2x!f~Hz z(}60ZoYMT~)bpCbhHG*32~vjTPGnJAi(7W%AKu<>i*eDKM^^aSq$4ePdZSw)Oa*xdkWAuCxm48jx!d-Y~pOpj1`ykVBSH7L0m!^LOn@kzfEr(CQ z0%-C639AiXh5rEKaGvsP`fELzGw%KwNa;ZRB_UIgCSdBlSXJ`n5<*qL*UYpO73NKM zjAuM@n4*wcgMHVeTM&B}A*Vdb0NMov(RuiYqp8TDyA%WFXNeDp0Ku2865X0{+lGjv zKGlS-tBaH{_#?jdqTyHOT0K!3XK8LI!5QafnYY$ZnuSnvt_X?CmeOJ3`x5w##eL7g z23IHIo}>JjWs=!f${$mWlm&H**E^_2TBArt)djX<{{T>yh156dUG=CtPU4j8dEQ#e zFuJrF?J@EPR#PkcN7BM_0WfV@Rn*U^wB8~eptnKTmTl$%qjh}8O+{i;HgEn%Z+x@< z`oG^0gg9YsVSVfM9%~vH>fcXvDJ&NqhWAjqbApB)yKgErjoptLW7-XDzgGnAb?a$= zf|N@rz`fS}*;oAeRJXJjH9(hp&l})O!Vd$F&jW$@Hkux%%+T4V@PZ~jf3q`gweX#l zD2NT!&P{hXk%s_;2yi$~BVINiV=zR~UZ>z!=SStg03g6^G!O;5rFY&qhYEYtF0)_h zDM6{s%FIc)i7+Kx1ZD^A;LODM08c&7DHY194j5}bb0if z3Ibz?nbfHFin{!Ybw^0uX$Fz7a-uU{=3b0xq&+yQx~Lr1`2*3IGjz9K0;t4qFBlOz)E}3{lv;qIf46u%T&pt8bOC#wS!lrNZRe>RAdRzJuFKN z(ACiPhlsSpeQA}JG%lUH#RkE7k+{G#Igz|KJ`L<=Ql~O;O6Y&Z4@gkmP;SfI3(fnI z^;6hRTF7aWPa%TgkO&bJjQAq1Kx`O&3!C06F3<^DtrMwq{FG zQ>vy43QJXbMa}_Q)J4=fLEua1TUtP6C}&yUd>iQeNXuo!K$%-qp{9~F_}W{IZ-DY zVni)bws#^7)@U`gF|4;$S*D?t1l84W(^V;sog%BdVy~u7lb55VJ|jV-+*BGdE?wQL zYvtt;*_$|G7MWqu^P~m4JeFZ3b&mHj5DiGXQM}G3O_6vA=w<=gr`HwS{tM!); zz|qY!0)R_Ug36c!dFE)W?Gc$|Y}8N$ff8h!)rjA+5DJJ)^pEN^nRG|XE(|1>MsEh# z3uIlt*r6SDLJe>|SLPUSqnP`iW##4K5uISD(=eROYBH;iHR6D<;gqTzI6M2#r9%($ z3>~^b^png5zu9JwX^Z~K?|JVo4wg6$hX8m^TuOUi=YvT;!=$`TD03TdT0?#D_pEk-R8&`8bm8t_Flsp(nY8&F9v-g-2nOu48}n%k09s;$iYcRM zOjokdjm6eeyKfCHW$!NFbo;+ER2E#s(fsHxd^L42PZ0*C%shk}8}2~5b7})xFU9ep z^y7)AHF$heQW%><_Z*G`FYvjvLj3Sy z7&f{F7CSV84yWH(XDf!BbvY`E1ztwxD5DI&kyv##nSf|2_GvFF14Yz5aOU1vf>WE> z3X#k@%PGubbL-&X3_=j$by^z~I#FCQb=RC08!dAktCRvEP1;>nlHvdr&RkMipHs^Z z!Osp?wr-@-YRA>;Z~B3Z0)v6Tg9vaS+$H}2;LV{S&R+>g9^6A*GY*x#p!R+yE6e6f z@%trtp+;ah z@j<0>O(w=52-(obcJ$0lGxgc5dSdQ!&lq)2#Jbg-Jy_o6txrGqjaJJ$@S0J2#gx6^ zh+t4BL;9J%gX8%Mmj#D$4O=X>XQmjG6THCKH>QY2DOd*k-6v!YdJD@vy? zkBMVjlqPh$OB0>1!nlFjjNTZUi=gvnW(^H?gmm8|wsjM0k9Mo~A*uGcu;mFO(YqW| zkOIDB&@kud!DhJTu|&JqNYlMOw6Edm0K~TKHXjK7Yc*i1tQKFoL^)OqY zu9L3wAsOnG{l)_q3^;SRM^xVb0211^>psNF_V3FMY+b7{f2UF_lyi2RozMk^l2FU5 zXIst%x9-}Xxp<(N{h%@hy0BYToR{xDL=TIFQqTnVZVMb>+?D&X#WwK{e+TbU&o4*| zlhA}Zj-P2&ef)1Qhc8T1ldRPPkX@THp>JWQ%sM;jH7@L0>NVz~T-R69IB8Xyr+82o zV_wxMXf+#1RYB%pO{-v6wd*RA1IhOf)JfcT2~}$Dstg2`qfT*_wU>!P zggUY0%NLpW>jx;B?Uy-L-DPA>-G!;73n-xs+@N0j`(|AUqfX6xGK7vMt!i944m7R?s*iT_SSL93104CX!JE3^Pmpf(LJD zl9+cV*D|xGwAH7y52~6U~$;wi+B5h7&Nh zG(ao8;E0Q_2A@L-m)6WRj$0?$x0*kj`&&F38u)^X9!!1|wFdjlBPVFIHQ;N#`EfW6 zUK@K&d1cbAJ+If|qYyzB5uz-x+WE^C1it3+Sv4~cQPFg)-B&F)nr5_8=*IjTQfH@u zJ;c&|HH!)q6q4tZ)b4D>!rqn*jKs&803uaZF7t@rB6>B2rc)q>tIudPnE=q0Sica4 zLaP0aL<`qvseM(^l`S;N`VxI^LHTKxSi#-zEf$>n*xpkT^;lbNL6vhyHtjdAXcV>@ z%DoSUCf6V=R;~LGs5N%D9OWE#?)o|tMRS-@_4-2W*l{G!g}OybrP8dj7H}V6vlGSMIOfFcw2nG?_A*DOZ z%7Vz#8cf%P%go|!0dCg}i#-=DR9krF9+7IVT&hrC2#0E~ukem0{6_?@b`@P=dvx_K zXX))3GQ{w?3|}R6gaGv1bbn?%6WKrFMsYZ$!VV*e2x|yKKl!>&{iEuNyC^*l4RdUx z?PG!rvE6>&4E{qafdU){6N!(~%xamF_9l|Q_%DU^YT$uDre5rzf{w7Pj$G{3Yd%R) z=Q`eW_=F!{)`#JlLX-ahf>G|(fE=u00Xb1hbaUB-w&GjcO?FFDDeP)8%Lq#JhO zPU@Xt2+@^yiH9FNB35+tB||DTsA$gsf_i{lG

+5es%*XI8hAoZf85DA6<;cBqki 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

r30Ku>Rf@*b-2pBuc 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

rp4LrDC&v;j2zp& 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⪚ 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
$gJ+ zbbn(6-kO_Az+PU_4pqU}v^X*D%NIsIXWV3)5IP^Qp|8ixAxLd7b&Par{33Kq%)(Zq zDRPv8qBH>Yjwe#{F{>{2{2N9XUa?@8XhA{+U@XK@m;*()m-fC3^h-PsS3dPMo)P}S zd=P(PiZSbF>KK(S&Hc*pZEzpBVu_@q>QiDhb6@`e!F)O~Y#w5qf{`D|%MX$T$py*x zf^XQtis{KxybfB+1?F}}9Y3=HY3m2i#+0*vgiQnQ7`rCZFIVaSF7c#@snONLZVlPm z7cH(Uycmaqs<8&;SW5SZ%RQzUHN7Omo`S0}D_BvgT9+=6a_q(o>vX-fIw81H8XklN zP^Sl@=?S+lx);=%w4h+u65`m+o^aH5$;%yO4qD7?JvDG|IC@+%!Ntrsi@IWE-!VOI z1Fd(4LhC%v)nu-}5Z_Rg6?p2vbdvx}A$9S@Dy(wKw{?A{2R=EEi#;kHSZIS9qN1iL zxNABV<>0xEAoZtuS1z64=?*3(O8imMRImR4B3ugB;*plQE6hevtBUxj<+)EV*MbP) zxF7jDvk8up7~?aakWa*q zyG_|_UgT&QYKqDU%X4Px@zL!UoDc-iDD|hhAR$gq>mE(05!GGGddqstddvR+{L3$9Vkk(&5GLFA5HLk72gDEhWB&kSANv@8>|y@^ zp@p3XNi6>47)pDu<%H(+n71}<#k`khB3)f^>^EIiHvId#)Z=nbMThf z;`JJp!xGh=u>tD?6#TbYqOFUgtn{~eh1Q@^nOI7@gJ(;DzQhzA`gMmlc$Nhi>n{yJ zR|!*XL%vj_7)#9dyhAD}s6Md&0Qjz=3%OI8j{5eB;k%St>%0_*d7X4k+~CAmUG65J z%Jh|sfL03D3OF~@{{RT|5l5s+qG5BbxGpBZ1FEomz9E4V37wz*s1fXl6&go`=_v5k zD5_g6RBqf&zSVUHQMcL=zz#WOaEDoz(dj9sYwjd6q5*6ohu#l%y-8bQn>5V@Oz!6} zTiQ|^DD44;`QkFHZ>KW4gB;A)OixWrt_5|1ojlGw!qNioq&I=Lyt=iYGiO5OsCQJv zXhYLJ?G~f3l-%QRAm9H0#o~{|{{YT7cigvmAhd(tR_xfR-}U z0JQPpuumVA^ zF5ZJKw(;f>bB=Bm8uM{ORqGI66C}9_B^w4DPoNQ3YPiN_GN$##WtWkxa&?xVhRlG` zWo+0loHR3KKs%`X!3DMALGlN~`imM|p}Gl;bs5O&<4|YC>uh*vR*CLCXH!nC&T{WB zzyAQOVjC1-HCTJ5y?WC*W6W>86a9!-TcSTucn>a*U_hj^P(hb&8{AyH^n(>)>qgjW zXw`Hdb8;#`N-rff%3F=G(3O@-93+|DR{Ajfh@=CmfWgOPGyq*k gzhx4-)6Oe!n&?$ Date: Wed, 22 Mar 2017 16:59:24 +0100 Subject: [PATCH 121/261] change to texture drawing --- affection.cabal | 1 + src/Affection.hs | 13 ++++++++--- src/Affection/Draw.hs | 51 ++++++++++++++++++++++++++++++++++++++++-- src/Affection/Types.hs | 1 + 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/affection.cabal b/affection.cabal index 8b3959c..3d9bf49 100644 --- a/affection.cabal +++ b/affection.cabal @@ -69,6 +69,7 @@ library , containers , clock , glib + , bytestring -- , sdl2-image executable example00 diff --git a/src/Affection.hs b/src/Affection.hs index dc82f06..165ac2d 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -73,6 +73,12 @@ withAffection AffectionConfig{..} = do window (-1) SDL.defaultRenderer + -- make draw texture + texture <- SDL.createTexture + renderer + SDL.ABGR8888 + SDL.TextureAccessStreaming + (SDL.windowInitialSize windowConfig) -- make draw surface surface <- SDL.createRGBSurface (SDL.windowInitialSize windowConfig) @@ -91,7 +97,8 @@ withAffection AffectionConfig{..} = do , userState = x , drawWindow = window , windowRenderer = renderer - , drawSurface = surface + -- , drawSurface = surface + , drawTexture = texture , drawFormat = format , drawPixels = pixels , drawDimensions = (w, h) @@ -140,12 +147,12 @@ withAffection AffectionConfig{..} = do put $ ad2 { drawStack = clear } -- get texture from surface - texture <- SDL.createTextureFromSurface (windowRenderer ad2) (drawSurface ad2) + -- texture <- SDL.createTextureFromSurface (windowRenderer ad2) (drawSurface ad2) -- actual drawing SDL.copy (windowRenderer ad2) texture Nothing Nothing SDL.present (windowRenderer ad2) -- clean the texture - SDL.destroyTexture texture + -- SDL.destroyTexture texture -- save new time liftIO $ writeIORef execTime now ) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 20620b6..9a1ac08 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -14,9 +14,11 @@ module Affection.Draw import Affection.Types import Data.Maybe (maybe) +import Data.ByteString (ByteString) import Foreign import Foreign.C.Types +import Foreign.Marshal.Alloc (malloc, free) import Control.Monad.State (get, put) import Control.Monad (when, unless) @@ -93,6 +95,49 @@ putToSurface pixels realRect stride cpp DrawRequest{..} = do G.GeglAbyssNone liftIO $ SDL.unlockSurface $ drawSurface ad +putToTexture + :: G.GeglRectangle + -> Int + -> Int + -> DrawRequest + -> Affection us () +putToTexture realRect stride cpp DrawRequest{..} = do + ad <- get + destRect <- return $ + SDL.Rectangle + (SDL.P $ SDL.V2 + (CInt $ fromIntegral $ rectangleX realRect) + (CInt $ fromIntegral $ rectangleY realRect) + ) + (SDL.V2 + (CInt $ fromIntegral $ rectangleWidth realRect) + (CInt $ fromIntegral $ rectangleHeight realRect) + ) + (destPtr, destStride) <- SDL.lockTexture + (drawTexture ad) + (Just destRect) + -- destPtr <- liftIO $ malloc :: (Ptr ByteString) + liftIO $ G.gegl_buffer_get + requestBuffer + (Just realRect) + 1 + (Just $ drawFormat ad) + destPtr + stride + G.GeglAbyssNone + -- pixelData <- liftIO $ peekArray + -- (rectangleWidth realRect * rectangleHeight realRect) + -- destPtr + -- SDL.updateTexture + -- (drawTexture ad) + -- (Just $ SDL.Rectangle + -- (SDL.P $ SDL.V2 (rectangleX realRect) (rectangleY realRect)) + -- (SDL.V2 (rectangleWidth realRect) (rectangleHeight realRect)) + -- ) + -- pixelData + -- (CInt $ fromIntegral stride) + SDL.unlockTexture $ drawTexture ad + -- | function for handling 'DrawRequest's and updating the output handleDrawRequest :: Ptr a -- ^ Pixel buffer to blit to @@ -108,7 +153,8 @@ handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do requestArea (uncurry (G.GeglRectangle 0 0) (drawDimensions ad)) maybe (return()) (\realRect -> - putToSurface pixels realRect stride cpp dr + -- putToSurface pixels realRect stride cpp dr + putToTexture realRect stride cpp dr ) mrealRect case requestPersist of Persist -> @@ -131,7 +177,8 @@ invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do (uncurry (G.GeglRectangle 0 0) (drawDimensions ad)) maybe (return()) (\realRect -> do liftIO $ clearArea requestBuffer realRect - putToSurface pixels realRect stride cpp dr + -- putToSurface pixels realRect stride cpp dr + putToTexture realRect stride cpp dr ) mrealRect case requestPersist of Kill (Just victim) -> diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index d659cb4..018c64e 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -82,6 +82,7 @@ data AffectionData us = AffectionData , drawWindow :: SDL.Window -- ^ SDL window , windowRenderer :: SDL.Renderer -- ^ Internal renderer of window , drawSurface :: SDL.Surface -- ^ SDL surface + , drawTexture :: SDL.Texture -- ^ SDL Texture to draw to , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed , drawPixels :: Ptr () -- ^ Destination Pixel buffer From bbc7142b74e13c6ef99199815890347b722189f1 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 22 Mar 2017 16:59:35 +0100 Subject: [PATCH 122/261] move it faster --- examples/example04.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/example04.hs b/examples/example04.hs index dcd1335..d1e6578 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -164,8 +164,8 @@ handle (SDL.KeyboardEvent dat) = nmap <- return $ M.adjust (updateProperties $ props $ prop "x" $ case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of - SDL.KeycodeLeft -> xpos - 5 - SDL.KeycodeRight -> xpos + 5 + SDL.KeycodeLeft -> xpos - 20 + SDL.KeycodeRight -> xpos + 20 _ -> xpos ) "rect" @@ -184,7 +184,7 @@ handle (SDL.KeyboardEvent dat) = then case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of SDL.KeycodeLeft -> - if bgPos <= 0 then bgPos + 5 else bgPos + if bgPos <= 0 then bgPos + 20 else bgPos SDL.KeycodeRight -> bgPos _ -> bgPos @@ -193,7 +193,7 @@ handle (SDL.KeyboardEvent dat) = SDL.KeycodeLeft -> bgPos SDL.KeycodeRight -> - if True {- TODO: CHANGE-} then bgPos - 5 else bgPos + if bgPos >= -2489 then bgPos - 20 else bgPos _ -> bgPos ) "background" @@ -206,11 +206,11 @@ handle (SDL.KeyboardEvent dat) = then xpos else - xpos - 5 + xpos - 20 SDL.KeycodeRight -> if xpos < 40 then - xpos + 5 + xpos + 20 else xpos _ -> xpos From 004f52aeb4e1d5179235fd75aa4357df2dd8815b Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Mar 2017 04:25:59 +0100 Subject: [PATCH 123/261] disable example00 --- affection.cabal | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/affection.cabal b/affection.cabal index 3d9bf49..0daf7f6 100644 --- a/affection.cabal +++ b/affection.cabal @@ -72,22 +72,22 @@ library , bytestring -- , sdl2-image -executable example00 - hs-source-dirs: examples - main-is: example00.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 example00 +-- hs-source-dirs: examples +-- main-is: example00.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 example01 hs-source-dirs: examples From 0963ca4f4245c0becfce107f286de0b89ff1e1dc Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Mar 2017 04:27:57 +0100 Subject: [PATCH 124/261] remove surface from load function --- examples/example00.hs | 4 ++-- examples/example01.hs | 4 ++-- examples/example02.1.hs | 4 ++-- examples/example02.hs | 4 ++-- examples/example03.hs | 4 ++-- examples/example04.hs | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index 844d545..ef595d1 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -41,8 +41,8 @@ data UserData = UserData { nodeGraph :: M.Map String G.GeglNode } -load :: SDL.Surface -> IO UserData -load _ = do +load :: IO UserData +load = do traceM "loading" root <- G.gegl_node_new traceM "new root node" diff --git a/examples/example01.hs b/examples/example01.hs index b2e03a2..87360e9 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -43,8 +43,8 @@ data UserData = UserData -- , lastTick :: Double } -load :: SDL.Surface -> IO UserData -load _ = do +load :: IO UserData +load = do traceM "loading" root <- G.gegl_node_new traceM "new root node" diff --git a/examples/example02.1.hs b/examples/example02.1.hs index aa2d443..62d8c55 100644 --- a/examples/example02.1.hs +++ b/examples/example02.1.hs @@ -32,8 +32,8 @@ data UserData = UserData , lastTick :: Double } -load :: SDL.Surface -> IO UserData -load _ = do +load :: IO UserData +load = do -- traceM "loading" -- root <- G.gegl_node_new -- traceM "new root node" diff --git a/examples/example02.hs b/examples/example02.hs index 013ab86..151c16e 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -32,8 +32,8 @@ data UserData = UserData , updateActors :: [Actor String] } -load :: SDL.Surface -> IO UserData -load _ = do +load :: IO UserData +load = do traceM "loading" root <- G.gegl_node_new traceM "new root node" diff --git a/examples/example03.hs b/examples/example03.hs index 2c633fb..b6f2d2f 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -37,8 +37,8 @@ data UserData = UserData , partsys :: ParticleSystem } -load :: SDL.Surface -> IO UserData -load _ = do +load :: IO UserData +load = do traceM "loading" root <- G.gegl_node_new traceM "new root node" diff --git a/examples/example04.hs b/examples/example04.hs index d1e6578..37a71d4 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -35,8 +35,8 @@ data UserData = UserData , updateActors :: [Actor String] } -load :: SDL.Surface -> IO UserData -load _ = do +load :: IO UserData +load = do traceM "loading" root <- G.gegl_node_new traceM "new root node" From 369a74c0d08ce826f0d57fe65f52591ef08f74c3 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Mar 2017 04:29:48 +0100 Subject: [PATCH 125/261] make update provide the time again --- examples/example00.hs | 6 +++--- examples/example01.hs | 6 +++--- examples/example02.1.hs | 6 +++--- examples/example02.hs | 6 +++--- examples/example03.hs | 4 ++-- src/Affection/Types.hs | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index ef595d1..def9f9e 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -100,13 +100,13 @@ draw = do handle :: SDL.EventPayload -> Affection UserData () handle = const $ return () -update :: Affection UserData () -update = do +update :: Double -> Affection UserData () +update sec = do traceM "updating" ad <- get ud@UserData{..} <- getAffection - sec <- getDelta + -- sec <- getDelta traceM $ (show $ 1 / sec) ++ " FPS" when (elapsedTime ad > 5) $ put $ ad diff --git a/examples/example01.hs b/examples/example01.hs index 87360e9..7619052 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -91,15 +91,15 @@ draw = do UserData{..} <- getAffection drawRect (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground process $ nodeGraph M.! "sink" + render Nothing Nothing -update :: Affection UserData () -update = do +update :: Double -> Affection UserData () +update dt = do traceM "updating" ud <- getAffection -- let last = lastTick ud -- tick <- getTick -- putAffection $ ud { lastTick = tick } - dt <- getDelta traceM $ (show $ 1 / dt) ++ " FPS" elapsed <- getElapsedTime when (elapsed > 20) $ diff --git a/examples/example02.1.hs b/examples/example02.1.hs index 62d8c55..4859fec 100644 --- a/examples/example02.1.hs +++ b/examples/example02.1.hs @@ -134,16 +134,16 @@ draw = do traceM "loading complete" process (myMap M.! "sink") present (GeglRectangle 0 0 800 600) buffer True + render Nothing Nothing -update :: Affection UserData () -update = do +update :: Double -> Affection UserData () +update dt = do traceM "updating" tick <- getElapsedTime ud <- getAffection putAffection $ ud { lastTick = tick } - let dt = tick - lastTick ud return () traceM $ (show $ 1 / dt) ++ " FPS" diff --git a/examples/example02.hs b/examples/example02.hs index 151c16e..b600e6c 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -108,15 +108,15 @@ draw = do process (nodeGraph M.! "sink") present (GeglRectangle 0 0 800 600) foreground True -update :: Affection UserData () -update = do +update :: Double -> Affection UserData () +update dt = do traceM "updating" tick <- getElapsedTime ud <- getAffection putAffection $ ud { lastTick = tick } - let dt = tick - lastTick ud + -- let dt = tick - lastTick ud return () traceM $ show (1 / dt) ++ " FPS" diff --git a/examples/example03.hs b/examples/example03.hs index b6f2d2f..51124d1 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -100,8 +100,8 @@ draw = do foreground True -update :: Affection UserData () -update = do +update :: Double -> Affection UserData () +update dt = do traceM "updating" ad <- get ud <- getAffection diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 018c64e..27ed78f 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -59,7 +59,7 @@ data AffectionConfig us = AffectionConfig -- ^ Actions to be performed, before loop starts , eventLoop :: SDL.EventPayload -> Affection us () -- ^ Main update function. Takes fractions of a second as input. - , updateLoop :: Affection us () + , updateLoop :: Double -> Affection us () -- ^ Main update function. Takes fractions of a second as input. , drawLoop :: Affection us () -- ^ Function for updating graphics. From 0d48565c850a46d3a9018e66c1b513a8d4f08e7b Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Mar 2017 04:31:10 +0100 Subject: [PATCH 126/261] introduce canvas size --- examples/example01.hs | 1 + examples/example02.1.hs | 1 + examples/example02.hs | 1 + examples/example03.hs | 1 + examples/example04.hs | 1 + src/Affection/Types.hs | 2 ++ 6 files changed, 7 insertions(+) diff --git a/examples/example01.hs b/examples/example01.hs index 7619052..3380349 100644 --- a/examples/example01.hs +++ b/examples/example01.hs @@ -34,6 +34,7 @@ main = do , drawLoop = draw , loadState = load , cleanUp = clean + , canvasSize = Nothing } withAffection conf diff --git a/examples/example02.1.hs b/examples/example02.1.hs index 4859fec..2f2c48c 100644 --- a/examples/example02.1.hs +++ b/examples/example02.1.hs @@ -20,6 +20,7 @@ main = do , drawLoop = draw , loadState = load , cleanUp = clean + , canvasSize = Nothing } withAffection conf diff --git a/examples/example02.hs b/examples/example02.hs index b600e6c..e4993e8 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -21,6 +21,7 @@ main = do , drawLoop = draw , loadState = load , cleanUp = clean + , canvasSize = Nothing } withAffection conf diff --git a/examples/example03.hs b/examples/example03.hs index 51124d1..6214f51 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -28,6 +28,7 @@ main = do , drawLoop = draw , loadState = load , cleanUp = clean + , canvasSize = Nothing } withAffection conf diff --git a/examples/example04.hs b/examples/example04.hs index 37a71d4..a9ff1cb 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -18,6 +18,7 @@ main = do { initComponents = All , windowTitle = "Affection: example00" , windowConfig = SDL.defaultWindow + , canvasSize = Just (3289, 600) , preLoop = return () , eventLoop = handle , updateLoop = update diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 27ed78f..63f356c 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -55,6 +55,8 @@ data AffectionConfig us = AffectionConfig -- ^ Window title , windowConfig :: SDL.WindowConfig -- ^ Window configuration + , canvasSize :: Maybe (Int, Int) + -- ^ size of the texture canvas , preLoop :: Affection us () -- ^ Actions to be performed, before loop starts , eventLoop :: SDL.EventPayload -> Affection us () From 4c7918d9df133f92bc609e90cd0696eb91d414f6 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Mar 2017 04:32:43 +0100 Subject: [PATCH 127/261] more canvas size --- examples/example02.hs | 1 + examples/example03.hs | 1 + src/Affection.hs | 33 ++++++++++++++++++++------------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/examples/example02.hs b/examples/example02.hs index e4993e8..1fe808c 100644 --- a/examples/example02.hs +++ b/examples/example02.hs @@ -108,6 +108,7 @@ draw = do } process (nodeGraph M.! "sink") present (GeglRectangle 0 0 800 600) foreground True + render Nothing Nothing update :: Double -> Affection UserData () update dt = do diff --git a/examples/example03.hs b/examples/example03.hs index 6214f51..120b6ee 100644 --- a/examples/example03.hs +++ b/examples/example03.hs @@ -100,6 +100,7 @@ draw = do (G.GeglRectangle 0 0 800 600) foreground True + render Nothing Nothing update :: Double -> Affection UserData () update dt = do diff --git a/src/Affection.hs b/src/Affection.hs index 165ac2d..2152e59 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -78,19 +78,24 @@ withAffection AffectionConfig{..} = do renderer SDL.ABGR8888 SDL.TextureAccessStreaming - (SDL.windowInitialSize windowConfig) + (case canvasSize of + Just (cw, ch) -> (SDL.V2 + (CInt $ fromIntegral cw) + (CInt $ fromIntegral ch) + ) + Nothing -> + SDL.windowInitialSize windowConfig + ) -- make draw surface - surface <- SDL.createRGBSurface - (SDL.windowInitialSize windowConfig) - SDL.ABGR8888 - let (SDL.Surface ptr _) = surface - pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr - SDL.V2 (CInt rw) (CInt rh) <- liftIO $ SDL.surfaceDimensions surface - let (w, h) = (fromIntegral rw, fromIntegral rh) - stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w - pixels <- SDL.surfacePixels surface - let bablFormat = B.PixelFormat B.RGBA B.CFu8 + -- pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr + let SDL.V2 (CInt rw) (CInt rh) = windowInitialSize windowConfig + (w, h) = case canvasSize of + Just (cw, ch) -> (cw, ch) + Nothing -> (fromIntegral rw, fromIntegral rh) + -- stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w + bablFormat = B.PixelFormat B.RGBA B.CFu8 cpp = B.babl_components_per_pixel bablFormat + !stride = cpp * w format <- B.babl_format bablFormat initContainer <- (\x -> AffectionData { quitEvent = False @@ -100,8 +105,10 @@ withAffection AffectionConfig{..} = do -- , drawSurface = surface , drawTexture = texture , drawFormat = format - , drawPixels = pixels - , drawDimensions = (w, h) + -- , drawPixels = pixels + , drawDimensions = case canvasSize of + Just (cw, ch) -> (cw, ch) + Nothing -> (w, h) , drawStride = stride , drawCPP = cpp , drawStack = [] From 538f5c0f7e71aa935b922cf9f49f41f3b2468639 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Mar 2017 04:34:04 +0100 Subject: [PATCH 128/261] it works. what do you want more? --- examples/example04.hs | 153 ++++++++++++++++++++--------------------- src/Affection.hs | 14 ++-- src/Affection/Draw.hs | 96 ++++++++++++++++---------- src/Affection/Types.hs | 8 +-- 4 files changed, 145 insertions(+), 126 deletions(-) diff --git a/examples/example04.hs b/examples/example04.hs index a9ff1cb..55a78a9 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -10,6 +10,8 @@ import Data.Maybe (fromJust) import Control.Monad (when) import qualified Control.Monad.Parallel as MP +import Foreign.C.Types + import Debug.Trace main :: IO () @@ -32,8 +34,9 @@ data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , actors :: M.Map String (Actor String) , foreground :: G.GeglBuffer - , lastTick :: Double , updateActors :: [Actor String] + , keyDown :: Maybe SDL.Keycode + , cameraX :: Int } load :: IO UserData @@ -56,7 +59,7 @@ load = do traceM "background" over <- G.gegl_node_new_child root G.defaultOverOperation traceM "over" - buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 3289 600) =<< B.babl_format (B.PixelFormat B.RGBA B.CFfloat) sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ props $ @@ -72,8 +75,8 @@ load = do traceM "rect" crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ props $ do - prop "width" (800::Double) - prop "height" (600::Double) + prop "width" (3289 :: Double) + prop "height" (600 :: Double) G.gegl_node_link_many [bg, bgTrans, bgScale, over, crop, sink] _ <- G.gegl_node_connect_to rect "output" over "aux" rectActor <- return $ Actor (map @@ -122,14 +125,17 @@ load = do { nodeGraph = myMap , actors = actorMap , foreground = buffer - , lastTick = 0 , updateActors = [] + , keyDown = Nothing + , cameraX = 0 } --- drawInit :: Affection UserData () --- drawInit = do --- UserData{..} <- getAffection --- present (GeglRectangle 0 0 800 600) foreground True +drawInit :: Affection UserData () +drawInit = do + ad <- get + ud <- getAffection + process (nodeGraph ud M.! "sink") + present (GeglRectangle 0 0 3289 600) (foreground ud) True draw :: Affection UserData () draw = do @@ -139,88 +145,79 @@ draw = do { updateActors = [] } process (nodeGraph M.! "sink") - present (GeglRectangle 0 0 800 600) foreground True + present (GeglRectangle cameraX 0 800 600) foreground True + render + (Just $ G.GeglRectangle cameraX 0 800 600) + Nothing -update :: Affection UserData () -update = do +update :: Double -> Affection UserData () +update dt = do traceM "updating" - tick <- getElapsedTime ud <- getAffection - putAffection $ ud { lastTick = tick } - let dt = tick - lastTick ud - return () traceM $ show (1 / dt) ++ " FPS" -handle :: SDL.EventPayload -> Affection UserData () -handle (SDL.KeyboardEvent dat) = - when (SDL.keyboardEventKeyMotion dat == SDL.Pressed) $ do - ud <- getAffection + maybe (return ()) (\code -> do + let vel = 400 -- velocity in Pixels per second + leg = floor (vel * dt) (G.PropertyDouble xpos) <- return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ actorProperties $ actors ud M.! "rect" - if xpos >= 40 && xpos <= 760 - then do - nmap <- return $ M.adjust - (updateProperties $ props $ prop "x" $ - case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of - SDL.KeycodeLeft -> xpos - 20 - SDL.KeycodeRight -> xpos + 20 - _ -> xpos - ) - "rect" - (actors ud) + nmap <- return $ M.adjust + (updateProperties $ props $ prop "x" $ + case code of + SDL.KeycodeLeft -> + if xpos > 0 + then xpos - fromIntegral leg + else xpos + SDL.KeycodeRight -> + if xpos < 3269 + then xpos + fromIntegral leg + else xpos + _ -> xpos + ) + "rect" + (actors ud) + putAffection ud + { actors = nmap + , updateActors = (nmap M.! "rect") : updateActors ud + } + ud2 <- getAffection + (G.PropertyDouble xpos2) <- + return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ + actorProperties $ actors ud M.! "rect" + ncx <- + if xpos2 - fromIntegral (cameraX ud2) > 750 + then return $ + if cameraX ud2 + leg > 2489 && code == SDL.KeycodeRight + then cameraX ud2 + else cameraX ud2 + leg + else + if xpos2 - fromIntegral (cameraX ud2) < 50 + then return $ + if cameraX ud2 - leg < 0 && code == SDL.KeycodeLeft + then cameraX ud2 + else cameraX ud2 - leg + else + return $ cameraX ud2 + putAffection ud2 + { cameraX = ncx + } + ) (keyDown ud) + +handle :: SDL.EventPayload -> Affection UserData () +handle (SDL.KeyboardEvent dat) = + when (not (SDL.keyboardEventRepeat dat)) $ do + ud <- getAffection + if (SDL.keyboardEventKeyMotion dat == SDL.Pressed) + then putAffection ud - { actors = nmap - , updateActors = (nmap M.! "rect") : updateActors ud + { keyDown = Just $ SDL.keysymKeycode $ SDL.keyboardEventKeysym dat } - else do - (G.PropertyDouble bgPos) <- - return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ - actorProperties $ actors ud M.! "background" - nmap <- return $ M.adjust - (updateProperties $ props $ prop "x" $ - if xpos < 40 - then - case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of - SDL.KeycodeLeft -> - if bgPos <= 0 then bgPos + 20 else bgPos - SDL.KeycodeRight -> - bgPos - _ -> bgPos - else - case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of - SDL.KeycodeLeft -> - bgPos - SDL.KeycodeRight -> - if bgPos >= -2489 then bgPos - 20 else bgPos - _ -> bgPos - ) - "background" - (actors ud) - nmap2 <- return $ M.adjust - (updateProperties $ props $ prop "x" $ - case SDL.keysymKeycode $ SDL.keyboardEventKeysym dat of - SDL.KeycodeLeft -> - if xpos < 40 - then - xpos - else - xpos - 20 - SDL.KeycodeRight -> - if xpos < 40 - then - xpos + 20 - else - xpos - _ -> xpos - ) - "rect" - nmap + else putAffection ud - { actors = nmap2 - , updateActors = (nmap2 M.! "background") : (nmap2 M.! "rect") : updateActors ud + { keyDown = Nothing } handle (SDL.WindowClosedEvent _) = do diff --git a/src/Affection.hs b/src/Affection.hs index 2152e59..a2266f2 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -114,7 +114,7 @@ withAffection AffectionConfig{..} = do , drawStack = [] , elapsedTime = 0 , dt = 0 - }) <$> loadState surface + }) <$> loadState (_, nState) <- runStateT ( A.runState $ do preLoop whileM_ (do @@ -128,7 +128,7 @@ withAffection AffectionConfig{..} = do -- get state ad <- get -- clean draw requests from last run - mapM_ (invalidateDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad) + mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time -- SDL.clear renderer -- compute dt and update elapsedTime @@ -143,23 +143,19 @@ withAffection AffectionConfig{..} = do evs <- preHandleEvents =<< liftIO SDL.pollEvents MP.mapM_ eventLoop evs -- execute user defined update loop - updateLoop + updateLoop dt -- execute user defined draw loop drawLoop -- handle all new draw requests ad2 <- get clear <- catMaybes <$> - mapM (handleDrawRequest (drawPixels ad) (drawStride ad) (drawCPP ad)) (drawStack ad2) + mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) -- save all draw requests to clear in next run put $ ad2 { drawStack = clear } - -- get texture from surface - -- texture <- SDL.createTextureFromSurface (windowRenderer ad2) (drawSurface ad2) -- actual drawing - SDL.copy (windowRenderer ad2) texture Nothing Nothing + -- SDL.copy (windowRenderer ad2) texture Nothing Nothing SDL.present (windowRenderer ad2) - -- clean the texture - -- SDL.destroyTexture texture -- save new time liftIO $ writeIORef execTime now ) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 9a1ac08..c02cd1e 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -6,8 +6,9 @@ module Affection.Draw , clear , handleDrawRequest , invalidateDrawRequest - , present , process + , present + , render , clearArea ) where @@ -56,7 +57,13 @@ drawRect node color Fill rect@GeglRectangle{..} buf = do { drawStack = DrawRequest rect buf (Kill (Just tempRoot)) : drawStack ad } --- | Force update of a specific region on screen +-- | Process a node graph +process + :: G.GeglNode + -> Affection us () +process = liftIO . G.gegl_node_process + +-- | Update of a specific region on the texture present :: G.GeglRectangle -- ^ Area to be updated -> G.GeglBuffer -- ^ Target buffer @@ -69,31 +76,51 @@ present rect buf kill = do { drawStack = DrawRequest rect buf k : drawStack ad } -process - :: G.GeglNode +-- | Render the Texture or a clipping thereof. The clipping will be stretched +-- to fit the render target. +render + :: Maybe G.GeglRectangle + -- ^ Area of the texture to render. + -- Pass 'Nothing' to render the whole texture. + -> Maybe G.GeglRectangle + -- ^ Area of render target to draw to. + -- Pass 'Nothing' to render to the whole render target. -> Affection us () -process = liftIO . G.gegl_node_process +render msrc mtgt = + do + AffectionData{..} <- get + SDL.copy + windowRenderer + drawTexture + src + tgt + where + toSdlRect (G.GeglRectangle x y w h) = SDL.Rectangle + (SDL.P $ SDL.V2 (CInt $ fromIntegral x) (CInt $ fromIntegral y)) + (SDL.V2 (CInt $ fromIntegral w) (CInt $ fromIntegral h)) + src = maybe Nothing (Just . toSdlRect) msrc + tgt = maybe Nothing (Just . toSdlRect) mtgt -putToSurface - :: Ptr a - -> G.GeglRectangle - -> Int - -> Int - -> DrawRequest - -> Affection us () -putToSurface pixels realRect stride cpp DrawRequest{..} = do - ad <- get - liftIO $ SDL.lockSurface $ drawSurface ad - liftIO $ G.gegl_buffer_get - requestBuffer - (Just realRect) - 1 - (Just $ drawFormat ad) - (pixels `plusPtr` - (rectangleX realRect * cpp + rectangleY realRect * stride)) - stride - G.GeglAbyssNone - liftIO $ SDL.unlockSurface $ drawSurface ad +-- putToSurface +-- :: Ptr a +-- -> G.GeglRectangle +-- -> Int +-- -> Int +-- -> DrawRequest +-- -> Affection us () +-- putToSurface pixels realRect stride cpp DrawRequest{..} = do +-- ad <- get +-- liftIO $ SDL.lockSurface $ drawSurface ad +-- liftIO $ G.gegl_buffer_get +-- requestBuffer +-- (Just realRect) +-- 1 +-- (Just $ drawFormat ad) +-- (pixels `plusPtr` +-- (rectangleX realRect * cpp + rectangleY realRect * stride)) +-- stride +-- G.GeglAbyssNone +-- liftIO $ SDL.unlockSurface $ drawSurface ad putToTexture :: G.GeglRectangle @@ -140,15 +167,15 @@ putToTexture realRect stride cpp DrawRequest{..} = do -- | function for handling 'DrawRequest's and updating the output handleDrawRequest - :: Ptr a -- ^ Pixel buffer to blit to + -- :: Ptr a -- ^ Pixel buffer to blit to -- -> B.BablFormatPtr -- ^ format to blit in - -> Int -- ^ Stride + :: Int -- ^ Stride -> Int -- ^ Components per Pixel -> DrawRequest -- ^ 'DrawRequest' to handle -> Affection us (Maybe DrawRequest) -handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do +handleDrawRequest stride cpp dr@DrawRequest{..} = do ad <- get - let surf = drawSurface ad + -- let surf = drawSurface ad mrealRect <- liftIO $ G.gegl_rectangle_intersect requestArea (uncurry (G.GeglRectangle 0 0) (drawDimensions ad)) @@ -164,13 +191,13 @@ handleDrawRequest pixels stride cpp dr@DrawRequest{..} = do -- | clear a previously drawn area invalidateDrawRequest - :: Ptr a -- ^ Pixel buffer to blit to + -- :: Ptr a -- ^ Pixel buffer to blit to -- -> B.BablFormatPtr -- ^ format to blit in - -> Int -- ^ Stride + :: Int -- ^ Stride -> Int -- ^ Components per Pixel -> DrawRequest -- ^ Drawrequest to invalidate -> Affection us () -invalidateDrawRequest pixels stride cpp dr@DrawRequest{..} = do +invalidateDrawRequest stride cpp dr@DrawRequest{..} = do ad <- get mrealRect <- liftIO $ G.gegl_rectangle_intersect requestArea @@ -242,7 +269,6 @@ clearArea = G.gegl_buffer_clear -- | Clear the whole drawing area clear :: G.GeglBuffer -> Affection us () clear buffer = do - ad <- get - SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions $ drawSurface ad - let (w, h) = (fromIntegral rw, fromIntegral rh) + ad <- get + let (w, h) = drawDimensions ad liftIO $ clearArea buffer (GeglRectangle 0 0 w h) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 63f356c..6cb0c92 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -59,13 +59,13 @@ data AffectionConfig us = AffectionConfig -- ^ size of the texture canvas , preLoop :: Affection us () -- ^ Actions to be performed, before loop starts - , eventLoop :: SDL.EventPayload -> Affection us () + , 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. - , loadState :: SDL.Surface -> IO us + , loadState :: IO us -- ^ Provide your own load function to create this data. , cleanUp :: us -> IO () -- ^ Provide your own finisher function to clean your data. @@ -83,11 +83,11 @@ data AffectionData us = AffectionData , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window , windowRenderer :: SDL.Renderer -- ^ Internal renderer of window - , drawSurface :: SDL.Surface -- ^ SDL surface + -- , drawSurface :: SDL.Surface -- ^ SDL surface , drawTexture :: SDL.Texture -- ^ SDL Texture to draw to , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed - , drawPixels :: Ptr () -- ^ Destination Pixel buffer + -- , drawPixels :: Ptr () -- ^ Destination Pixel buffer , drawDimensions :: (Int, Int) -- ^ Dimensions of target surface , drawStride :: Int -- ^ Stride of target buffer , drawCPP :: Int -- ^ Number of components per pixel From b3f4b871ea4a063bdae3f6b7086bfe196f72faca Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 24 Mar 2017 21:58:53 +0100 Subject: [PATCH 129/261] fix strange scrolling behaviour --- examples/example04.hs | 57 ++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/examples/example04.hs b/examples/example04.hs index 55a78a9..0d7a66a 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -36,7 +36,7 @@ data UserData = UserData , foreground :: G.GeglBuffer , updateActors :: [Actor String] , keyDown :: Maybe SDL.Keycode - , cameraX :: Int + , cameraX :: Double } load :: IO UserData @@ -145,9 +145,9 @@ draw = do { updateActors = [] } process (nodeGraph M.! "sink") - present (GeglRectangle cameraX 0 800 600) foreground True + present (GeglRectangle (round cameraX) 0 800 600) foreground True render - (Just $ G.GeglRectangle cameraX 0 800 600) + (Just $ G.GeglRectangle (round cameraX) 0 800 600) Nothing update :: Double -> Affection UserData () @@ -160,49 +160,40 @@ update dt = do maybe (return ()) (\code -> do let vel = 400 -- velocity in Pixels per second - leg = floor (vel * dt) + leg = vel * dt (G.PropertyDouble xpos) <- return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ actorProperties $ actors ud M.! "rect" nmap <- return $ M.adjust - (updateProperties $ props $ prop "x" $ + (updateProperties $ props $ prop "x" $ xpos + case code of SDL.KeycodeLeft -> - if xpos > 0 - then xpos - fromIntegral leg - else xpos + if xpos - leg > 0 + then (- leg) + else 0 SDL.KeycodeRight -> - if xpos < 3269 - then xpos + fromIntegral leg - else xpos - _ -> xpos + if xpos + leg < 3269 + then leg + else 0 + _ -> 0 ) "rect" (actors ud) + let { + offset | xpos - cameraX ud > 750 && cameraX ud + leg < 2489 = + case code of + SDL.KeycodeRight -> leg + _ -> 0 + | xpos - cameraX ud < 50 && cameraX ud - leg > 0 = + case code of + SDL.KeycodeLeft -> (-leg) + _ -> 0 + | otherwise = 0 + } putAffection ud { actors = nmap , updateActors = (nmap M.! "rect") : updateActors ud - } - ud2 <- getAffection - (G.PropertyDouble xpos2) <- - return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ - actorProperties $ actors ud M.! "rect" - ncx <- - if xpos2 - fromIntegral (cameraX ud2) > 750 - then return $ - if cameraX ud2 + leg > 2489 && code == SDL.KeycodeRight - then cameraX ud2 - else cameraX ud2 + leg - else - if xpos2 - fromIntegral (cameraX ud2) < 50 - then return $ - if cameraX ud2 - leg < 0 && code == SDL.KeycodeLeft - then cameraX ud2 - else cameraX ud2 - leg - else - return $ cameraX ud2 - putAffection ud2 - { cameraX = ncx + , cameraX = cameraX ud + offset } ) (keyDown ud) From fc452c53ffd18355c004a89550df3ca2a7ac740a Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 29 Mar 2017 00:43:09 +0200 Subject: [PATCH 130/261] new example: game of life --- affection.cabal | 22 ++ examples/example05.hs | 462 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 484 insertions(+) create mode 100644 examples/example05.hs diff --git a/affection.cabal b/affection.cabal index 0daf7f6..23dad16 100644 --- a/affection.cabal +++ b/affection.cabal @@ -177,3 +177,25 @@ executable example04 , 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 + , mtl + , random + , matrix + , random + , monad-parallel + , parallel + else + buildable: False diff --git a/examples/example05.hs b/examples/example05.hs new file mode 100644 index 0000000..e1e53c6 --- /dev/null +++ b/examples/example05.hs @@ -0,0 +1,462 @@ +{-# LANGUAGE RecordWildCards, BangPatterns #-} + +import Affection +import qualified SDL +import qualified GEGL as G +import qualified BABL as B +import qualified Data.Map.Strict as M +import qualified Data.Matrix as X +import Data.List as L +import Data.Maybe (fromJust, catMaybes) +-- import Data.Vector as V +import System.Random (randomRIO) +import Control.Monad as CM (when, unless, foldM) +import qualified Control.Monad.Parallel as MP +import Control.Parallel.Strategies +import Foreign.C.Types +import Foreign.Marshal.Utils + +import Debug.Trace + +dimension :: Int +dimension = 30 + +resolution :: Int +resolution = 600 + +main :: IO () +main = do + conf <- return AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + { windowInitialSize = SDL.V2 + (CInt $ fromIntegral resolution) + (CInt $ fromIntegral resolution) + } + , canvasSize = Nothing + , preLoop = loadMatrix + , eventLoop = handle + , updateLoop = Main.update + , drawLoop = draw + , loadState = load + , cleanUp = clean + } + withAffection conf + +data UserData = UserData + { nodeGraph :: M.Map String G.GeglNode + , foreground :: G.GeglBuffer + , keysDown :: [SDL.Keycode] + , matrix :: X.Matrix Bool + , indices :: [(Int, Int)] + -- , cells :: [((Int, Int), M.Map String G.GeglNode)] + , cells :: [((Int, Int), Actor String)] + , updateActors :: [Actor String] + } + +load :: IO UserData +load = do + traceM "loading" + root <- G.gegl_node_new + traceM "new root node" + let bgProps = props $ do + prop "x" (0 :: Double) + prop "y" (0 :: Double) + prop "width" (fromIntegral resolution :: Double) + prop "height" (fromIntegral resolution :: Double) + prop "color" $ G.RGB 0 0 0 + bg <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" bgProps + traceM "background" + over <- G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 resolution resolution) =<< + B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ + props $ + prop "buffer" buffer + traceM "buffer-sink" + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ + props $ do + prop "width" (fromIntegral resolution :: Double) + prop "height" (fromIntegral resolution :: Double) + G.gegl_node_link_many [bg, over, crop, sink] + nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] + _ <- G.gegl_node_connect_to nop "output" over "aux" + traceM "connections made" + myMap <- return $ M.fromList + [ ("root" , root) + , ("over" , over) + , ("background" , bg) + , ("sink" , sink) + , ("nop" , nop) + , ("crop" , crop) + ] + traceM "building matrix" + let !emptyMatrix = X.matrix dimension dimension (const False) + !indices = [(row, col) | row <- [1..dimension], col <- [1..dimension]] + traceM "building cells" + cells <- foldM (\acc index@(row, col) -> do + tempOver <- G.gegl_node_new_child root $ G.defaultOverOperation + let !scale = (fromIntegral resolution) / (fromIntegral dimension) + tempProps = + props $ do + prop "x" (((fromIntegral col) - 1) * scale :: Double) + prop "y" (((fromIntegral row) - 1) * scale :: Double) + prop "width" (scale :: Double) + prop "height" (scale :: Double) + prop "color" $ G.RGB 1 1 1 + tempRect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" + tempProps + G.gegl_node_connect_to tempRect "output" tempOver "aux" + -- unless (null acc) $ + -- G.gegl_node_link tempOver ((actorNodes $ snd $ head acc) M.! "over") + let tempMap = M.fromList + [ ("over", tempOver) + , ("rect", tempRect) + ] + tempAProps = + map (\p -> ActorProperty + { apName = G.propertyName p + , apValue = G.propertyValue p + , apMapping = Just "rect" + }) tempProps + traceIO $ show index + return $ (index, Actor + { actorProperties = tempAProps + , actorNodes = tempMap + }) : acc + ) + ([] :: [((Int, Int), Actor String)]) + indices + :: IO [((Int, Int), Actor String)] + -- _ <- G.gegl_node_link ((actorNodes $ snd $ last cells) M.! "over") nop + traceM "loading complete" + return UserData + { nodeGraph = myMap + , foreground = buffer + , keysDown = [] + , matrix = emptyMatrix + , indices = indices + , cells = cells + , updateActors = [] + } + +loadMatrix :: Affection UserData () +loadMatrix = do + !init <- liftIO $ X.fromList dimension dimension <$> MP.mapM (const (randomRIO (False, True))) [1..(dimension ^ 2)] + ud <- getAffection + putAffection ud + { matrix = init + } + traceM "matrix initialized" + +getNeighbors :: (Int, Int) -> Affection UserData (X.Matrix Bool) +getNeighbors (row, col) + | row == 1 && col == 1 = do + UserData{..} <- getAffection + return $ X.joinBlocks + ( X.submatrix + dimension + dimension + dimension + dimension + matrix + , X.submatrix + dimension + dimension + col + (col + 1) + matrix + , X.submatrix + row + (row + 1) + dimension + dimension + matrix + , X.submatrix + row + (row + 1) + col + (col + 1) + matrix + ) + | row == 1 && col == dimension = do + UserData{..} <- getAffection + return $ X.joinBlocks + ( X.submatrix + dimension + dimension + (col - 1) + col + matrix + , X.submatrix + dimension + dimension + 1 + 1 + matrix + , X.submatrix + row + (row + 1) + (col - 1) + col + matrix + , X.submatrix + row + (row + 1) + 1 + 1 + matrix + ) + | row == dimension && col == 1 = do + UserData{..} <- getAffection + return $ X.joinBlocks + ( X.submatrix + (row - 1) + row + dimension + dimension + matrix + , X.submatrix + (row - 1) + row + col + (col + 1) + matrix + , X.submatrix + 1 + 1 + dimension + dimension + matrix + , X.submatrix + 1 + 1 + col + (col + 1) + matrix + ) + | row == dimension && col == dimension = do + UserData{..} <- getAffection + return $ X.joinBlocks + ( X.submatrix + (row - 1) + row + (col - 1) + col + matrix + , X.submatrix + (row - 1) + row + 1 + 1 + matrix + , X.submatrix + 1 + 1 + (col - 1) + col + matrix + , X.submatrix + 1 + 1 + 1 + 1 + matrix + ) + | row == 1 = do + UserData{..} <- getAffection + return $ + X.submatrix + dimension + dimension + (col - 1) + (col + 1) + matrix + X.<-> + X.submatrix + row + (row + 1) + (col - 1) + (col + 1) + matrix + | row == dimension = do + UserData{..} <- getAffection + return $ + X.submatrix + (row - 1) + row + (col - 1) + (col + 1) + matrix + X.<-> + X.submatrix + 1 + 1 + (col - 1) + (col + 1) + matrix + | col == 1 = do + UserData{..} <- getAffection + return $ + X.submatrix + (row - 1) + (row + 1) + dimension + dimension + matrix + X.<|> + X.submatrix + (row - 1) + (row + 1) + col + (col + 1) + matrix + | col == dimension = do + UserData{..} <- getAffection + return $ + X.submatrix + (row - 1) + (row + 1) + (col - 1) + col + matrix + X.<|> + X.submatrix + (row - 1) + (row + 1) + 1 + 1 + matrix + | otherwise = do + UserData{..} <- getAffection + return $ X.submatrix (row - 1) (row + 1) (col - 1) (col + 1) matrix + +draw :: Affection UserData () +draw = do + traceM "drawing" + ud@UserData{..} <- getAffection + _ <- liftIO $ MP.mapM + (\(_, a) -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" + ) cells + -- _ <- liftIO $ G.gegl_node_disconnect (nodeGraph M.! "nop") "input" + let livingActors = catMaybes $ map (\coord -> do + if matrix X.! coord + then lookup coord cells + else Nothing + ) indices + unless (null livingActors) $ do + liftIO $ G.gegl_node_link_many $ + map (\a -> (actorNodes a) M.! "over") livingActors + _ <- liftIO $ G.gegl_node_link ((actorNodes $ last livingActors) M.! "over") + (nodeGraph M.! "nop") + return () + -- MP.mapM_ applyProperties updateActors + process (nodeGraph M.! "sink") + present (G.GeglRectangle 0 0 resolution resolution) foreground True + render + Nothing + Nothing + putAffection ud + { updateActors = [] + } + +update :: Double -> Affection UserData () +update dt = do + traceM "updating" + + ud <- getAffection + elapsed <- getElapsedTime + + traceM $ show (1 / dt) ++ " FPS" + traceM $ show $ keysDown ud + new <- MP.mapM (\coord@(row, col) -> do + neighs <- X.toList <$> getNeighbors coord + if True `elem` neighs + then do + let !living = sum $ parMap rpar (fromBool) neighs + if matrix ud X.! coord + then + if living - 1 == 2 || living - 1 == 3 + then + return True + else do + -- tud <- getAffection + -- let nac = updateProperties (props $ do + -- prop "color" $ G.RGB 0 0 0 + -- ) (fromJust $ lookup coord (cells tud)) + -- putAffection tud + -- { updateActors = + -- nac : updateActors tud + -- , cells = (coord, nac) : + -- (deleteBy (\a b -> fst a == fst b) (coord, nac) (cells tud)) + -- } + return False + else + if living == 3 + then do + -- tud <- getAffection + -- let nac = updateProperties (props $ do + -- prop "color" $ G.RGB 1 1 1 + -- ) (fromJust $ lookup coord (cells tud)) + -- putAffection tud + -- { updateActors = + -- nac : updateActors tud + -- , cells = (coord, nac) : + -- (deleteBy (\a b -> fst a == fst b) (coord, nac) (cells tud)) + -- } + return True + else + return False + else + return False + ) (indices ud) + ud2 <- getAffection + putAffection ud2 + { matrix = X.fromList dimension dimension new + } + -- when (floor elapsed < floor (elapsed + dt)) $ do + -- new <- mapM (\coord@(col, row) -> do + -- living <- foldl (\acc b -> if b then acc + 1 else acc) 0 <$> + -- getNeighbors coord + -- if matrix ud X.! coord + -- then + -- if living == 2 || living == 3 then return $ True else return $ False + -- else + -- if living == 3 then return $ True else return $ False + -- ) (indices ud) + -- traceM $ show $ X.fromList 10 10 new + -- putAffection ud + -- { matrix = X.fromList 10 10 new + -- } + + mapM_ (\code -> + when (code == SDL.KeycodeR) loadMatrix + ) (keysDown ud) + +handle :: SDL.EventPayload -> Affection UserData () +handle (SDL.KeyboardEvent dat) = + when (not (SDL.keyboardEventRepeat dat)) $ do + ud <- getAffection + if (SDL.keyboardEventKeyMotion dat == SDL.Pressed) + then + putAffection ud + { keysDown = + SDL.keysymKeycode (SDL.keyboardEventKeysym dat) : keysDown ud + } + else + putAffection ud + { keysDown = + delete (SDL.keysymKeycode $ SDL.keyboardEventKeysym dat) (keysDown ud) + } + +handle (SDL.WindowClosedEvent _) = do + traceM "seeya!" + quit + +handle _ = + return () + +clean :: UserData -> IO () +clean _ = return () From 705f0320358c0bd22f0c61ae456bc46fb04f04e2 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 29 Mar 2017 00:43:59 +0200 Subject: [PATCH 131/261] change keyboard event handling --- examples/example04.hs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/example04.hs b/examples/example04.hs index 0d7a66a..4e179bf 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -35,7 +35,7 @@ data UserData = UserData , actors :: M.Map String (Actor String) , foreground :: G.GeglBuffer , updateActors :: [Actor String] - , keyDown :: Maybe SDL.Keycode + , keysDown :: [SDL.Keycode] , cameraX :: Double } @@ -126,7 +126,7 @@ load = do , actors = actorMap , foreground = buffer , updateActors = [] - , keyDown = Nothing + , keysDown = [] , cameraX = 0 } @@ -157,8 +157,9 @@ update dt = do ud <- getAffection traceM $ show (1 / dt) ++ " FPS" + -- traceM $ show $ keysDown ud - maybe (return ()) (\code -> do + mapM_ (\code -> do let vel = 400 -- velocity in Pixels per second leg = vel * dt (G.PropertyDouble xpos) <- @@ -195,7 +196,7 @@ update dt = do , updateActors = (nmap M.! "rect") : updateActors ud , cameraX = cameraX ud + offset } - ) (keyDown ud) + ) (keysDown ud) handle :: SDL.EventPayload -> Affection UserData () handle (SDL.KeyboardEvent dat) = @@ -204,11 +205,13 @@ handle (SDL.KeyboardEvent dat) = if (SDL.keyboardEventKeyMotion dat == SDL.Pressed) then putAffection ud - { keyDown = Just $ SDL.keysymKeycode $ SDL.keyboardEventKeysym dat + { keysDown = + SDL.keysymKeycode (SDL.keyboardEventKeysym dat) : keysDown ud } else putAffection ud - { keyDown = Nothing + { keysDown = + delete (SDL.keysymKeycode $ SDL.keyboardEventKeysym dat) (keysDown ud) } handle (SDL.WindowClosedEvent _) = do From 66b8f54a07567d4df2a07c7329c49e48c1b589eb Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 29 Mar 2017 00:44:36 +0200 Subject: [PATCH 132/261] better camera handling --- examples/example04.hs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/example04.hs b/examples/example04.hs index 4e179bf..9c926f9 100644 --- a/examples/example04.hs +++ b/examples/example04.hs @@ -135,7 +135,8 @@ drawInit = do ad <- get ud <- getAffection process (nodeGraph ud M.! "sink") - present (GeglRectangle 0 0 3289 600) (foreground ud) True + present (GeglRectangle (round $ cameraX ud) 0 800 600) (foreground ud) True + render Nothing Nothing draw :: Affection UserData () draw = do @@ -181,14 +182,10 @@ update dt = do "rect" (actors ud) let { - offset | xpos - cameraX ud > 750 && cameraX ud + leg < 2489 = - case code of - SDL.KeycodeRight -> leg - _ -> 0 - | xpos - cameraX ud < 50 && cameraX ud - leg > 0 = - case code of - SDL.KeycodeLeft -> (-leg) - _ -> 0 + offset | xpos - cameraX ud > 750 && cameraX ud + leg < 2489 && code == SDL.KeycodeRight = + leg + | xpos - cameraX ud < 50 && cameraX ud - leg > 0 && code == SDL.KeycodeLeft = + (-leg) | otherwise = 0 } putAffection ud From 121c9fb1202a103766d960ea7dbdb5983c1589ef Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 29 Mar 2017 00:46:03 +0200 Subject: [PATCH 133/261] to parallel and not to parallel --- src/Affection.hs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index a2266f2..430d57b 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -128,7 +128,7 @@ withAffection AffectionConfig{..} = do -- get state ad <- get -- clean draw requests from last run - mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) + MP.mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time -- SDL.clear renderer -- compute dt and update elapsedTime @@ -141,7 +141,7 @@ withAffection AffectionConfig{..} = do } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents - MP.mapM_ eventLoop evs + mapM_ eventLoop evs -- execute user defined update loop updateLoop dt -- execute user defined draw loop @@ -149,12 +149,11 @@ withAffection AffectionConfig{..} = do -- handle all new draw requests ad2 <- get clear <- catMaybes <$> - mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) + MP.mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) -- save all draw requests to clear in next run put $ ad2 { drawStack = clear } -- actual drawing - -- SDL.copy (windowRenderer ad2) texture Nothing Nothing SDL.present (windowRenderer ad2) -- save new time liftIO $ writeIORef execTime now From 1875797cb109a3bc8c12039a21852e96262eefb8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 29 Mar 2017 16:37:36 +0200 Subject: [PATCH 134/261] optimization --- examples/example05.hs | 335 +++++++++--------------------------------- 1 file changed, 73 insertions(+), 262 deletions(-) diff --git a/examples/example05.hs b/examples/example05.hs index e1e53c6..612770e 100644 --- a/examples/example05.hs +++ b/examples/example05.hs @@ -19,7 +19,7 @@ import Foreign.Marshal.Utils import Debug.Trace dimension :: Int -dimension = 30 +dimension = 60 resolution :: Int resolution = 600 @@ -35,7 +35,7 @@ main = do (CInt $ fromIntegral resolution) } , canvasSize = Nothing - , preLoop = loadMatrix + , preLoop = loadList , eventLoop = handle , updateLoop = Main.update , drawLoop = draw @@ -48,9 +48,8 @@ data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer , keysDown :: [SDL.Keycode] - , matrix :: X.Matrix Bool + , liveIndices :: [(Int, Int)] , indices :: [(Int, Int)] - -- , cells :: [((Int, Int), M.Map String G.GeglNode)] , cells :: [((Int, Int), Actor String)] , updateActors :: [Actor String] } @@ -109,8 +108,6 @@ load = do tempRect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" tempProps G.gegl_node_connect_to tempRect "output" tempOver "aux" - -- unless (null acc) $ - -- G.gegl_node_link tempOver ((actorNodes $ snd $ head acc) M.! "over") let tempMap = M.fromList [ ("over", tempOver) , ("rect", tempRect) @@ -130,234 +127,73 @@ load = do ([] :: [((Int, Int), Actor String)]) indices :: IO [((Int, Int), Actor String)] - -- _ <- G.gegl_node_link ((actorNodes $ snd $ last cells) M.! "over") nop traceM "loading complete" return UserData { nodeGraph = myMap , foreground = buffer , keysDown = [] - , matrix = emptyMatrix , indices = indices + , liveIndices = [] , cells = cells , updateActors = [] } -loadMatrix :: Affection UserData () -loadMatrix = do - !init <- liftIO $ X.fromList dimension dimension <$> MP.mapM (const (randomRIO (False, True))) [1..(dimension ^ 2)] - ud <- getAffection +loadList :: Affection UserData () +loadList = do + ud@UserData{..} <- getAffection + init <- foldM (\acc coord -> do + trig <- liftIO $ randomRIO (True, False) + if trig then return (coord : acc) else return acc + ) [] indices putAffection ud - { matrix = init + { liveIndices = init } - traceM "matrix initialized" -getNeighbors :: (Int, Int) -> Affection UserData (X.Matrix Bool) -getNeighbors (row, col) - | row == 1 && col == 1 = do +getNeighbors :: (Int, Int) -> Affection UserData [(Int, Int)] +getNeighbors (row, col) = + do UserData{..} <- getAffection - return $ X.joinBlocks - ( X.submatrix - dimension - dimension - dimension - dimension - matrix - , X.submatrix - dimension - dimension - col - (col + 1) - matrix - , X.submatrix - row - (row + 1) - dimension - dimension - matrix - , X.submatrix - row - (row + 1) - col - (col + 1) - matrix - ) - | row == 1 && col == dimension = do - UserData{..} <- getAffection - return $ X.joinBlocks - ( X.submatrix - dimension - dimension - (col - 1) - col - matrix - , X.submatrix - dimension - dimension - 1 - 1 - matrix - , X.submatrix - row - (row + 1) - (col - 1) - col - matrix - , X.submatrix - row - (row + 1) - 1 - 1 - matrix - ) - | row == dimension && col == 1 = do - UserData{..} <- getAffection - return $ X.joinBlocks - ( X.submatrix - (row - 1) - row - dimension - dimension - matrix - , X.submatrix - (row - 1) - row - col - (col + 1) - matrix - , X.submatrix - 1 - 1 - dimension - dimension - matrix - , X.submatrix - 1 - 1 - col - (col + 1) - matrix - ) - | row == dimension && col == dimension = do - UserData{..} <- getAffection - return $ X.joinBlocks - ( X.submatrix - (row - 1) - row - (col - 1) - col - matrix - , X.submatrix - (row - 1) - row - 1 - 1 - matrix - , X.submatrix - 1 - 1 - (col - 1) - col - matrix - , X.submatrix - 1 - 1 - 1 - 1 - matrix - ) - | row == 1 = do - UserData{..} <- getAffection - return $ - X.submatrix - dimension - dimension - (col - 1) - (col + 1) - matrix - X.<-> - X.submatrix - row - (row + 1) - (col - 1) - (col + 1) - matrix - | row == dimension = do - UserData{..} <- getAffection - return $ - X.submatrix - (row - 1) - row - (col - 1) - (col + 1) - matrix - X.<-> - X.submatrix - 1 - 1 - (col - 1) - (col + 1) - matrix - | col == 1 = do - UserData{..} <- getAffection - return $ - X.submatrix - (row - 1) - (row + 1) - dimension - dimension - matrix - X.<|> - X.submatrix - (row - 1) - (row + 1) - col - (col + 1) - matrix - | col == dimension = do - UserData{..} <- getAffection - return $ - X.submatrix - (row - 1) - (row + 1) - (col - 1) - col - matrix - X.<|> - X.submatrix - (row - 1) - (row + 1) - 1 - 1 - matrix - | otherwise = do - UserData{..} <- getAffection - return $ X.submatrix (row - 1) (row + 1) (col - 1) (col + 1) matrix + mapM (\(pr, pc) -> + return (overflow(row + pr), overflow(col + pc)) + ) lcs + where + overflow x + | x < 1 = dimension + | x > dimension = 1 + | otherwise = x + +lcs :: [(Int, Int)] +lcs = + [ (-1, -1) + , (-1, 0) + , (-1, 1) + , (0, -1) + -- , (0, 0) + , (0, 1) + , (1, -1) + , (1, 0) + , (1, 1) + ] draw :: Affection UserData () draw = do traceM "drawing" ud@UserData{..} <- getAffection - _ <- liftIO $ MP.mapM - (\(_, a) -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" - ) cells - -- _ <- liftIO $ G.gegl_node_disconnect (nodeGraph M.! "nop") "input" - let livingActors = catMaybes $ map (\coord -> do - if matrix X.! coord - then lookup coord cells - else Nothing - ) indices + let livingActors = catMaybes $ parMap rpar (\i -> lookup i cells) liveIndices unless (null livingActors) $ do liftIO $ G.gegl_node_link_many $ - map (\a -> (actorNodes a) M.! "over") livingActors + parMap rpar (\a -> (actorNodes a) M.! "over") livingActors _ <- liftIO $ G.gegl_node_link ((actorNodes $ last livingActors) M.! "over") (nodeGraph M.! "nop") return () - -- MP.mapM_ applyProperties updateActors process (nodeGraph M.! "sink") present (G.GeglRectangle 0 0 resolution resolution) foreground True render Nothing Nothing + _ <- liftIO $ MP.mapM + (\a -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" + ) livingActors putAffection ud { updateActors = [] } @@ -371,70 +207,45 @@ update dt = do traceM $ show (1 / dt) ++ " FPS" traceM $ show $ keysDown ud - new <- MP.mapM (\coord@(row, col) -> do - neighs <- X.toList <$> getNeighbors coord - if True `elem` neighs - then do - let !living = sum $ parMap rpar (fromBool) neighs - if matrix ud X.! coord - then - if living - 1 == 2 || living - 1 == 3 - then - return True - else do - -- tud <- getAffection - -- let nac = updateProperties (props $ do - -- prop "color" $ G.RGB 0 0 0 - -- ) (fromJust $ lookup coord (cells tud)) - -- putAffection tud - -- { updateActors = - -- nac : updateActors tud - -- , cells = (coord, nac) : - -- (deleteBy (\a b -> fst a == fst b) (coord, nac) (cells tud)) - -- } - return False - else - if living == 3 - then do - -- tud <- getAffection - -- let nac = updateProperties (props $ do - -- prop "color" $ G.RGB 1 1 1 - -- ) (fromJust $ lookup coord (cells tud)) - -- putAffection tud - -- { updateActors = - -- nac : updateActors tud - -- , cells = (coord, nac) : - -- (deleteBy (\a b -> fst a == fst b) (coord, nac) (cells tud)) - -- } - return True - else - return False - else - return False - ) (indices ud) + + relevant <- getRelevant (liveIndices ud) + + new <- catMaybes <$> MP.mapM (\coord@(row, col) -> do + neighs <- + foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 <$> + getNeighbors coord + let ret + | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = + Just coord + | neighs == 3 = + Just coord + | otherwise = + Nothing + return ret + ) relevant + ud2 <- getAffection putAffection ud2 - { matrix = X.fromList dimension dimension new + { liveIndices = new } - -- when (floor elapsed < floor (elapsed + dt)) $ do - -- new <- mapM (\coord@(col, row) -> do - -- living <- foldl (\acc b -> if b then acc + 1 else acc) 0 <$> - -- getNeighbors coord - -- if matrix ud X.! coord - -- then - -- if living == 2 || living == 3 then return $ True else return $ False - -- else - -- if living == 3 then return $ True else return $ False - -- ) (indices ud) - -- traceM $ show $ X.fromList 10 10 new - -- putAffection ud - -- { matrix = X.fromList 10 10 new - -- } mapM_ (\code -> - when (code == SDL.KeycodeR) loadMatrix + when (code == SDL.KeycodeR) loadList ) (keysDown ud) +getRelevant :: [(Int, Int)] -> Affection UserData [(Int, Int)] +getRelevant ls = + getRelevant' ls + where + getRelevant' xs = foldM (\acc x -> do + neighs <- getNeighbors x + let slice = (x : neighs) + rels = foldl (\a y -> + if y `elem` acc then a else y : a + ) [] slice + return (acc ++ rels) + ) [] xs + handle :: SDL.EventPayload -> Affection UserData () handle (SDL.KeyboardEvent dat) = when (not (SDL.keyboardEventRepeat dat)) $ do From 63a493ed19b3bcf4c9a56c438e85bb75a3026487 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 17 Apr 2017 12:40:17 +0200 Subject: [PATCH 135/261] blerg --- affection.cabal | 2 + examples/example05.1.hs | 294 +++++++++++++++++++++++++++++ examples/example05.hs | 121 ++++++++---- src/Affection.hs | 1 + src/Affection/Actor.hs | 28 ++- src/Affection/Animation.hs | 49 +++++ src/Affection/Draw.hs | 33 ---- src/Affection/MouseInteractable.hs | 8 +- src/Affection/Particle.hs | 12 +- src/Affection/StateMachine.hs | 2 +- 10 files changed, 460 insertions(+), 90 deletions(-) create mode 100644 examples/example05.1.hs create mode 100644 src/Affection/Animation.hs diff --git a/affection.cabal b/affection.cabal index 23dad16..99db33f 100644 --- a/affection.cabal +++ b/affection.cabal @@ -43,6 +43,7 @@ library , Affection.MouseInteractable , Affection.Property , Affection.Actor + , Affection.Animation default-extensions: OverloadedStrings -- Modules included in this library but not exported. @@ -191,6 +192,7 @@ executable example05 , gegl , babl , containers + , unordered-containers , mtl , random , matrix diff --git a/examples/example05.1.hs b/examples/example05.1.hs new file mode 100644 index 0000000..2758c28 --- /dev/null +++ b/examples/example05.1.hs @@ -0,0 +1,294 @@ +{-# LANGUAGE RecordWildCards, BangPatterns #-} + +import Affection +import qualified SDL +import qualified GEGL as G +import qualified BABL as B +import qualified Data.Map.Strict as M +import qualified Data.Matrix as X +import Data.List as L +import Data.Maybe (fromJust, catMaybes) +-- import Data.Vector as V +import System.Random (randomRIO) +import Control.Monad as CM (when, unless, foldM) +import qualified Control.Monad.Parallel as MP +import Control.Parallel.Strategies +import Foreign.C.Types +import Foreign.Marshal.Utils + +import Debug.Trace + +dimension :: Int +dimension = 60 + +resolution :: Int +resolution = 600 + +main :: IO () +main = do + conf <- return AffectionConfig + { initComponents = All + , windowTitle = "Affection: example00" + , windowConfig = SDL.defaultWindow + { windowInitialSize = SDL.V2 + (CInt $ fromIntegral resolution) + (CInt $ fromIntegral resolution) + } + , canvasSize = Nothing + , preLoop = loadList + , eventLoop = handle + , updateLoop = Main.update + , drawLoop = draw + , loadState = load + , cleanUp = clean + } + withAffection conf + +data UserData = UserData + { nodeGraph :: M.Map String G.GeglNode + , foreground :: G.GeglBuffer + , keysDown :: [SDL.Keycode] + , liveIndices :: [(Int, Int)] + , indices :: [(Int, Int)] + -- , cells :: [((Int, Int), Actor String)] + -- , updateActors :: [Actor String] + } + +load :: IO UserData +load = do + traceM "loading" + root <- G.gegl_node_new + traceM "new root node" + let bgProps = props $ do + prop "x" (0 :: Double) + prop "y" (0 :: Double) + prop "width" (fromIntegral resolution :: Double) + prop "height" (fromIntegral resolution :: Double) + prop "color" $ G.RGB 0 0 0 + bg <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" bgProps + traceM "background" + over <- G.gegl_node_new_child root G.defaultOverOperation + traceM "over" + buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 resolution resolution) =<< + B.babl_format (B.PixelFormat B.RGBA B.CFfloat) + sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ + props $ + prop "buffer" buffer + traceM "buffer-sink" + crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ + props $ do + prop "width" (fromIntegral resolution :: Double) + prop "height" (fromIntegral resolution :: Double) + G.gegl_node_link_many [bg, over, crop, sink] + nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] + _ <- G.gegl_node_connect_to nop "output" over "aux" + traceM "connections made" + myMap <- return $ M.fromList + [ ("root" , root) + , ("over" , over) + , ("background" , bg) + , ("sink" , sink) + , ("nop" , nop) + , ("crop" , crop) + ] + traceM "building matrix" + let !emptyMatrix = X.matrix dimension dimension (const False) + !indices = [(row, col) | row <- [1..dimension], col <- [1..dimension]] + -- traceM "building cells" + -- cells <- foldM (\acc index@(row, col) -> do + -- tempOver <- G.gegl_node_new_child root $ G.defaultOverOperation + -- let !scale = (fromIntegral resolution) / (fromIntegral dimension) + -- tempProps = + -- props $ do + -- prop "x" (((fromIntegral col) - 1) * scale :: Double) + -- prop "y" (((fromIntegral row) - 1) * scale :: Double) + -- prop "width" (scale :: Double) + -- prop "height" (scale :: Double) + -- prop "color" $ G.RGB 1 1 1 + -- tempRect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" + -- tempProps + -- G.gegl_node_connect_to tempRect "output" tempOver "aux" + -- let tempMap = M.fromList + -- [ ("over", tempOver) + -- , ("rect", tempRect) + -- ] + -- tempAProps = + -- map (\p -> ActorProperty + -- { apName = G.propertyName p + -- , apValue = G.propertyValue p + -- , apMapping = Just "rect" + -- }) tempProps + -- traceIO $ show index + -- return $ (index, Actor + -- { actorProperties = tempAProps + -- , actorNodes = tempMap + -- }) : acc + -- ) + -- ([] :: [((Int, Int), Actor String)]) + -- indices + -- :: IO [((Int, Int), Actor String)] + traceM "loading complete" + return UserData + { nodeGraph = myMap + , foreground = buffer + , keysDown = [] + , indices = indices + , liveIndices = [] + -- , cells = cells + -- , updateActors = [] + } + +loadList :: Affection UserData () +loadList = do + ud@UserData{..} <- getAffection + init <- foldM (\acc coord -> do + trig <- liftIO $ randomRIO (True, False) + if trig then return (coord : acc) else return acc + ) [] indices + putAffection ud + { liveIndices = init + } + +getNeighbors :: (Int, Int) -> Affection UserData [(Int, Int)] +getNeighbors (row, col) = + do + UserData{..} <- getAffection + mapM (\(pr, pc) -> + return (overflow(row + pr), overflow(col + pc)) + ) lcs + where + overflow x + | x < 1 = dimension + | x > dimension = 1 + | otherwise = x + +lcs :: [(Int, Int)] +lcs = + [ (-1, -1) + , (-1, 0) + , (-1, 1) + , (0, -1) + -- , (0, 0) + , (0, 1) + , (1, -1) + , (1, 0) + , (1, 1) + ] + +draw :: Affection UserData () +draw = do + traceM "drawing" + ad <- get + ud@UserData{..} <- getAffection + SDL.rendererDrawColor (windowRenderer ad) SDL.$= (SDL.V4 0 0 0 255) + SDL.clear (windowRenderer ad) + SDL.rendererDrawColor (windowRenderer ad) SDL.$= (SDL.V4 255 255 255 255) + MP.mapM_ (\(row, col) -> + let scale = floor (fromIntegral resolution / fromIntegral dimension) + in + SDL.fillRect + (windowRenderer ad) + (Just ( SDL.Rectangle + (SDL.P (SDL.V2 + (CInt $ fromIntegral $ (col - 1) * scale) + (CInt $ fromIntegral $ (row - 1) * scale) + )) + (SDL.V2 + (CInt $ fromIntegral $ scale) + (CInt $ fromIntegral $ scale) + ) + )) + ) liveIndices + -- render Nothing Nothing + -- let livingActors = catMaybes $ parMap rpar (\i -> lookup i cells) liveIndices + -- unless (null livingActors) $ do + -- liftIO $ G.gegl_node_link_many $ + -- parMap rpar (\a -> (actorNodes a) M.! "over") livingActors + -- _ <- liftIO $ G.gegl_node_link ((actorNodes $ last livingActors) M.! "over") + -- (nodeGraph M.! "nop") + -- return () + -- process (nodeGraph M.! "sink") + -- present (G.GeglRectangle 0 0 resolution resolution) foreground True + -- render + -- Nothing + -- Nothing + -- _ <- liftIO $ MP.mapM + -- (\a -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" + -- ) livingActors + -- putAffection ud + -- { updateActors = [] + -- } + +update :: Double -> Affection UserData () +update dt = do + traceM "updating" + + ud <- getAffection + elapsed <- getElapsedTime + + traceM $ show (1 / dt) ++ " FPS" + traceM $ show $ keysDown ud + + relevant <- getRelevant (liveIndices ud) + + new <- catMaybes <$> MP.mapM (\coord@(row, col) -> do + neighs <- + foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 <$> + getNeighbors coord + let ret + | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = + Just coord + | neighs == 3 = + Just coord + | otherwise = + Nothing + return ret + ) relevant + + ud2 <- getAffection + putAffection ud2 + { liveIndices = new + } + + mapM_ (\code -> + when (code == SDL.KeycodeR) loadList + ) (keysDown ud) + +getRelevant :: [(Int, Int)] -> Affection UserData [(Int, Int)] +getRelevant ls = + getRelevant' ls + where + getRelevant' xs = foldM (\acc x -> do + neighs <- getNeighbors x + let slice = (x : neighs) + rels = foldl (\a y -> + if y `elem` acc then a else y : a + ) [] slice + return (acc ++ rels) + ) [] xs + +handle :: SDL.EventPayload -> Affection UserData () +handle (SDL.KeyboardEvent dat) = + when (not (SDL.keyboardEventRepeat dat)) $ do + ud <- getAffection + if (SDL.keyboardEventKeyMotion dat == SDL.Pressed) + then + putAffection ud + { keysDown = + SDL.keysymKeycode (SDL.keyboardEventKeysym dat) : keysDown ud + } + else + putAffection ud + { keysDown = + delete (SDL.keysymKeycode $ SDL.keyboardEventKeysym dat) (keysDown ud) + } + +handle (SDL.WindowClosedEvent _) = do + traceM "seeya!" + quit + +handle _ = + return () + +clean :: UserData -> IO () +clean _ = return () diff --git a/examples/example05.hs b/examples/example05.hs index 612770e..b30b28f 100644 --- a/examples/example05.hs +++ b/examples/example05.hs @@ -5,12 +5,15 @@ import qualified SDL import qualified GEGL as G import qualified BABL as B import qualified Data.Map.Strict as M +import qualified Data.HashMap.Strict as HM +import qualified Data.HashSet as HS import qualified Data.Matrix as X import Data.List as L +import Data.Foldable (foldrM, foldlM) import Data.Maybe (fromJust, catMaybes) -- import Data.Vector as V import System.Random (randomRIO) -import Control.Monad as CM (when, unless, foldM) +import Control.Monad as CM (when, unless) import qualified Control.Monad.Parallel as MP import Control.Parallel.Strategies import Foreign.C.Types @@ -48,9 +51,9 @@ data UserData = UserData { nodeGraph :: M.Map String G.GeglNode , foreground :: G.GeglBuffer , keysDown :: [SDL.Keycode] - , liveIndices :: [(Int, Int)] + , liveIndices :: HS.HashSet (Int, Int) , indices :: [(Int, Int)] - , cells :: [((Int, Int), Actor String)] + , cells :: HM.HashMap (Int, Int) (Actor String) , updateActors :: [Actor String] } @@ -95,7 +98,7 @@ load = do let !emptyMatrix = X.matrix dimension dimension (const False) !indices = [(row, col) | row <- [1..dimension], col <- [1..dimension]] traceM "building cells" - cells <- foldM (\acc index@(row, col) -> do + cells <- foldrM (\index@(row, col) acc -> do tempOver <- G.gegl_node_new_child root $ G.defaultOverOperation let !scale = (fromIntegral resolution) / (fromIntegral dimension) tempProps = @@ -119,21 +122,21 @@ load = do , apMapping = Just "rect" }) tempProps traceIO $ show index - return $ (index, Actor + return $ HM.insert index Actor { actorProperties = tempAProps , actorNodes = tempMap - }) : acc + } + acc ) - ([] :: [((Int, Int), Actor String)]) + HM.empty indices - :: IO [((Int, Int), Actor String)] traceM "loading complete" return UserData { nodeGraph = myMap , foreground = buffer , keysDown = [] , indices = indices - , liveIndices = [] + , liveIndices = HS.empty , cells = cells , updateActors = [] } @@ -141,21 +144,18 @@ load = do loadList :: Affection UserData () loadList = do ud@UserData{..} <- getAffection - init <- foldM (\acc coord -> do + -- init <- foldrM (\coord acc -> do + init <- foldlM (\acc coord -> do trig <- liftIO $ randomRIO (True, False) - if trig then return (coord : acc) else return acc - ) [] indices + if trig then return (HS.insert coord acc) else return acc + ) HS.empty indices putAffection ud { liveIndices = init } -getNeighbors :: (Int, Int) -> Affection UserData [(Int, Int)] +getNeighbors :: (Int, Int) -> [(Int, Int)] getNeighbors (row, col) = - do - UserData{..} <- getAffection - mapM (\(pr, pc) -> - return (overflow(row + pr), overflow(col + pc)) - ) lcs + map (\(pr, pc) -> (overflow(row + pr), overflow(col + pc))) lcs where overflow x | x < 1 = dimension @@ -179,7 +179,7 @@ draw :: Affection UserData () draw = do traceM "drawing" ud@UserData{..} <- getAffection - let livingActors = catMaybes $ parMap rpar (\i -> lookup i cells) liveIndices + let livingActors = parMap rpar (cells HM.!) $ HS.toList liveIndices unless (null livingActors) $ do liftIO $ G.gegl_node_link_many $ parMap rpar (\a -> (actorNodes a) M.! "over") livingActors @@ -191,9 +191,9 @@ draw = do render Nothing Nothing - _ <- liftIO $ MP.mapM - (\a -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" - ) livingActors + -- _ <- liftIO $ MP.mapM + -- (\a -> G.gegl_node_disconnect (actorNodes a M.! "over") "input" + -- ) livingActors putAffection ud { updateActors = [] } @@ -210,19 +210,48 @@ update dt = do relevant <- getRelevant (liveIndices ud) - new <- catMaybes <$> MP.mapM (\coord@(row, col) -> do - neighs <- - foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 <$> - getNeighbors coord - let ret + -- let new = HS.fromList $ catMaybes $ parMap rpar (\coord@(row, col) -> + -- let neighs = + -- foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 $ + -- getNeighbors coord + -- ret + -- | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = + -- Just coord + -- | neighs == 3 = + -- Just coord + -- | otherwise = + -- Nothing + -- in ret + -- ) (HS.toList relevant) + + new <- foldrM (\coord@(row, col) acc -> do + -- new <- foldlM (\acc coord@(row, col) -> do + let !neighs = sum $ + map (\n -> if n `elem` liveIndices ud then 1 else 0)$ + getNeighbors coord + ret | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = - Just coord + HS.insert coord acc | neighs == 3 = - Just coord + HS.insert coord acc | otherwise = - Nothing + acc return ret - ) relevant + ) HS.empty relevant + + -- new <- catMaybes <$> MP.mapM (\coord@(row, col) -> do + -- neighs <- + -- foldl (\acc n -> if n `elem` liveIndices ud then acc + 1 else acc) 0 <$> + -- getNeighbors coord + -- let ret + -- | coord `elem` liveIndices ud && (neighs == 2 || neighs == 3) = + -- Just coord + -- | neighs == 3 = + -- Just coord + -- | otherwise = + -- Nothing + -- return ret + -- ) relevant ud2 <- getAffection putAffection ud2 @@ -233,18 +262,19 @@ update dt = do when (code == SDL.KeycodeR) loadList ) (keysDown ud) -getRelevant :: [(Int, Int)] -> Affection UserData [(Int, Int)] -getRelevant ls = +getRelevant :: HS.HashSet (Int, Int) -> Affection UserData (HS.HashSet (Int, Int)) +getRelevant ls = do getRelevant' ls where - getRelevant' xs = foldM (\acc x -> do - neighs <- getNeighbors x - let slice = (x : neighs) - rels = foldl (\a y -> - if y `elem` acc then a else y : a - ) [] slice - return (acc ++ rels) - ) [] xs + getRelevant' xs = foldlM (\acc x -> + let neighs = getNeighbors x + slice = (x : neighs) + rels = foldr (\y a -> + -- rels = foldl (\a y -> + if y `elem` acc then a else HS.insert y a + ) HS.empty slice + in return (acc `HS.union` rels) + ) HS.empty xs handle :: SDL.EventPayload -> Affection UserData () handle (SDL.KeyboardEvent dat) = @@ -271,3 +301,12 @@ handle _ = clean :: UserData -> IO () clean _ = return () + +parHSMap f = (`using` parTraversable rpar) . HM.map f + +-- catHSMaybe = +-- foldr (\x acc -> +-- case x of +-- Just a -> a `HS.insert` acc +-- Nothing -> acc +-- ) HS.empty diff --git a/src/Affection.hs b/src/Affection.hs index 430d57b..ea65131 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -41,6 +41,7 @@ import Affection.StateMachine as A import Affection.MouseInteractable as A import Affection.Property as A import Affection.Actor as A +import Affection.Animation as A import qualified BABL as B diff --git a/src/Affection/Actor.hs b/src/Affection/Actor.hs index cd773c6..e7dff80 100644 --- a/src/Affection/Actor.hs +++ b/src/Affection/Actor.hs @@ -3,11 +3,12 @@ -- | This module implements the Actor, a Datastructure binding a 'G.GeglNode' -- to a game asset module Affection.Actor - ( Actor(..) - , ActorProperty(..) - , updateProperties - , applyProperties - ) where + ( Actor(..) + , ActorProperty(..) + , updateProperties + , applyProperties + , getProperty + ) where import qualified GEGL as G @@ -20,13 +21,14 @@ import Affection.Types data Ord a => Actor a = Actor { actorProperties :: [ActorProperty a] , actorNodes :: M.Map a G.GeglNode - } + , actorFlange :: G.GeglNode + } deriving (Eq) data Ord a => ActorProperty a = ActorProperty { apName :: String , apValue :: G.PropertyValue - , apMapping :: Maybe a - } + , apMapping :: Maybe (a, String) + } deriving (Eq) updateProperties :: Ord a => [G.Property] -> Actor a -> Actor a updateProperties ps act@Actor{..} = @@ -48,7 +50,13 @@ applyProperties :: Ord a => Actor a -> Affection us () applyProperties Actor{..} = MP.mapM_ (\(ActorProperty{..}) -> maybe (return ()) (\m -> - liftIO $ G.gegl_node_set (actorNodes M.! m) $ G.Operation "" $ - (G.Property apName apValue) : [] + liftIO $ G.gegl_node_set (actorNodes M.! fst m) $ G.Operation "" $ + (G.Property (snd m) apValue) : [] ) apMapping ) actorProperties + +getProperty :: Ord a => String -> Actor a -> Maybe G.PropertyValue +getProperty name act = + case find (\a -> name == apName a) $ actorProperties act of + Just p -> Just $ apValue p + Nothing -> Nothing diff --git a/src/Affection/Animation.hs b/src/Affection/Animation.hs new file mode 100644 index 0000000..0bafb9f --- /dev/null +++ b/src/Affection/Animation.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE RecordWildCards #-} + +module Affection.Animation + ( SpriteAnimation(..) + , runAnimation + ) where + +import Affection.Actor +import Affection.Types +import Affection.Property + +import qualified GEGL as G + +import Control.Monad.State + +data SpriteAnimation = SpriteAnimation + { sanimCurrentFrame :: Int + , sanimSprites :: [FilePath] + , sanimFrameDuration :: Double + , sanimLoop :: Bool + , sanimLastChange :: Double + , sanimPropName :: String + } + +runAnimation + :: Ord a + => Actor a + -> SpriteAnimation + -> Affection us (SpriteAnimation, Actor a) +runAnimation act anim@SpriteAnimation{..} = do + ad <- get + let elapsed = elapsedTime ad + if elapsed - sanimLastChange > sanimFrameDuration + then do + let nframe = + if sanimCurrentFrame + 1 > length sanimSprites + then + if sanimLoop then 1 else sanimCurrentFrame + else sanimCurrentFrame + 1 + nact = + updateProperties + (props $ prop sanimPropName $ sanimSprites !! (nframe - 1)) + act + return (anim + { sanimCurrentFrame = nframe + , sanimLastChange = elapsed + }, nact) + else + return (anim, act) diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index c02cd1e..21c4cd8 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -101,27 +101,6 @@ render msrc mtgt = src = maybe Nothing (Just . toSdlRect) msrc tgt = maybe Nothing (Just . toSdlRect) mtgt --- putToSurface --- :: Ptr a --- -> G.GeglRectangle --- -> Int --- -> Int --- -> DrawRequest --- -> Affection us () --- putToSurface pixels realRect stride cpp DrawRequest{..} = do --- ad <- get --- liftIO $ SDL.lockSurface $ drawSurface ad --- liftIO $ G.gegl_buffer_get --- requestBuffer --- (Just realRect) --- 1 --- (Just $ drawFormat ad) --- (pixels `plusPtr` --- (rectangleX realRect * cpp + rectangleY realRect * stride)) --- stride --- G.GeglAbyssNone --- liftIO $ SDL.unlockSurface $ drawSurface ad - putToTexture :: G.GeglRectangle -> Int @@ -143,7 +122,6 @@ putToTexture realRect stride cpp DrawRequest{..} = do (destPtr, destStride) <- SDL.lockTexture (drawTexture ad) (Just destRect) - -- destPtr <- liftIO $ malloc :: (Ptr ByteString) liftIO $ G.gegl_buffer_get requestBuffer (Just realRect) @@ -152,17 +130,6 @@ putToTexture realRect stride cpp DrawRequest{..} = do destPtr stride G.GeglAbyssNone - -- pixelData <- liftIO $ peekArray - -- (rectangleWidth realRect * rectangleHeight realRect) - -- destPtr - -- SDL.updateTexture - -- (drawTexture ad) - -- (Just $ SDL.Rectangle - -- (SDL.P $ SDL.V2 (rectangleX realRect) (rectangleY realRect)) - -- (SDL.V2 (rectangleWidth realRect) (rectangleHeight realRect)) - -- ) - -- pixelData - -- (CInt $ fromIntegral stride) SDL.unlockTexture $ drawTexture ad -- | function for handling 'DrawRequest's and updating the output diff --git a/src/Affection/MouseInteractable.hs b/src/Affection/MouseInteractable.hs index 9af9a64..e3a6436 100644 --- a/src/Affection/MouseInteractable.hs +++ b/src/Affection/MouseInteractable.hs @@ -23,11 +23,11 @@ class MouseClickable a us where -- This function does not consume provided events, but passes them on. handleMouseClicks :: (Foldable t, MouseClickable clickable us) - => SDL.Event -- ^ Piped event in - -> t clickable -- ^ 'MouseClickable' elemt to be checked - -> Affection us SDL.Event -- ^ Unaltered event + => SDL.EventPayload -- ^ Piped event in + -> t clickable -- ^ 'MouseClickable' elemt to be checked + -> Affection us SDL.EventPayload -- ^ Unaltered event handleMouseClicks e clickables = - case SDL.eventPayload e of + case e of SDL.MouseButtonEvent dat -> do mapM_ (\clickable -> do let SDL.P (SDL.V2 x y) = SDL.mouseButtonEventPos dat diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 0623a51..0518cd3 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -34,7 +34,17 @@ updateParticle -> Affection us [Particle] -- ^ processed 'Particle's updateParticle time func l = - updateParticle' time func l + catMaybes <$> MP.mapM (\p -> do + now <- elapsedTime <$> get + if particleCreation p + particleTimeToLive p < now + then do + dropParticle p + return Nothing + else do + np <- func time p + return $ Just np + ) l + -- updateParticle' time func l where updateParticle' _ _ [] = return [] updateParticle' dt fun [p] = do diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs index 7ea9597..067ae78 100644 --- a/src/Affection/StateMachine.hs +++ b/src/Affection/StateMachine.hs @@ -9,6 +9,6 @@ import qualified SDL class StateMachine a us where smLoad :: a -> Affection us () smUpdate :: a -> Double -> Affection us () - smEvent :: a -> Double -> SDL.Event -> Affection us () + smEvent :: a -> SDL.EventPayload -> Affection us () smDraw :: a -> Affection us () smClean :: a -> Affection us () From 17822564c7642cdc60b65d4ece588589065c040b Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 20 May 2017 10:24:05 +0200 Subject: [PATCH 136/261] temporary disabling examples --- affection.cabal | 178 +++++++++++++++++++++--------------------- examples/example05.hs | 2 +- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/affection.cabal b/affection.cabal index 99db33f..866123f 100644 --- a/affection.cabal +++ b/affection.cabal @@ -89,95 +89,95 @@ library -- , mtl -- 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 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 diff --git a/examples/example05.hs b/examples/example05.hs index b30b28f..e95f660 100644 --- a/examples/example05.hs +++ b/examples/example05.hs @@ -119,7 +119,7 @@ load = do map (\p -> ActorProperty { apName = G.propertyName p , apValue = G.propertyValue p - , apMapping = Just "rect" + , apMapping = Just ("rect", "color") }) tempProps traceIO $ show index return $ HM.insert index Actor From aeb814a86a3fdda8698ca1eed591cd2ff424f639 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 26 Jun 2017 06:57:02 +0200 Subject: [PATCH 137/261] removed name shadowing possibilities and new angle definition --- src/Affection.hs | 6 ++-- src/Affection/Types.hs | 64 ++++++++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index ea65131..6a71530 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -114,7 +114,7 @@ withAffection AffectionConfig{..} = do , drawCPP = cpp , drawStack = [] , elapsedTime = 0 - , dt = 0 + , deltaTime = 0 }) <$> loadState (_, nState) <- runStateT ( A.runState $ do preLoop @@ -138,7 +138,7 @@ withAffection AffectionConfig{..} = do put $ ad { drawStack = [] , elapsedTime = ne - , dt = dt + , deltaTime = dt } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents @@ -214,7 +214,7 @@ getElapsedTime = getDelta :: Affection us Double getDelta = - dt <$> get + deltaTime <$> get quit :: Affection us () quit = do diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 6cb0c92..5a9bfac 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -10,12 +10,12 @@ module Affection.Types -- , AffectionDrawInner(..) , InitComponents(..) -- , Loop(..) - , RGBA(..) + -- , RGBA(..) , DrawType(..) , DrawRequest(..) , RequestPersist(..) , Angle(..) - , ConvertAngle(..) + -- , ConvertAngle(..) -- | Particle system , Particle(..) , ParticleSystem(..) @@ -92,7 +92,7 @@ data AffectionData us = AffectionData , drawStride :: Int -- ^ Stride of target buffer , drawCPP :: Int -- ^ Number of components per pixel , elapsedTime :: Double -- ^ Elapsed time in seconds - , dt :: Double -- ^ Elapsed time in seconds since last tick + , deltaTime :: Double -- ^ Elapsed time in seconds since last tick } -- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated @@ -143,12 +143,12 @@ type Affection us a = AffectionState (AffectionData us) IO a -- { runLoop :: f -> (a, f) } -- deriving (Functor, Applicative, Monad, MonadState (Loop f)) -data RGBA = RGBA - { r :: Int - , g :: Int - , b :: Int - , a :: Int - } +-- data RGBA = RGBA +-- { r :: Int +-- , g :: Int +-- , b :: Int +-- , a :: Int +-- } -- | Type for defining the draw type of draw functions data DrawType @@ -159,29 +159,31 @@ data DrawType { lineWidth :: Int -- ^ Width of line in pixels } --- | Type for defining angles -data Angle - = Rad Double -- ^ Angle in radians - | Deg Double -- ^ Angle in degrees - deriving (Show) +type Angle = Double --- | 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 +-- -- | 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 From f544b5f025ce955471ba4247ce73e6ed7a07659b Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 02:19:48 +0200 Subject: [PATCH 138/261] moving time initialization after window creation --- src/Affection.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 6a71530..15da2fa 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -64,8 +64,6 @@ withAffection AffectionConfig{..} = do renderQuality <- SDL.get SDL.HintRenderScaleQuality when (renderQuality /= SDL.ScaleLinear) $ putStrLn "Warning: Linear texture filtering not enabled!" - -- get current time - execTime <- newIORef =<< getTime Monotonic -- construct window window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window @@ -116,6 +114,8 @@ withAffection AffectionConfig{..} = do , elapsedTime = 0 , deltaTime = 0 }) <$> loadState + -- get current time + execTime <- newIORef =<< getTime Monotonic (_, nState) <- runStateT ( A.runState $ do preLoop whileM_ (do From 2499fe526a0987016fed565e1a7e29c708600c4a Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 02:20:32 +0200 Subject: [PATCH 139/261] show everything --- src/Affection/Actor.hs | 14 +++++++------- src/Affection/Animation.hs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Affection/Actor.hs b/src/Affection/Actor.hs index e7dff80..cff094f 100644 --- a/src/Affection/Actor.hs +++ b/src/Affection/Actor.hs @@ -18,19 +18,19 @@ import qualified Control.Monad.Parallel as MP import Affection.Types -data Ord a => Actor a = Actor +data (Show a, Ord a) => Actor a = Actor { actorProperties :: [ActorProperty a] , actorNodes :: M.Map a G.GeglNode , actorFlange :: G.GeglNode - } deriving (Eq) + } deriving (Show, Eq) -data Ord a => ActorProperty a = ActorProperty +data (Show a, Ord a) => ActorProperty a = ActorProperty { apName :: String , apValue :: G.PropertyValue , apMapping :: Maybe (a, String) - } deriving (Eq) + } deriving (Show, Eq) -updateProperties :: Ord a => [G.Property] -> Actor a -> Actor a +updateProperties :: (Show a, Ord a) => [G.Property] -> Actor a -> Actor a updateProperties ps act@Actor{..} = act { actorProperties = @@ -46,7 +46,7 @@ updateProperties ps act@Actor{..} = | apName p == n = p | otherwise = getProp' n ps -applyProperties :: Ord a => Actor a -> Affection us () +applyProperties :: (Show a, Ord a) => Actor a -> Affection us () applyProperties Actor{..} = MP.mapM_ (\(ActorProperty{..}) -> maybe (return ()) (\m -> @@ -55,7 +55,7 @@ applyProperties Actor{..} = ) apMapping ) actorProperties -getProperty :: Ord a => String -> Actor a -> Maybe G.PropertyValue +getProperty :: (Show a, Ord a) => String -> Actor a -> Maybe G.PropertyValue getProperty name act = case find (\a -> name == apName a) $ actorProperties act of Just p -> Just $ apValue p diff --git a/src/Affection/Animation.hs b/src/Affection/Animation.hs index 0bafb9f..87d2e8a 100644 --- a/src/Affection/Animation.hs +++ b/src/Affection/Animation.hs @@ -23,7 +23,7 @@ data SpriteAnimation = SpriteAnimation } runAnimation - :: Ord a + :: (Show a, Ord a) => Actor a -> SpriteAnimation -> Affection us (SpriteAnimation, Actor a) From 9d9833d88a489d4413640a8ffd3e30c700adce00 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 02:24:20 +0200 Subject: [PATCH 140/261] removing old remnants --- src/Affection/Types.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 5a9bfac..f39618e 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -83,11 +83,9 @@ data AffectionData us = AffectionData , userState :: us -- ^ State data provided by user , drawWindow :: SDL.Window -- ^ SDL window , windowRenderer :: SDL.Renderer -- ^ Internal renderer of window - -- , drawSurface :: SDL.Surface -- ^ SDL surface , drawTexture :: SDL.Texture -- ^ SDL Texture to draw to , drawFormat :: B.BablFormatPtr -- ^ Target format , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed - -- , drawPixels :: Ptr () -- ^ Destination Pixel buffer , drawDimensions :: (Int, Int) -- ^ Dimensions of target surface , drawStride :: Int -- ^ Stride of target buffer , drawCPP :: Int -- ^ Number of components per pixel From 61a36984aee169e483a0db7b21d3976e26f29464 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 02:40:41 +0200 Subject: [PATCH 141/261] changing time management --- src/Affection.hs | 21 ++++++++++++--------- src/Affection/Types.hs | 1 + 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 15da2fa..4003009 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -96,15 +96,15 @@ withAffection AffectionConfig{..} = do cpp = B.babl_components_per_pixel bablFormat !stride = cpp * w format <- B.babl_format bablFormat + -- get current time + execTime <- getTime Monotonic initContainer <- (\x -> AffectionData { quitEvent = False , userState = x , drawWindow = window , windowRenderer = renderer - -- , drawSurface = surface , drawTexture = texture , drawFormat = format - -- , drawPixels = pixels , drawDimensions = case canvasSize of Just (cw, ch) -> (cw, ch) Nothing -> (w, h) @@ -113,9 +113,9 @@ withAffection AffectionConfig{..} = do , drawStack = [] , elapsedTime = 0 , deltaTime = 0 + , sysTime = execTime + , pausedTime = False }) <$> loadState - -- get current time - execTime <- newIORef =<< getTime Monotonic (_, nState) <- runStateT ( A.runState $ do preLoop whileM_ (do @@ -123,11 +123,11 @@ withAffection AffectionConfig{..} = do return $ not $ A.quitEvent current ) (do - -- Measure time difference form last run - now <- liftIO $ getTime Monotonic - lastTime <- liftIO $ readIORef execTime -- get state ad <- get + -- Measure time difference form last run + now <- liftIO $ getTime Monotonic + lastTime <- sysTime ad -- clean draw requests from last run MP.mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time @@ -144,7 +144,7 @@ withAffection AffectionConfig{..} = do evs <- preHandleEvents =<< liftIO SDL.pollEvents mapM_ eventLoop evs -- execute user defined update loop - updateLoop dt + unless pausedTime (updateLoop dt) -- execute user defined draw loop drawLoop -- handle all new draw requests @@ -157,7 +157,10 @@ withAffection AffectionConfig{..} = do -- actual drawing SDL.present (windowRenderer ad2) -- save new time - liftIO $ writeIORef execTime now + ad3 <- get + put ad3 + { sysTime = now + } ) ) initContainer G.gegl_exit diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index f39618e..cb352ae 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -91,6 +91,7 @@ data AffectionData us = AffectionData , drawCPP :: Int -- ^ Number of components per pixel , elapsedTime :: Double -- ^ Elapsed time in seconds , deltaTime :: Double -- ^ Elapsed time in seconds since last tick + , pausedTime :: Bool -- ^ Should the update loop be executed? } -- | This datatype stores information about areas of a 'G.GeglBuffer' to be updated From 686b2873a295d6516c4030158bb0dae63c22fd93 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 02:47:26 +0200 Subject: [PATCH 142/261] add sysTime --- src/Affection/Types.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index cb352ae..be4effb 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -91,6 +91,7 @@ data AffectionData us = AffectionData , 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? } From 528ca220307fa2652a4f3a12a439bbd46985a570 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 02:51:18 +0200 Subject: [PATCH 143/261] fixing errors --- src/Affection.hs | 4 ++-- src/Affection/Types.hs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 4003009..fbec434 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -127,7 +127,7 @@ withAffection AffectionConfig{..} = do ad <- get -- Measure time difference form last run now <- liftIO $ getTime Monotonic - lastTime <- sysTime ad + let lastTime = sysTime ad -- clean draw requests from last run MP.mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time @@ -144,7 +144,7 @@ withAffection AffectionConfig{..} = do evs <- preHandleEvents =<< liftIO SDL.pollEvents mapM_ eventLoop evs -- execute user defined update loop - unless pausedTime (updateLoop dt) + unless (pausedTime ad) (updateLoop dt) -- execute user defined draw loop drawLoop -- handle all new draw requests diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index be4effb..9638d4d 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -41,6 +41,8 @@ import qualified BABL as B import Control.Monad.IO.Class import Control.Monad.State import qualified Control.Monad.Parallel as MP + +import System.Clock (TimeSpec) -- import Control.Monad.Reader -- import Control.Concurrent.MVar From d2a6d4119b360c742981d36e9b54d2b70ad348d0 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 16:31:48 +0200 Subject: [PATCH 144/261] only update sysTime, when it had not changed --- src/Affection.hs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index fbec434..d4c2c5d 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -158,9 +158,11 @@ withAffection AffectionConfig{..} = do SDL.present (windowRenderer ad2) -- save new time ad3 <- get - put ad3 - { sysTime = now - } + when (sysTime ad == sysTime ad3) ( + put ad3 + { sysTime = now + } + ) ) ) initContainer G.gegl_exit From eeb342eeba16e01c40fabc84dc263df5918ab9fc Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 21:45:40 +0200 Subject: [PATCH 145/261] relocating --- affection.cabal | 1 + examples/example05.hs | 1 + src/Affection.hs | 62 +-------------------------------------- src/Affection/Misc.hs | 66 ++++++++++++++++++++++++++++++++++++++++++ src/Affection/Types.hs | 3 ++ 5 files changed, 72 insertions(+), 61 deletions(-) create mode 100644 src/Affection/Misc.hs diff --git a/affection.cabal b/affection.cabal index 866123f..ccef510 100644 --- a/affection.cabal +++ b/affection.cabal @@ -44,6 +44,7 @@ library , Affection.Property , Affection.Actor , Affection.Animation + , Affection.Misc default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/examples/example05.hs b/examples/example05.hs index e95f660..9985d73 100644 --- a/examples/example05.hs +++ b/examples/example05.hs @@ -38,6 +38,7 @@ main = do (CInt $ fromIntegral resolution) } , canvasSize = Nothing + , initScreenMode = SDL.Windowed , preLoop = loadList , eventLoop = handle , updateLoop = Main.update diff --git a/src/Affection.hs b/src/Affection.hs index d4c2c5d..1766e63 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -7,11 +7,6 @@ module Affection -- , withWindow -- , withDefaultWindow , delaySec - , get - , put - , getElapsedTime - , getDelta - , quit , module A ) where @@ -42,6 +37,7 @@ import Affection.MouseInteractable as A import Affection.Property as A import Affection.Actor as A import Affection.Animation as A +import Affection.Misc as A import qualified BABL as B @@ -169,59 +165,3 @@ withAffection AffectionConfig{..} = do cleanUp $ userState nState SDL.destroyWindow window SDL.quit - -getSurfaces :: SDL.Window -> IO (SDL.Surface, SDL.Surface) -getSurfaces window = do - oldSurf@(SDL.Surface ptr _) <- SDL.getWindowSurface window - rawSurfacePtr <- Raw.convertSurfaceFormat ptr (SDL.toNumber SDL.ABGR8888) 0 - let surface = SDL.Surface rawSurfacePtr Nothing - return (oldSurf, surface) - --- Prehandle SDL events in case any window events occur -preHandleEvents :: [SDL.Event] -> Affection us [SDL.EventPayload] -preHandleEvents evs = - -- mapM handle evs - -- where - -- handle e = - -- case SDL.eventPayload e of - -- SDL.WindowMovedEvent _ -> do - -- liftIO $ traceIO "I was moved" - -- return e - -- _ -> - -- return e - return $ map SDL.eventPayload evs - --- | Return the userstate to the user -getAffection :: Affection us us -getAffection = do - ad <- get - return $ userState ad - --- | 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 - -> IO () -delaySec dur = SDL.delay (fromIntegral $ dur * 1000) - --- | Get time since start but always the same in the current tick. -getElapsedTime :: Affection us Double -getElapsedTime = - elapsedTime <$> get - -getDelta :: Affection us Double -getDelta = - deltaTime <$> get - -quit :: Affection us () -quit = do - ad <- get - put $ ad { quitEvent = True } diff --git a/src/Affection/Misc.hs b/src/Affection/Misc.hs new file mode 100644 index 0000000..8a13398 --- /dev/null +++ b/src/Affection/Misc.hs @@ -0,0 +1,66 @@ +module Affection.Misc +where + +import Affection.Types + +import qualified SDL + +import System.Clock + +import Control.Monad.State + +-- Prehandle SDL events in case any window events occur +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 + +-- | 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 + -> IO () +delaySec dur = SDL.delay (fromIntegral $ dur * 1000) + +-- | Get time since start but always the same in the current tick. +getElapsedTime :: Affection us Double +getElapsedTime = + elapsedTime <$> get + +getDelta :: Affection us Double +getDelta = + deltaTime <$> get + +quit :: Affection us () +quit = do + ad <- get + put $ ad { quitEvent = True } + +toggleScreen :: Affection us () +toggleScreen = do + ad <- get + newMode <- case screenMode ad of + SDL.Windowed -> do + SDL.setWindowMode (drawWindow ad) SDL.Fullscreen + return SDL.Fullscreen + SDL.Fullscreen -> do + SDL.setWindowMode (drawWindow ad) SDL.Windowed + return SDL.Windowed + now <- liftIO $ getTime Monotonic + put ad + { sysTime = now + , screenMode = newMode + } diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 9638d4d..c94d2c5 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -59,6 +59,8 @@ data AffectionConfig us = AffectionConfig -- ^ Window configuration , canvasSize :: Maybe (Int, Int) -- ^ size of the texture canvas + , initScreenMode :: SDL.WindowMode + -- ^ Window mode to start in , preLoop :: Affection us () -- ^ Actions to be performed, before loop starts , eventLoop :: SDL.EventPayload -> Affection us () @@ -87,6 +89,7 @@ data AffectionData us = AffectionData , 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 From 6f943ece7b94f49811e88f546becd1ef8ac46c00 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 29 Jul 2017 21:57:06 +0200 Subject: [PATCH 146/261] set screen mode --- src/Affection.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Affection.hs b/src/Affection.hs index 1766e63..42c5207 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -93,6 +93,7 @@ withAffection AffectionConfig{..} = do !stride = cpp * w format <- B.babl_format bablFormat -- get current time + SDL.setWindowMode window initScreenMode execTime <- getTime Monotonic initContainer <- (\x -> AffectionData { quitEvent = False @@ -104,6 +105,7 @@ withAffection AffectionConfig{..} = do , drawDimensions = case canvasSize of Just (cw, ch) -> (cw, ch) Nothing -> (w, h) + , screenMode = initScreenMode , drawStride = stride , drawCPP = cpp , drawStack = [] From 821550b7cdb5eedf8997703d51c951fcdf470179 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 7 Sep 2017 06:23:01 +0200 Subject: [PATCH 147/261] misc is util --- affection.cabal | 2 +- src/Affection.hs | 2 +- src/Affection/{Misc.hs => Util.hs} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/Affection/{Misc.hs => Util.hs} (98%) diff --git a/affection.cabal b/affection.cabal index ccef510..7d65f3b 100644 --- a/affection.cabal +++ b/affection.cabal @@ -44,7 +44,7 @@ library , Affection.Property , Affection.Actor , Affection.Animation - , Affection.Misc + , Affection.Util default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/src/Affection.hs b/src/Affection.hs index 42c5207..e3f9d4a 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -37,7 +37,7 @@ import Affection.MouseInteractable as A import Affection.Property as A import Affection.Actor as A import Affection.Animation as A -import Affection.Misc as A +import Affection.Util as A import qualified BABL as B diff --git a/src/Affection/Misc.hs b/src/Affection/Util.hs similarity index 98% rename from src/Affection/Misc.hs rename to src/Affection/Util.hs index 8a13398..9cf794a 100644 --- a/src/Affection/Misc.hs +++ b/src/Affection/Util.hs @@ -1,4 +1,4 @@ -module Affection.Misc +module Affection.Util where import Affection.Types From 46de809202e13cf69a2624d9a1b4124aa1f74c57 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 9 Sep 2017 16:47:24 +0200 Subject: [PATCH 148/261] switching to 3D --- affection.cabal | 1 + src/Affection.hs | 90 +++++++++++++++++++++++------------------- src/Affection/Types.hs | 1 + 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/affection.cabal b/affection.cabal index 7d65f3b..2ef28de 100644 --- a/affection.cabal +++ b/affection.cabal @@ -72,6 +72,7 @@ library , clock , glib , bytestring + , OpenGL -- , sdl2-image -- executable example00 diff --git a/src/Affection.hs b/src/Affection.hs index e3f9d4a..1f96651 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -2,6 +2,7 @@ {-# LANGUAGE BangPatterns #-} module Affection ( withAffection + , get , getAffection , putAffection -- , withWindow @@ -13,7 +14,7 @@ module Affection import qualified SDL import qualified SDL.Internal.Numbered as SDL (toNumber) import qualified SDL.Raw as Raw -import qualified GEGL as G +-- import qualified GEGL as G import Data.Maybe import Data.IORef @@ -39,6 +40,8 @@ import Affection.Actor as A import Affection.Animation as A import Affection.Util as A +import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) + import qualified BABL as B -- | Main function which bootstraps everything else. @@ -52,7 +55,7 @@ withAffection AffectionConfig{..} = do SDL.initializeAll Only is -> SDL.initialize is - G.gegl_init + -- G.gegl_init -- give SDL render quality SDL.HintRenderScaleQuality SDL.$= SDL.ScaleLinear -- just checking… @@ -63,35 +66,36 @@ withAffection AffectionConfig{..} = do -- construct window window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window - -- create renderer - renderer <- SDL.createRenderer - window - (-1) - SDL.defaultRenderer - -- make draw texture - texture <- SDL.createTexture - renderer - SDL.ABGR8888 - SDL.TextureAccessStreaming - (case canvasSize of - Just (cw, ch) -> (SDL.V2 - (CInt $ fromIntegral cw) - (CInt $ fromIntegral ch) - ) - Nothing -> - SDL.windowInitialSize windowConfig - ) - -- make draw surface - -- pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr + context <- SDL.glCreateContext(window) + -- -- create renderer + -- renderer <- SDL.createRenderer + -- window + -- (-1) + -- SDL.defaultRenderer + -- -- make draw texture + -- texture <- SDL.createTexture + -- renderer + -- SDL.ABGR8888 + -- SDL.TextureAccessStreaming + -- (case canvasSize of + -- Just (cw, ch) -> (SDL.V2 + -- (CInt $ fromIntegral cw) + -- (CInt $ fromIntegral ch) + -- ) + -- Nothing -> + -- SDL.windowInitialSize windowConfig + -- ) + -- -- make draw surface + -- -- pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr let SDL.V2 (CInt rw) (CInt rh) = windowInitialSize windowConfig (w, h) = case canvasSize of Just (cw, ch) -> (cw, ch) Nothing -> (fromIntegral rw, fromIntegral rh) - -- stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w - bablFormat = B.PixelFormat B.RGBA B.CFu8 - cpp = B.babl_components_per_pixel bablFormat - !stride = cpp * w - format <- B.babl_format bablFormat + -- -- stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w + -- bablFormat = B.PixelFormat B.RGBA B.CFu8 + -- cpp = B.babl_components_per_pixel bablFormat + -- !stride = cpp * w + -- format <- B.babl_format bablFormat -- get current time SDL.setWindowMode window initScreenMode execTime <- getTime Monotonic @@ -99,15 +103,16 @@ withAffection AffectionConfig{..} = do { quitEvent = False , userState = x , drawWindow = window - , windowRenderer = renderer - , drawTexture = texture - , drawFormat = format + , glContext = context + -- , windowRenderer = renderer + -- , drawTexture = texture + -- , drawFormat = format , drawDimensions = case canvasSize of Just (cw, ch) -> (cw, ch) Nothing -> (w, h) , screenMode = initScreenMode - , drawStride = stride - , drawCPP = cpp + -- , drawStride = stride + -- , drawCPP = cpp , drawStack = [] , elapsedTime = 0 , deltaTime = 0 @@ -126,8 +131,8 @@ withAffection AffectionConfig{..} = do -- Measure time difference form last run now <- liftIO $ getTime Monotonic let lastTime = sysTime ad - -- clean draw requests from last run - MP.mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) + -- -- clean draw requests from last run + -- MP.mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time -- SDL.clear renderer -- compute dt and update elapsedTime @@ -144,16 +149,19 @@ withAffection AffectionConfig{..} = do -- execute user defined update loop unless (pausedTime ad) (updateLoop dt) -- execute user defined draw loop + liftIO $ GL.clear [ColorBuffer, DepthBuffer] drawLoop + liftIO $ flush -- handle all new draw requests ad2 <- get - clear <- catMaybes <$> - MP.mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) - -- save all draw requests to clear in next run - put $ ad2 - { drawStack = clear } + -- clear <- catMaybes <$> + -- MP.mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) + -- -- save all draw requests to clear in next run + -- put $ ad2 + -- { drawStack = clear } -- actual drawing - SDL.present (windowRenderer ad2) + SDL.glSwapWindow window + -- SDL.present (windowRenderer ad2) -- save new time ad3 <- get when (sysTime ad == sysTime ad3) ( @@ -163,7 +171,7 @@ withAffection AffectionConfig{..} = do ) ) ) initContainer - G.gegl_exit + -- G.gegl_exit cleanUp $ userState nState SDL.destroyWindow window SDL.quit diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index c94d2c5..800ad5f 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -86,6 +86,7 @@ data AffectionData us = AffectionData { 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 From 08c28fe6863d0ec6db75321e1ab2943a5ffdb114 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 3 Oct 2017 12:47:18 +0200 Subject: [PATCH 149/261] unBABL and unGEGL --- affection.cabal | 16 ++-- src/Affection.hs | 22 +++--- src/Affection/Draw.hs | 5 +- src/Affection/Types.hs | 163 +++++++++++++++++++++-------------------- 4 files changed, 104 insertions(+), 102 deletions(-) diff --git a/affection.cabal b/affection.cabal index 2ef28de..b7ca0f8 100644 --- a/affection.cabal +++ b/affection.cabal @@ -36,14 +36,14 @@ flag examples library exposed-modules: Affection - , Affection.Draw - , Affection.Particle + -- , Affection.Draw + -- , Affection.Particle , Affection.Types , Affection.StateMachine , Affection.MouseInteractable - , Affection.Property - , Affection.Actor - , Affection.Animation + -- , Affection.Property + -- , Affection.Actor + -- , Affection.Animation , Affection.Util default-extensions: OverloadedStrings @@ -60,12 +60,12 @@ library default-language: Haskell2010 ghc-options: -Wall -- Other library packages from which modules are imported. - build-depends: base >=4.9 && <4.10 + build-depends: base >=4.9 , sdl2 , text , mtl - , gegl - , babl + -- , gegl + -- , babl , monad-loops , monad-parallel , containers diff --git a/src/Affection.hs b/src/Affection.hs index 1f96651..b760c22 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -11,6 +11,7 @@ module Affection , module A ) where +import SDL (($=)) import qualified SDL import qualified SDL.Internal.Numbered as SDL (toNumber) import qualified SDL.Raw as Raw @@ -31,18 +32,18 @@ import Foreign.Storable (peek) import Debug.Trace import Affection.Types as A -import Affection.Draw as A -import Affection.Particle as A +-- import Affection.Draw as A +-- import Affection.Particle as A import Affection.StateMachine as A import Affection.MouseInteractable as A -import Affection.Property as A -import Affection.Actor as A -import Affection.Animation as A +-- import Affection.Property as A +-- import Affection.Actor as A +-- import Affection.Animation as A import Affection.Util as A import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) -import qualified BABL as B +-- import qualified BABL as B -- | Main function which bootstraps everything else. withAffection @@ -87,7 +88,7 @@ withAffection AffectionConfig{..} = do -- ) -- -- make draw surface -- -- pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr - let SDL.V2 (CInt rw) (CInt rh) = windowInitialSize windowConfig + 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) @@ -98,6 +99,7 @@ withAffection AffectionConfig{..} = do -- format <- B.babl_format bablFormat -- get current time SDL.setWindowMode window initScreenMode + SDL.swapInterval $= SDL.SynchronizedUpdates execTime <- getTime Monotonic initContainer <- (\x -> AffectionData { quitEvent = False @@ -113,7 +115,7 @@ withAffection AffectionConfig{..} = do , screenMode = initScreenMode -- , drawStride = stride -- , drawCPP = cpp - , drawStack = [] + -- , drawStack = [] , elapsedTime = 0 , deltaTime = 0 , sysTime = execTime @@ -139,8 +141,8 @@ withAffection AffectionConfig{..} = do let !dt = fromIntegral (toNanoSecs $ diffTimeSpec lastTime now) / (10 ^ 9) !ne = elapsedTime ad + dt put $ ad - { drawStack = [] - , elapsedTime = ne + -- { drawStack = [] + { elapsedTime = ne , deltaTime = dt } -- poll events diff --git a/src/Affection/Draw.hs b/src/Affection/Draw.hs index 21c4cd8..e533f7e 100644 --- a/src/Affection/Draw.hs +++ b/src/Affection/Draw.hs @@ -28,9 +28,8 @@ import System.Glib.GObject import qualified SDL -import qualified BABL as B - -import qualified GEGL as G +-- import qualified BABL as B +-- import qualified GEGL as G import Debug.Trace diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 800ad5f..df107dc 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -1,33 +1,34 @@ {-# 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 + -- ( 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 import qualified SDL.Init as SDL import qualified SDL.Video as SDL @@ -35,8 +36,8 @@ 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 qualified GEGL as G +-- import qualified BABL as B import Control.Monad.IO.Class import Control.Monad.State @@ -89,9 +90,9 @@ data AffectionData us = AffectionData , 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 + -- , drawFormat :: B.BablFormatPtr -- ^ Target format , screenMode :: SDL.WindowMode -- ^ current screen mode - , drawStack :: [DrawRequest] -- ^ Stack of 'DrawRequest's to be processed + -- , 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 @@ -101,19 +102,19 @@ data AffectionData us = AffectionData , pausedTime :: Bool -- ^ Should the update loop be executed? } --- | 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 +-- -- | 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 @@ -191,39 +192,39 @@ type Angle = Double -- (==) 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 - } +-- -- | 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 +-- } From 4594dc58e022541da192b64fad52369275621484 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 4 Nov 2017 12:57:56 +0100 Subject: [PATCH 150/261] pushing upper bound --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index 7d65f3b..251ce66 100644 --- a/affection.cabal +++ b/affection.cabal @@ -60,7 +60,7 @@ library default-language: Haskell2010 ghc-options: -Wall -- Other library packages from which modules are imported. - build-depends: base >=4.9 && <4.10 + build-depends: base >=4.9 && <4.11 , sdl2 , text , mtl From 2a9f7ffebc90b41cc3d47f34500eadd5c3871c8d Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 4 Nov 2017 18:35:09 +0100 Subject: [PATCH 151/261] export get and put --- src/Affection.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Affection.hs b/src/Affection.hs index e3f9d4a..9c1ad4c 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -7,6 +7,8 @@ module Affection -- , withWindow -- , withDefaultWindow , delaySec + , get + , put , module A ) where From 2c731bea9789418a8bf9a2e49780ae17b3c9ba76 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 4 Nov 2017 20:37:49 +0100 Subject: [PATCH 152/261] de-parallelize --- src/Affection.hs | 5 ++--- src/Affection/Particle.hs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 9c1ad4c..6d49ada 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -23,7 +23,6 @@ import Data.IORef import System.Clock import Control.Monad.Loops -import qualified Control.Monad.Parallel as MP import Control.Monad.State import Foreign.C.Types (CInt(..)) @@ -129,7 +128,7 @@ withAffection AffectionConfig{..} = do now <- liftIO $ getTime Monotonic let lastTime = sysTime ad -- clean draw requests from last run - MP.mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) + mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time -- SDL.clear renderer -- compute dt and update elapsedTime @@ -150,7 +149,7 @@ withAffection AffectionConfig{..} = do -- handle all new draw requests ad2 <- get clear <- catMaybes <$> - MP.mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) + mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) -- save all draw requests to clear in next run put $ ad2 { drawStack = clear } diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 0518cd3..637709f 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -13,7 +13,6 @@ import Affection.Types import Control.Monad import Control.Monad.State (get) -import qualified Control.Monad.Parallel as MP import Data.Maybe @@ -34,7 +33,7 @@ updateParticle -> Affection us [Particle] -- ^ processed 'Particle's updateParticle time func l = - catMaybes <$> MP.mapM (\p -> do + catMaybes <$> mapM (\p -> do now <- elapsedTime <$> get if particleCreation p + particleTimeToLive p < now then do @@ -124,7 +123,7 @@ drawParticleSystem sys draw = if not (null parts) then do liftIO $ G.gegl_node_link (particleStackCont $ head parts) (partSysNode sys) - MP.mapM_ (draw (partSysBuffer sys) (partSysNode sys)) parts + mapM_ (draw (partSysBuffer sys) (partSysNode sys)) parts else do _ <- liftIO $ G.gegl_node_disconnect (partSysNode sys) "input" return () From f7dac3e0b606957cec156e1738e29859670b4b59 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 15 Nov 2017 18:28:38 +0100 Subject: [PATCH 153/261] deparallelize --- src/Affection/Particle.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Affection/Particle.hs b/src/Affection/Particle.hs index 0518cd3..637709f 100644 --- a/src/Affection/Particle.hs +++ b/src/Affection/Particle.hs @@ -13,7 +13,6 @@ import Affection.Types import Control.Monad import Control.Monad.State (get) -import qualified Control.Monad.Parallel as MP import Data.Maybe @@ -34,7 +33,7 @@ updateParticle -> Affection us [Particle] -- ^ processed 'Particle's updateParticle time func l = - catMaybes <$> MP.mapM (\p -> do + catMaybes <$> mapM (\p -> do now <- elapsedTime <$> get if particleCreation p + particleTimeToLive p < now then do @@ -124,7 +123,7 @@ drawParticleSystem sys draw = if not (null parts) then do liftIO $ G.gegl_node_link (particleStackCont $ head parts) (partSysNode sys) - MP.mapM_ (draw (partSysBuffer sys) (partSysNode sys)) parts + mapM_ (draw (partSysBuffer sys) (partSysNode sys)) parts else do _ <- liftIO $ G.gegl_node_disconnect (partSysNode sys) "input" return () From 0a0b85139549ef18f48dd3447a63d1c3f99e9d6c Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 22 Nov 2017 22:32:50 +0100 Subject: [PATCH 154/261] add notes --- notes/TODO.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 notes/TODO.md diff --git a/notes/TODO.md b/notes/TODO.md new file mode 100644 index 0000000..db88493 --- /dev/null +++ b/notes/TODO.md @@ -0,0 +1,9 @@ +# TODO + +Implement following things in approximately this Order: + +* Message bus +* Console +* GUI +* Framework +* Everything else From b2da2d287a374eacc07ad75da14a92ae5d03b502 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 23 Nov 2017 19:37:36 +0100 Subject: [PATCH 155/261] post merge cleanup --- src/Affection.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 636750e..60191d3 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -135,7 +135,7 @@ withAffection AffectionConfig{..} = do now <- liftIO $ getTime Monotonic let lastTime = sysTime ad -- clean draw requests from last run - mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) + -- mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) -- clean the renderer form last time -- SDL.clear renderer -- compute dt and update elapsedTime @@ -157,11 +157,11 @@ withAffection AffectionConfig{..} = do liftIO $ flush -- handle all new draw requests ad2 <- get - clear <- catMaybes <$> - mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) - -- save all draw requests to clear in next run - put $ ad2 - { drawStack = clear } + -- clear <- catMaybes <$> + -- mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) + -- -- save all draw requests to clear in next run + -- put $ ad2 + -- { drawStack = clear } -- actual drawing SDL.glSwapWindow window -- SDL.present (windowRenderer ad2) From c6066e3e08b92042ab4395320af39126e196669f Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 25 Nov 2017 09:05:04 +0100 Subject: [PATCH 156/261] starting messagebus --- src/Affection/MessageBus.hs | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/Affection/MessageBus.hs diff --git a/src/Affection/MessageBus.hs b/src/Affection/MessageBus.hs new file mode 100644 index 0000000..1966643 --- /dev/null +++ b/src/Affection/MessageBus.hs @@ -0,0 +1,4 @@ +module Affection.MessageBus where + +class MessageBus a where + From 8c2f85be94ed769412eee39445ac2b734e5c6eb5 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 26 Nov 2017 13:59:59 +0100 Subject: [PATCH 157/261] preparing some classes --- affection.cabal | 2 ++ src/Affection.hs | 1 + src/Affection/MessageBus.hs | 22 ++++++++++++++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/affection.cabal b/affection.cabal index 698a187..84e6563 100644 --- a/affection.cabal +++ b/affection.cabal @@ -45,6 +45,7 @@ library -- , Affection.Actor -- , Affection.Animation , Affection.Util + , Affection.MessageBus default-extensions: OverloadedStrings -- Modules included in this library but not exported. @@ -73,6 +74,7 @@ library , glib , bytestring , OpenGL + , stm -- , sdl2-image -- executable example00 diff --git a/src/Affection.hs b/src/Affection.hs index 60191d3..5c22a32 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -41,6 +41,7 @@ import Affection.MouseInteractable as A -- import Affection.Actor as A -- import Affection.Animation as A import Affection.Util as A +import Affection.MessageBus as A import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) diff --git a/src/Affection/MessageBus.hs b/src/Affection/MessageBus.hs index 1966643..2fcbaf8 100644 --- a/src/Affection/MessageBus.hs +++ b/src/Affection/MessageBus.hs @@ -1,4 +1,22 @@ module Affection.MessageBus where -class MessageBus a where - +import Control.Concurrent.STM as STM + +import Data.IORef + +class Message msg where + msgPayload :: msg -> Maybe a + +type Channel msg = TChan msg + +class Participant prt where + partChannel :: (Message msg) => prt -> IORef (Channel msg) + + partConnectChannel :: (Message msg) => prt -> (Channel msg) -> IO () + + partListen :: (Message msg) => prt -> IO msg + + partBroadcast :: (Message msg) => prt -> msg -> IO () + +newBroadcastChannel :: IO (Channel msg) +newBroadcastChannel = atomically $ newBroadcastTChan From 5defc8e74a3577c6235f3cc76f89ea32dfbd8d87 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 26 Nov 2017 20:25:06 +0100 Subject: [PATCH 158/261] reorder things --- affection.cabal | 5 ++++- src/Affection.hs | 5 ++++- src/Affection/MessageBus.hs | 22 ---------------------- 3 files changed, 8 insertions(+), 24 deletions(-) delete mode 100644 src/Affection/MessageBus.hs diff --git a/affection.cabal b/affection.cabal index 51cb0f7..5077048 100644 --- a/affection.cabal +++ b/affection.cabal @@ -45,7 +45,10 @@ library -- , Affection.Actor -- , Affection.Animation , Affection.Util - , Affection.MessageBus + , Affection.MessageBus.Util + , Affection.MessageBus.Class + , Affection.MessageBus.Engine + , Affection.MessageBus.Message default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/src/Affection.hs b/src/Affection.hs index d0d5afa..3dbb127 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -41,7 +41,10 @@ import Affection.MouseInteractable as A -- import Affection.Actor as A -- import Affection.Animation as A import Affection.Util as A -import Affection.MessageBus as A +import Affection.MessageBus.Class as A +import Affection.MessageBus.Message as A +import Affection.MessageBus.Engine as A +import Affection.MessageBus.Util as A import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) diff --git a/src/Affection/MessageBus.hs b/src/Affection/MessageBus.hs deleted file mode 100644 index 2fcbaf8..0000000 --- a/src/Affection/MessageBus.hs +++ /dev/null @@ -1,22 +0,0 @@ -module Affection.MessageBus where - -import Control.Concurrent.STM as STM - -import Data.IORef - -class Message msg where - msgPayload :: msg -> Maybe a - -type Channel msg = TChan msg - -class Participant prt where - partChannel :: (Message msg) => prt -> IORef (Channel msg) - - partConnectChannel :: (Message msg) => prt -> (Channel msg) -> IO () - - partListen :: (Message msg) => prt -> IO msg - - partBroadcast :: (Message msg) => prt -> msg -> IO () - -newBroadcastChannel :: IO (Channel msg) -newBroadcastChannel = atomically $ newBroadcastTChan From da2ba52aaeb777a23190714d744db141848ff7eb Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 26 Nov 2017 22:26:41 +0100 Subject: [PATCH 159/261] adding missing files --- src/Affection/MessageBus/Class.hs | 18 ++++++++++++++++++ src/Affection/MessageBus/Engine.hs | 3 +++ src/Affection/MessageBus/Message.hs | 4 ++++ src/Affection/MessageBus/Util.hs | 8 ++++++++ 4 files changed, 33 insertions(+) create mode 100644 src/Affection/MessageBus/Class.hs create mode 100644 src/Affection/MessageBus/Engine.hs create mode 100644 src/Affection/MessageBus/Message.hs create mode 100644 src/Affection/MessageBus/Util.hs diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs new file mode 100644 index 0000000..fb07942 --- /dev/null +++ b/src/Affection/MessageBus/Class.hs @@ -0,0 +1,18 @@ +module Affection.MessageBus.Class where + +import Control.Concurrent.STM as STM + +import Data.IORef + +import Affection.MessageBus.Message + +type Channel msg = TChan msg + +class Participant prt where + partChannel :: (Message msg) => prt -> IORef (Channel msg) + + partConnectChannel :: (Message msg) => prt -> Channel msg -> IO () + + partListen :: (Message msg) => prt -> IO msg + + partBroadcast :: (Message msg) => prt -> msg -> IO () diff --git a/src/Affection/MessageBus/Engine.hs b/src/Affection/MessageBus/Engine.hs new file mode 100644 index 0000000..9990a94 --- /dev/null +++ b/src/Affection/MessageBus/Engine.hs @@ -0,0 +1,3 @@ +module Affection.MessageBus.Engine where + +import Affection.MessageBus.Class diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs new file mode 100644 index 0000000..5765aec --- /dev/null +++ b/src/Affection/MessageBus/Message.hs @@ -0,0 +1,4 @@ +module Affection.MessageBus.Message where + +class Message msg where + msgPayload :: msg -> a diff --git a/src/Affection/MessageBus/Util.hs b/src/Affection/MessageBus/Util.hs new file mode 100644 index 0000000..759c582 --- /dev/null +++ b/src/Affection/MessageBus/Util.hs @@ -0,0 +1,8 @@ +module Affection.MessageBus.Util where + +import Affection.MessageBus.Class +import Affection.MessageBus.Message +import Control.Concurrent.STM as STM + +newBroadcastChannel :: (Message msg) => IO (Channel msg) +newBroadcastChannel = atomically $ newBroadcastTChan From 1bb1af935b7c5b7a2aebb0934a061a9cbc3ca7fc Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 27 Nov 2017 01:43:43 +0100 Subject: [PATCH 160/261] defining mesage bus further --- affection.cabal | 1 + src/Affection/MessageBus/Class.hs | 13 +++++++------ src/Affection/MessageBus/Message.hs | 16 +++++++++++++++- src/Affection/MessageBus/Util.hs | 2 +- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/affection.cabal b/affection.cabal index 5077048..cf45d46 100644 --- a/affection.cabal +++ b/affection.cabal @@ -68,6 +68,7 @@ library , sdl2 , text , mtl + , time -- , gegl -- , babl , monad-loops diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index fb07942..c02687e 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE MultiParamTypeClasses #-} module Affection.MessageBus.Class where import Control.Concurrent.STM as STM @@ -6,13 +7,13 @@ import Data.IORef import Affection.MessageBus.Message -type Channel msg = TChan msg +newtype (Message msg) => Channel msg = Channel (TChan msg) -class Participant prt where - partChannel :: (Message msg) => prt -> IORef (Channel msg) +class (Message msg) => Participant prt msg where + partChannel :: prt -> IORef (Channel msg) - partConnectChannel :: (Message msg) => prt -> Channel msg -> IO () + partConnectChannel :: prt -> Channel msg -> IO () - partListen :: (Message msg) => prt -> IO msg + partListen :: prt -> IO msg - partBroadcast :: (Message msg) => prt -> msg -> IO () + partBroadcast :: prt -> msg -> IO () diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index 5765aec..8fe92fe 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -1,4 +1,18 @@ +{-# LANGUAGE RankNTypes #-} module Affection.MessageBus.Message where +import Data.Time.Clock (UTCTime(..)) + class Message msg where - msgPayload :: msg -> a + msgTime :: msg -> UTCTime + +data EngineMessage m + = MsgUserMessage + { msgPayload :: m + , msgWhen :: UTCTime + } -- ^ Generic user defined message with custom payload + | MsgEngineReady UTCTime + +instance Message (EngineMessage m) where + msgTime (MsgUserMessage _ t) = t + msgTime (MsgEngineReady t) = t diff --git a/src/Affection/MessageBus/Util.hs b/src/Affection/MessageBus/Util.hs index 759c582..ed7c551 100644 --- a/src/Affection/MessageBus/Util.hs +++ b/src/Affection/MessageBus/Util.hs @@ -5,4 +5,4 @@ import Affection.MessageBus.Message import Control.Concurrent.STM as STM newBroadcastChannel :: (Message msg) => IO (Channel msg) -newBroadcastChannel = atomically $ newBroadcastTChan +newBroadcastChannel = atomically $ Channel <$> newBroadcastTChan From 6e11ff9b1ea0d159beb82b8faa3b27ee77e9b5b4 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 27 Nov 2017 02:18:23 +0100 Subject: [PATCH 161/261] dusting off --- affection.cabal | 9 ----- src/Affection.hs | 60 +---------------------------- src/Affection/MessageBus.hs | 7 ++++ src/Affection/MessageBus/Class.hs | 2 +- src/Affection/MessageBus/Engine.hs | 3 -- src/Affection/MessageBus/Message.hs | 4 +- 6 files changed, 12 insertions(+), 73 deletions(-) create mode 100644 src/Affection/MessageBus.hs delete mode 100644 src/Affection/MessageBus/Engine.hs diff --git a/affection.cabal b/affection.cabal index cf45d46..5f4a95e 100644 --- a/affection.cabal +++ b/affection.cabal @@ -36,18 +36,12 @@ flag examples library exposed-modules: Affection - -- , Affection.Draw - -- , Affection.Particle , Affection.Types , Affection.StateMachine , Affection.MouseInteractable - -- , Affection.Property - -- , Affection.Actor - -- , Affection.Animation , Affection.Util , Affection.MessageBus.Util , Affection.MessageBus.Class - , Affection.MessageBus.Engine , Affection.MessageBus.Message default-extensions: OverloadedStrings @@ -69,8 +63,6 @@ library , text , mtl , time - -- , gegl - -- , babl , monad-loops , monad-parallel , containers @@ -79,7 +71,6 @@ library , bytestring , OpenGL , stm - -- , sdl2-image -- executable example00 -- hs-source-dirs: examples diff --git a/src/Affection.hs b/src/Affection.hs index 3dbb127..fd564ad 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -5,8 +5,6 @@ module Affection , get , getAffection , putAffection - -- , withWindow - -- , withDefaultWindow , delaySec , get , put @@ -17,7 +15,6 @@ import SDL (($=)) import qualified SDL import qualified SDL.Internal.Numbered as SDL (toNumber) import qualified SDL.Raw as Raw --- import qualified GEGL as G import Data.Maybe import Data.IORef @@ -33,22 +30,13 @@ import Foreign.Storable (peek) import Debug.Trace import Affection.Types as A --- import Affection.Draw as A --- import Affection.Particle as A import Affection.StateMachine as A import Affection.MouseInteractable as A --- import Affection.Property as A --- import Affection.Actor as A --- import Affection.Animation as A import Affection.Util as A -import Affection.MessageBus.Class as A -import Affection.MessageBus.Message as A -import Affection.MessageBus.Engine as A -import Affection.MessageBus.Util as A +import Affection.MessageBus as A import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) --- import qualified BABL as B -- | Main function which bootstraps everything else. withAffection @@ -61,7 +49,6 @@ withAffection AffectionConfig{..} = do SDL.initializeAll Only is -> SDL.initialize is - -- G.gegl_init -- give SDL render quality SDL.HintRenderScaleQuality SDL.$= SDL.ScaleLinear -- just checking… @@ -73,54 +60,23 @@ withAffection AffectionConfig{..} = do window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window context <- SDL.glCreateContext(window) - -- -- create renderer - -- renderer <- SDL.createRenderer - -- window - -- (-1) - -- SDL.defaultRenderer - -- -- make draw texture - -- texture <- SDL.createTexture - -- renderer - -- SDL.ABGR8888 - -- SDL.TextureAccessStreaming - -- (case canvasSize of - -- Just (cw, ch) -> (SDL.V2 - -- (CInt $ fromIntegral cw) - -- (CInt $ fromIntegral ch) - -- ) - -- Nothing -> - -- SDL.windowInitialSize windowConfig - -- ) - -- -- make draw surface - -- -- pixelFormat <- liftIO $ peek . Raw.surfaceFormat =<< peek ptr 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) - -- -- stride = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w - -- bablFormat = B.PixelFormat B.RGBA B.CFu8 - -- cpp = B.babl_components_per_pixel bablFormat - -- !stride = cpp * w - -- format <- B.babl_format bablFormat - -- get current time SDL.setWindowMode window initScreenMode SDL.swapInterval $= SDL.SynchronizedUpdates + -- get current time execTime <- getTime Monotonic initContainer <- (\x -> AffectionData { quitEvent = False , userState = x , drawWindow = window , glContext = context - -- , windowRenderer = renderer - -- , drawTexture = texture - -- , drawFormat = format , drawDimensions = case canvasSize of Just (cw, ch) -> (cw, ch) Nothing -> (w, h) , screenMode = initScreenMode - -- , drawStride = stride - -- , drawCPP = cpp - -- , drawStack = [] , elapsedTime = 0 , deltaTime = 0 , sysTime = execTime @@ -138,15 +94,10 @@ withAffection AffectionConfig{..} = do -- Measure time difference form last run now <- liftIO $ getTime Monotonic let lastTime = sysTime ad - -- -- clean draw requests from last run - -- MP.mapM_ (invalidateDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad) - -- clean the renderer form last time - -- SDL.clear renderer -- compute dt and update elapsedTime let !dt = fromIntegral (toNanoSecs $ diffTimeSpec lastTime now) / (10 ^ 9) !ne = elapsedTime ad + dt put $ ad - -- { drawStack = [] { elapsedTime = ne , deltaTime = dt } @@ -161,14 +112,8 @@ withAffection AffectionConfig{..} = do liftIO $ flush -- handle all new draw requests ad2 <- get - -- clear <- catMaybes <$> - -- mapM (handleDrawRequest (drawStride ad) (drawCPP ad)) (drawStack ad2) - -- -- save all draw requests to clear in next run - -- put $ ad2 - -- { drawStack = clear } -- actual drawing SDL.glSwapWindow window - -- SDL.present (windowRenderer ad2) -- save new time ad3 <- get when (sysTime ad == sysTime ad3) ( @@ -178,7 +123,6 @@ withAffection AffectionConfig{..} = do ) ) ) initContainer - -- G.gegl_exit cleanUp $ userState nState SDL.destroyWindow window SDL.quit diff --git a/src/Affection/MessageBus.hs b/src/Affection/MessageBus.hs new file mode 100644 index 0000000..ea372ec --- /dev/null +++ b/src/Affection/MessageBus.hs @@ -0,0 +1,7 @@ +module Affection.Messagebus + ( module M + ) where + +import Affection.MessageBus.Class as M +import Affection.MessageBus.Message as M +import Affection.MessageBus.Util as M diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index c02687e..880abac 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -16,4 +16,4 @@ class (Message msg) => Participant prt msg where partListen :: prt -> IO msg - partBroadcast :: prt -> msg -> IO () + partEmit :: prt -> msg -> IO () diff --git a/src/Affection/MessageBus/Engine.hs b/src/Affection/MessageBus/Engine.hs deleted file mode 100644 index 9990a94..0000000 --- a/src/Affection/MessageBus/Engine.hs +++ /dev/null @@ -1,3 +0,0 @@ -module Affection.MessageBus.Engine where - -import Affection.MessageBus.Class diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index 8fe92fe..8b108ee 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -6,13 +6,13 @@ import Data.Time.Clock (UTCTime(..)) class Message msg where msgTime :: msg -> UTCTime -data EngineMessage m +data SystemMessage m = MsgUserMessage { msgPayload :: m , msgWhen :: UTCTime } -- ^ Generic user defined message with custom payload | MsgEngineReady UTCTime -instance Message (EngineMessage m) where +instance Message (SystemMessage m) where msgTime (MsgUserMessage _ t) = t msgTime (MsgEngineReady t) = t From d8d29ec7df4e8e1836677daaa2791d374c6e122e Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 27 Nov 2017 05:27:34 +0100 Subject: [PATCH 162/261] new subsystem --- src/Affection/Subsystems/AffectionWindow.hs | 70 +++++++++++++++++++++ src/Affection/Subsystems/Class.hs | 8 +++ 2 files changed, 78 insertions(+) create mode 100644 src/Affection/Subsystems/AffectionWindow.hs create mode 100644 src/Affection/Subsystems/Class.hs diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs new file mode 100644 index 0000000..fd0882f --- /dev/null +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -0,0 +1,70 @@ +module Affection.Subsystem.AffectionWindow where + +import Affection.MessageBus +import Affection.Subsystems.Class + +import qualified SDL + +data AffectionWindow = AffectionWindow + { windowChannel :: Channel msg + } + +instance Participant AffectionWindow where + partChannel = windowChannel + + partListen p = do + let chan = partChannel p + mmsg <- tryPeekChannel chan + return mmsg + + partEmit p m = do + let chan = partChannel p + writeChannel chan m + +instance Subsystem AffectionWindow where + consumeEvents aw evs = doConsume evs + where + doConsume (e:es) = case SDL.eventPayload e of + SDL.WindowShownEvent (SDL.WindowShownEventData window) = do + partEmit aw (MsgWindowShown (SDL.eventTiemstamp e) window) + doConsume es + SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) = do + partEmit aw (MsgWindowHidden (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowExposedEvent (SDL.WindowExposedEventData window) = do + partEmit aw (MsgWindowExposed (SDL.eventTimestamp e) window) + doConsume es + SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) = do + partEmit aw (MsgWindowMoved (SDL.EventTimestamp e) window newPos) + doConsume es + SDL.WindowResizedEvent (SDL.WindowResizedEventData window newSize) = do + partEmit aw (MsgWindowResized (SDL.EventTimestamp e) window newSize) + doConsume es + SDL.WindowSizeChangedEvent (SDL.WindowSizeChangedEventData window) = do + partEmit aw (MsgWindowSizeChanged (SDL.EventTimestamp e) window) + doConsume es + SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) = do + partEmit aw (MsgWindowMinimized (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) = do + partEmit aw (MsgWindowMaximized (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) = do + partEmit aw (MsgWindowRestored (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) = do + partEmit aw (MsgWindowGainedMouseFocus (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) = do + partEmit aw (MsgWindowLostMouseFocus (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) = do + partEmit aw (MsgWindowGainedKeyboardFocus (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) = do + partEmit aw (MsgWindowLostKeyboardFocus (SDL.eventTimestemp e) window) + doConsume es + SDL.WindowClosed (SDL.WindowClosedEventData window) = do + partEmit aw (MsgWindowClosed (SDL.eventTimestemp e) window) + doConsume es + _ = fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs new file mode 100644 index 0000000..7caea9f --- /dev/null +++ b/src/Affection/Subsystems/Class.hs @@ -0,0 +1,8 @@ +module Affection.Subsystems.Class where + +import Affection.MessageBus.Class + +import qualified SDL + +class (Participant s) => Subsystem s where + consumeEvents :: s -> [SDL.Event] -> IO [SDL.Event] From a5ca89f93c493eed5801bc8ef8e357d77038af72 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 27 Nov 2017 05:27:52 +0100 Subject: [PATCH 163/261] more message foo --- affection.cabal | 3 +- src/Affection/MessageBus.hs | 2 +- src/Affection/MessageBus/Class.hs | 8 +-- src/Affection/MessageBus/Message.hs | 89 +++++++++++++++++++++++++++-- src/Affection/MessageBus/Util.hs | 22 +++++++ 5 files changed, 111 insertions(+), 13 deletions(-) diff --git a/affection.cabal b/affection.cabal index 5f4a95e..74f1fb7 100644 --- a/affection.cabal +++ b/affection.cabal @@ -40,6 +40,7 @@ library , Affection.StateMachine , Affection.MouseInteractable , Affection.Util + , Affection.MessageBus , Affection.MessageBus.Util , Affection.MessageBus.Class , Affection.MessageBus.Message @@ -60,9 +61,9 @@ library -- Other library packages from which modules are imported. build-depends: base >=4.9 , sdl2 + , linear , text , mtl - , time , monad-loops , monad-parallel , containers diff --git a/src/Affection/MessageBus.hs b/src/Affection/MessageBus.hs index ea372ec..28d1bfa 100644 --- a/src/Affection/MessageBus.hs +++ b/src/Affection/MessageBus.hs @@ -1,4 +1,4 @@ -module Affection.Messagebus +module Affection.MessageBus ( module M ) where diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 880abac..1c0a526 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -3,17 +3,15 @@ module Affection.MessageBus.Class where import Control.Concurrent.STM as STM -import Data.IORef - import Affection.MessageBus.Message newtype (Message msg) => Channel msg = Channel (TChan msg) class (Message msg) => Participant prt msg where - partChannel :: prt -> IORef (Channel msg) + partChannel :: prt -> Channel msg - partConnectChannel :: prt -> Channel msg -> IO () + -- partConnectChannel :: prt -> Channel msg -> IO () - partListen :: prt -> IO msg + partListen :: prt -> IO (Maybe msg) partEmit :: prt -> msg -> IO () diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index 8b108ee..bedf10c 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -1,18 +1,95 @@ {-# LANGUAGE RankNTypes #-} module Affection.MessageBus.Message where -import Data.Time.Clock (UTCTime(..)) +import Data.Word (Word32(..)) +import Data.Int (Int32(..)) + +import qualified SDL + +import Linear (V2(..)) class Message msg where - msgTime :: msg -> UTCTime + msgTime :: msg -> Word32 data SystemMessage m = MsgUserMessage - { msgPayload :: m - , msgWhen :: UTCTime + { msgUMWhen :: Word32 + , msgUMPayload :: m } -- ^ Generic user defined message with custom payload - | MsgEngineReady UTCTime + | MsgEngineReady Word32 + | MsgWindowShown + { msgWSWhen :: Word32 + , msgWSWindow :: SDL.Window + } + | MsgWindowHidden + { msgWHWhen :: Word32 + , msgWHWindow :: SDL.Window + } + | MsgWindowExposed + { msgWEWhen :: Word32 + , msgWEWindow :: SDL.Window + } + | MsgWindowMoved + { msgWMWhen :: Word32 + , msgWMWindow :: SDL.Window + , msgWMNewPos :: V2 Int32 + } + | MsgWindowResized + { msgWRWhen :: Word32 + , msgWRWindow :: SDL.Window + , msgWRNewSize :: V2 Int32 + } + | MsgWindowSizeChanged + { msgWSCWhen :: Word32 + , msgWSCWindow :: SDL.Window + } + | MsgWindowMinimized + { msgWMinWhen :: Word32 + , msgWMinWindow :: SDL.Window + } + | MsgWindowMaximized + { msgWMaxWhen :: Word32 + , msgWMaxWindow :: SDL.Window + } + | MsgWindowRestored + { msgWRestWhen :: Word32 + , msgWRestWindow :: SDL.Window + } + | MsgWindowGainedMouseFocus + { msgWGMFWhen :: Word32 + , msgWGMFWindow :: SDL.Window + } + | MsgWindowLostMouseFocus + { msgWLMFWhen :: Word32 + , msgWLMFWindow :: SDL.Window + } + | MsgWindowGainedKeyboardFocus + { msgWGKFWhen :: Word32 + , msgWGKFWindow :: SDL.Window + } + | MsgWindowLostKeyboardFocus + { msgWLKFWhen :: Word32 + , msgWLKFWindow :: SDL.Window + } + | MsgWindowClosed + { msgWCWhen :: Word32 + , msgWCWindow :: SDL.Window + } instance Message (SystemMessage m) where - msgTime (MsgUserMessage _ t) = t + msgTime (MsgUserMessage t _) = t msgTime (MsgEngineReady t) = t + msgTime (MsgWindowShown t _) = t + msgTime (MsgWindowHidden t _) = t + msgTime (MsgWindowExposed t _) = t + msgTime (MsgWindowMoved t _ _) = t + msgTime (MsgWindowResized t _ _) = t + msgTime (MsgWindowSizeChanged t _) = t + msgTime (MsgWindowMinimized t _) = t + msgTime (MsgWindowMaximized t _) = t + msgTime (MsgWindowRestored t _) = t + msgTime (MsgWindowGainedMouseFocus t _) = t + msgTime (MsgWindowLostMouseFocus t _) = t + msgTime (MsgWindowGainedKeyboardFocus t _) = t + msgTime (MsgWindowLostKeyboardFocus t _) = t + msgTime (MsgWindowClosed t _) = t diff --git a/src/Affection/MessageBus/Util.hs b/src/Affection/MessageBus/Util.hs index ed7c551..501e9ab 100644 --- a/src/Affection/MessageBus/Util.hs +++ b/src/Affection/MessageBus/Util.hs @@ -4,5 +4,27 @@ import Affection.MessageBus.Class import Affection.MessageBus.Message import Control.Concurrent.STM as STM +-- | Build a new broadcast channel newBroadcastChannel :: (Message msg) => IO (Channel msg) newBroadcastChannel = atomically $ Channel <$> newBroadcastTChan + +-- | Duplicate a broadcast channel, so it can be accessed by subsystems +dupChannel :: (Message msg) + => Channel msg -- ^ Original 'Channel' + -> IO (Channel msg) -- ^ 'Channel' duplicate +dupChannel (Channel c) = atomically $ Channel <$> dupTChan c + +-- | Try to read a 'Channel' wihtout deleting the message. Returns 'Nothing' on +-- an empty 'Channel' +tryPeekChannel :: (Message msg) + => Channel msg -- ^ Channel to read from + -> IO (Maybe msg) -- ^ Resulting message (or not) +tryPeekChannel (Channel c) = atomically $ tryPeekTChan c + +-- | Write a message to a 'Channel' and thus broadcast it to all connected +-- subsystems +writeChannel :: (Message msg) + => Channel msg -- ^ 'Channel' to write to + -> msg -- ^ The Message to emit + -> IO () +writeChannel (Channel c) m = atomically $ writeTChan c m From c4e3528885afa235a5fb5d5483032e198733ae5f Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 27 Nov 2017 05:28:34 +0100 Subject: [PATCH 164/261] ignore vim swap files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ced8b76..df67c56 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ dist/ *.aux *.hp *.ps +*.swp From 4ae117aa4832288a10461fdfbfe1ee7643b76352 Mon Sep 17 00:00:00 2001 From: nek0 Date: Mon, 27 Nov 2017 23:30:11 +0100 Subject: [PATCH 165/261] classy messup --- affection.cabal | 2 + src/Affection.hs | 2 +- src/Affection/MessageBus/Class.hs | 8 +-- src/Affection/MessageBus/Util.hs | 1 + src/Affection/MouseInteractable.hs | 4 +- src/Affection/StateMachine.hs | 10 +-- src/Affection/Subsystems/AffectionWindow.hs | 78 ++++++++++++--------- src/Affection/Subsystems/Class.hs | 6 +- src/Affection/Types.hs | 20 ++++-- src/Affection/Util.hs | 14 ++-- 10 files changed, 84 insertions(+), 61 deletions(-) diff --git a/affection.cabal b/affection.cabal index 74f1fb7..5315c9d 100644 --- a/affection.cabal +++ b/affection.cabal @@ -44,6 +44,8 @@ library , Affection.MessageBus.Util , Affection.MessageBus.Class , Affection.MessageBus.Message + , Affection.Subsystems.Class + , Affection.Subsystems.AffectionWindow default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/src/Affection.hs b/src/Affection.hs index fd564ad..5022675 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -40,7 +40,7 @@ import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) -- | Main function which bootstraps everything else. withAffection - :: AffectionConfig us -- ^ Configuration of the Game and its engine. + :: AffectionConfig us msg -- ^ Configuration of the Game and its engine. -> IO () withAffection AffectionConfig{..} = do -- intialiaze SDL diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 1c0a526..4f888cc 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,16 +1,14 @@ {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE AllowAmbiguousTypes #-} module Affection.MessageBus.Class where -import Control.Concurrent.STM as STM - import Affection.MessageBus.Message - -newtype (Message msg) => Channel msg = Channel (TChan msg) +import Affection.Types class (Message msg) => Participant prt msg where partChannel :: prt -> Channel msg - -- partConnectChannel :: prt -> Channel msg -> IO () + partInit :: Affection sd msg prt partListen :: prt -> IO (Maybe msg) diff --git a/src/Affection/MessageBus/Util.hs b/src/Affection/MessageBus/Util.hs index 501e9ab..341f21b 100644 --- a/src/Affection/MessageBus/Util.hs +++ b/src/Affection/MessageBus/Util.hs @@ -2,6 +2,7 @@ module Affection.MessageBus.Util where import Affection.MessageBus.Class import Affection.MessageBus.Message +import Affection.Types import Control.Concurrent.STM as STM -- | Build a new broadcast channel diff --git a/src/Affection/MouseInteractable.hs b/src/Affection/MouseInteractable.hs index e3a6436..5e0c1f5 100644 --- a/src/Affection/MouseInteractable.hs +++ b/src/Affection/MouseInteractable.hs @@ -17,7 +17,7 @@ class MouseClickable a us where -> (Int, Int) -- The coordinates of the click -> SDL.InputMotion -- The 'SDL.InputMotion' of the click -> Int -- The number of clicks - -> Affection us () + -> Affection us msg () -- | A helper function that checks wether provided clickables have been clicked. -- This function does not consume provided events, but passes them on. @@ -25,7 +25,7 @@ handleMouseClicks :: (Foldable t, MouseClickable clickable us) => SDL.EventPayload -- ^ Piped event in -> t clickable -- ^ 'MouseClickable' elemt to be checked - -> Affection us SDL.EventPayload -- ^ Unaltered event + -> Affection us msg SDL.EventPayload -- ^ Unaltered event handleMouseClicks e clickables = case e of SDL.MouseButtonEvent dat -> do diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs index 067ae78..c2f9f3d 100644 --- a/src/Affection/StateMachine.hs +++ b/src/Affection/StateMachine.hs @@ -7,8 +7,8 @@ import Affection.Types import qualified SDL class StateMachine a us where - smLoad :: a -> Affection us () - smUpdate :: a -> Double -> Affection us () - smEvent :: a -> SDL.EventPayload -> Affection us () - smDraw :: a -> Affection us () - smClean :: a -> Affection us () + smLoad :: a -> Affection us msg () + smUpdate :: a -> Double -> Affection us msg () + smEvent :: a -> SDL.EventPayload -> Affection us msg () + smDraw :: a -> Affection us msg () + smClean :: a -> Affection us msg () diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index fd0882f..805047e 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -1,17 +1,31 @@ -module Affection.Subsystem.AffectionWindow where +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +module Affection.Subsystems.AffectionWindow where import Affection.MessageBus import Affection.Subsystems.Class +import Affection.Types + +import Control.Monad.State.Class (get) +import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionWindow = AffectionWindow +data AffectionWindow msg = AffectionWindow { windowChannel :: Channel msg } -instance Participant AffectionWindow where +instance Participant (AffectionWindow (SystemMessage m)) (SystemMessage m) where partChannel = windowChannel + partInit = do + ad <- get + nchan <- liftIO $ dupChannel $ messageChannel ad + return $ AffectionWindow + { windowChannel = nchan + } + partListen p = do let chan = partChannel p mmsg <- tryPeekChannel chan @@ -21,50 +35,50 @@ instance Participant AffectionWindow where let chan = partChannel p writeChannel chan m -instance Subsystem AffectionWindow where +instance Subsystem (AffectionWindow (SystemMessage m)) (SystemMessage m) where consumeEvents aw evs = doConsume evs where doConsume (e:es) = case SDL.eventPayload e of - SDL.WindowShownEvent (SDL.WindowShownEventData window) = do - partEmit aw (MsgWindowShown (SDL.eventTiemstamp e) window) + SDL.WindowShownEvent (SDL.WindowShownEventData window) -> do + partEmit aw (MsgWindowShown (SDL.eventTimestamp e) window) doConsume es - SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) = do - partEmit aw (MsgWindowHidden (SDL.eventTimestemp e) window) + SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) -> do + partEmit aw (MsgWindowHidden (SDL.eventTimestamp e) window) doConsume es - SDL.WindowExposedEvent (SDL.WindowExposedEventData window) = do + SDL.WindowExposedEvent (SDL.WindowExposedEventData window) -> do partEmit aw (MsgWindowExposed (SDL.eventTimestamp e) window) doConsume es - SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) = do - partEmit aw (MsgWindowMoved (SDL.EventTimestamp e) window newPos) + SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) -> do + partEmit aw (MsgWindowMoved (SDL.eventTimestamp e) window newPos) doConsume es - SDL.WindowResizedEvent (SDL.WindowResizedEventData window newSize) = do - partEmit aw (MsgWindowResized (SDL.EventTimestamp e) window newSize) + SDL.WindowResizedEvent (SDL.WindowResizedEventData window newSize) -> do + partEmit aw (MsgWindowResized (SDL.eventTimestamp e) window newSize) doConsume es - SDL.WindowSizeChangedEvent (SDL.WindowSizeChangedEventData window) = do - partEmit aw (MsgWindowSizeChanged (SDL.EventTimestamp e) window) + SDL.WindowSizeChangedEvent (SDL.WindowSizeChangedEventData window) -> do + partEmit aw (MsgWindowSizeChanged (SDL.eventTimestamp e) window) doConsume es - SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) = do - partEmit aw (MsgWindowMinimized (SDL.eventTimestemp e) window) + SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) -> do + partEmit aw (MsgWindowMinimized (SDL.eventTimestamp e) window) doConsume es - SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) = do - partEmit aw (MsgWindowMaximized (SDL.eventTimestemp e) window) + SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) -> do + partEmit aw (MsgWindowMaximized (SDL.eventTimestamp e) window) doConsume es - SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) = do - partEmit aw (MsgWindowRestored (SDL.eventTimestemp e) window) + SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) -> do + partEmit aw (MsgWindowRestored (SDL.eventTimestamp e) window) doConsume es - SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) = do - partEmit aw (MsgWindowGainedMouseFocus (SDL.eventTimestemp e) window) + SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) -> do + partEmit aw (MsgWindowGainedMouseFocus (SDL.eventTimestamp e) window) doConsume es - SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) = do - partEmit aw (MsgWindowLostMouseFocus (SDL.eventTimestemp e) window) + SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) -> do + partEmit aw (MsgWindowLostMouseFocus (SDL.eventTimestamp e) window) doConsume es - SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) = do - partEmit aw (MsgWindowGainedKeyboardFocus (SDL.eventTimestemp e) window) + SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) -> do + partEmit aw (MsgWindowGainedKeyboardFocus (SDL.eventTimestamp e) window) doConsume es - SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) = do - partEmit aw (MsgWindowLostKeyboardFocus (SDL.eventTimestemp e) window) + SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) -> do + partEmit aw (MsgWindowLostKeyboardFocus (SDL.eventTimestamp e) window) doConsume es - SDL.WindowClosed (SDL.WindowClosedEventData window) = do - partEmit aw (MsgWindowClosed (SDL.eventTimestemp e) window) + SDL.WindowClosedEvent (SDL.WindowClosedEventData window) -> do + partEmit aw (MsgWindowClosed (SDL.eventTimestamp e) window) doConsume es - _ = fmap (e :) (doConsume es) + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index 7caea9f..e104640 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -1,8 +1,10 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE AllowAmbiguousTypes #-} module Affection.Subsystems.Class where -import Affection.MessageBus.Class +import Affection.MessageBus import qualified SDL -class (Participant s) => Subsystem s where +class (Message m, Participant s m) => Subsystem s m where consumeEvents :: s -> [SDL.Event] -> IO [SDL.Event] diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index df107dc..bb0b1e9 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -42,6 +42,7 @@ import Data.Map.Strict as M import Control.Monad.IO.Class import Control.Monad.State import qualified Control.Monad.Parallel as MP +import Control.Concurrent.STM as STM import System.Clock (TimeSpec) -- import Control.Monad.Reader @@ -50,8 +51,10 @@ import System.Clock (TimeSpec) import Foreign.Ptr (Ptr) +import Affection.MessageBus.Message + -- | Configuration for the aplication. needed at startup. -data AffectionConfig us = AffectionConfig +data AffectionConfig us msg = AffectionConfig { initComponents :: InitComponents -- ^ SDL components to initialize at startup , windowTitle :: T.Text @@ -62,13 +65,13 @@ data AffectionConfig us = AffectionConfig -- ^ size of the texture canvas , initScreenMode :: SDL.WindowMode -- ^ Window mode to start in - , preLoop :: Affection us () + , preLoop :: Affection us msg () -- ^ Actions to be performed, before loop starts - , eventLoop :: SDL.EventPayload -> Affection us () + , eventLoop :: SDL.EventPayload -> Affection us msg () -- ^ Main update function. Takes fractions of a second as input. - , updateLoop :: Double -> Affection us () + , updateLoop :: Double -> Affection us msg () -- ^ Main update function. Takes fractions of a second as input. - , drawLoop :: Affection us () + , drawLoop :: Affection us msg () -- ^ Function for updating graphics. , loadState :: IO us -- ^ Provide your own load function to create this data. @@ -82,7 +85,7 @@ data InitComponents | Only [SDL.InitFlag] -- | Main type for defining the look, feel and action of the whole application. -data AffectionData us = AffectionData +data AffectionData us msg = AffectionData -- { affectionConfig :: AffectionConfig us -- ^ Application configuration. { quitEvent :: Bool -- ^ Loop breaker. , userState :: us -- ^ State data provided by user @@ -100,6 +103,7 @@ data AffectionData us = AffectionData , 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 @@ -127,7 +131,7 @@ newtype AffectionState us m a = AffectionState instance MP.MonadParallel m => MP.MonadParallel (AffectionState us m) -type Affection us a = AffectionState (AffectionData us) IO a +type Affection us msg a = AffectionState (AffectionData us msg) IO a -- -- | Inner 'StateT' monad of Affection -- type AffectionInner us od a = StateT (AffectionState us od) IO a @@ -228,3 +232,5 @@ type Angle = Double -- { partStorLatest :: Maybe Particle -- ^ The particle stored last -- , partStorList :: [Particle] -- ^ List of particles in ascending order of remaining lifetime -- } + +newtype (Message msg) => Channel msg = Channel (TChan msg) diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 9cf794a..84d0dd2 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -10,12 +10,12 @@ import System.Clock import Control.Monad.State -- Prehandle SDL events in case any window events occur -preHandleEvents :: [SDL.Event] -> Affection us [SDL.EventPayload] +preHandleEvents :: [SDL.Event] -> Affection us msg [SDL.EventPayload] preHandleEvents evs = return $ map SDL.eventPayload evs -- | Return the userstate to the user -getAffection :: Affection us us +getAffection :: Affection us msg us getAffection = do ad <- get return $ userState ad @@ -23,7 +23,7 @@ getAffection = do -- | Put altered user state back putAffection :: us -- User state - -> Affection us () + -> Affection us msg () putAffection us = do ad <- get put $ ad @@ -36,20 +36,20 @@ 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 us msg Double getElapsedTime = elapsedTime <$> get -getDelta :: Affection us Double +getDelta :: Affection us msg Double getDelta = deltaTime <$> get -quit :: Affection us () +quit :: Affection us msg () quit = do ad <- get put $ ad { quitEvent = True } -toggleScreen :: Affection us () +toggleScreen :: Affection us msg () toggleScreen = do ad <- get newMode <- case screenMode ad of From c40cd04d999b5742ada5df84b0cf44ac2e5ba97a Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 29 Nov 2017 08:29:44 +0100 Subject: [PATCH 166/261] it compiles now. messagebus will have multiple channels --- src/Affection/MessageBus/Class.hs | 10 +++++----- src/Affection/MessageBus/Message.hs | 16 ++++++++-------- src/Affection/Subsystems/AffectionWindow.hs | 18 +++++++++--------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 4f888cc..80a1c51 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -5,11 +5,11 @@ module Affection.MessageBus.Class where import Affection.MessageBus.Message import Affection.Types -class (Message msg) => Participant prt msg where - partChannel :: prt -> Channel msg +class Participant prt m where + partChannel :: prt -> Channel m - partInit :: Affection sd msg prt + partInit :: Affection sd m prt - partListen :: prt -> IO (Maybe msg) + partListen :: prt -> IO (Maybe m) - partEmit :: prt -> msg -> IO () + partEmit :: prt -> m -> IO () diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index bedf10c..b96419d 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -11,12 +11,12 @@ import Linear (V2(..)) class Message msg where msgTime :: msg -> Word32 -data SystemMessage m - = MsgUserMessage - { msgUMWhen :: Word32 - , msgUMPayload :: m - } -- ^ Generic user defined message with custom payload - | MsgEngineReady Word32 +data SystemMessage + -- = MsgUserMessage + -- { msgUMWhen :: Word32 + -- , msgUMPayload :: m + -- } -- ^ Generic user defined message with custom payload + = MsgEngineReady Word32 | MsgWindowShown { msgWSWhen :: Word32 , msgWSWindow :: SDL.Window @@ -76,8 +76,8 @@ data SystemMessage m , msgWCWindow :: SDL.Window } -instance Message (SystemMessage m) where - msgTime (MsgUserMessage t _) = t +instance Message SystemMessage where + -- msgTime (MsgUserMessage t _) = t msgTime (MsgEngineReady t) = t msgTime (MsgWindowShown t _) = t msgTime (MsgWindowHidden t _) = t diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 805047e..2954ee5 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -7,21 +7,21 @@ import Affection.MessageBus import Affection.Subsystems.Class import Affection.Types -import Control.Monad.State.Class (get) +import Control.Monad.State.Class (gets) import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionWindow msg = AffectionWindow - { windowChannel :: Channel msg +data AffectionWindow m = AffectionWindow + { windowChannel :: Channel m } -instance Participant (AffectionWindow (SystemMessage m)) (SystemMessage m) where +instance (Message m) => Participant (AffectionWindow m) m where partChannel = windowChannel partInit = do - ad <- get - nchan <- liftIO $ dupChannel $ messageChannel ad + chan <- gets messageChannel :: Affection sd m (Channel m) + nchan <- liftIO $ dupChannel $ chan return $ AffectionWindow { windowChannel = nchan } @@ -31,11 +31,11 @@ instance Participant (AffectionWindow (SystemMessage m)) (SystemMessage m) where mmsg <- tryPeekChannel chan return mmsg - partEmit p m = do + partEmit p message = do let chan = partChannel p - writeChannel chan m + writeChannel chan message -instance Subsystem (AffectionWindow (SystemMessage m)) (SystemMessage m) where +instance Subsystem (AffectionWindow SystemMessage) SystemMessage where consumeEvents aw evs = doConsume evs where doConsume (e:es) = case SDL.eventPayload e of From df2f59c6b91eef26899a8055df0bad53914d925c Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 29 Nov 2017 17:49:56 +0100 Subject: [PATCH 167/261] splitting the messages up --- affection.cabal | 2 + src/Affection.hs | 13 +-- src/Affection/MessageBus/Message.hs | 99 +------------------ src/Affection/MessageBus/Message/Class.hs | 7 ++ .../MessageBus/Message/WindowMessage.hs | 89 +++++++++++++++++ 5 files changed, 105 insertions(+), 105 deletions(-) create mode 100644 src/Affection/MessageBus/Message/Class.hs create mode 100644 src/Affection/MessageBus/Message/WindowMessage.hs diff --git a/affection.cabal b/affection.cabal index 5315c9d..018d1ff 100644 --- a/affection.cabal +++ b/affection.cabal @@ -44,6 +44,8 @@ library , Affection.MessageBus.Util , Affection.MessageBus.Class , Affection.MessageBus.Message + , Affection.MessageBus.Message.Class + , Affection.MessageBus.Message.WindowMessage , Affection.Subsystems.Class , Affection.Subsystems.AffectionWindow default-extensions: OverloadedStrings diff --git a/src/Affection.hs b/src/Affection.hs index 5022675..59448a2 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -3,21 +3,12 @@ module Affection ( withAffection , get - , getAffection - , putAffection - , delaySec - , get , put , module A ) where import SDL (($=)) import qualified SDL -import qualified SDL.Internal.Numbered as SDL (toNumber) -import qualified SDL.Raw as Raw - -import Data.Maybe -import Data.IORef import System.Clock @@ -25,7 +16,6 @@ import Control.Monad.Loops import Control.Monad.State import Foreign.C.Types (CInt(..)) -import Foreign.Storable (peek) import Debug.Trace @@ -95,7 +85,8 @@ withAffection AffectionConfig{..} = do now <- liftIO $ getTime Monotonic let lastTime = sysTime ad -- compute dt and update elapsedTime - let !dt = fromIntegral (toNanoSecs $ diffTimeSpec lastTime now) / (10 ^ 9) + let !dt = fromIntegral + (toNanoSecs $ diffTimeSpec lastTime now) / (10 ^ (9 :: Int)) !ne = elapsedTime ad + dt put $ ad { elapsedTime = ne diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index b96419d..b1d0d46 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -1,95 +1,6 @@ -{-# LANGUAGE RankNTypes #-} -module Affection.MessageBus.Message where +module Affection.MessageBus.Message + ( module M + ) where -import Data.Word (Word32(..)) -import Data.Int (Int32(..)) - -import qualified SDL - -import Linear (V2(..)) - -class Message msg where - msgTime :: msg -> Word32 - -data SystemMessage - -- = MsgUserMessage - -- { msgUMWhen :: Word32 - -- , msgUMPayload :: m - -- } -- ^ Generic user defined message with custom payload - = MsgEngineReady Word32 - | MsgWindowShown - { msgWSWhen :: Word32 - , msgWSWindow :: SDL.Window - } - | MsgWindowHidden - { msgWHWhen :: Word32 - , msgWHWindow :: SDL.Window - } - | MsgWindowExposed - { msgWEWhen :: Word32 - , msgWEWindow :: SDL.Window - } - | MsgWindowMoved - { msgWMWhen :: Word32 - , msgWMWindow :: SDL.Window - , msgWMNewPos :: V2 Int32 - } - | MsgWindowResized - { msgWRWhen :: Word32 - , msgWRWindow :: SDL.Window - , msgWRNewSize :: V2 Int32 - } - | MsgWindowSizeChanged - { msgWSCWhen :: Word32 - , msgWSCWindow :: SDL.Window - } - | MsgWindowMinimized - { msgWMinWhen :: Word32 - , msgWMinWindow :: SDL.Window - } - | MsgWindowMaximized - { msgWMaxWhen :: Word32 - , msgWMaxWindow :: SDL.Window - } - | MsgWindowRestored - { msgWRestWhen :: Word32 - , msgWRestWindow :: SDL.Window - } - | MsgWindowGainedMouseFocus - { msgWGMFWhen :: Word32 - , msgWGMFWindow :: SDL.Window - } - | MsgWindowLostMouseFocus - { msgWLMFWhen :: Word32 - , msgWLMFWindow :: SDL.Window - } - | MsgWindowGainedKeyboardFocus - { msgWGKFWhen :: Word32 - , msgWGKFWindow :: SDL.Window - } - | MsgWindowLostKeyboardFocus - { msgWLKFWhen :: Word32 - , msgWLKFWindow :: SDL.Window - } - | MsgWindowClosed - { msgWCWhen :: Word32 - , msgWCWindow :: SDL.Window - } - -instance Message SystemMessage where - -- msgTime (MsgUserMessage t _) = t - msgTime (MsgEngineReady t) = t - msgTime (MsgWindowShown t _) = t - msgTime (MsgWindowHidden t _) = t - msgTime (MsgWindowExposed t _) = t - msgTime (MsgWindowMoved t _ _) = t - msgTime (MsgWindowResized t _ _) = t - msgTime (MsgWindowSizeChanged t _) = t - msgTime (MsgWindowMinimized t _) = t - msgTime (MsgWindowMaximized t _) = t - msgTime (MsgWindowRestored t _) = t - msgTime (MsgWindowGainedMouseFocus t _) = t - msgTime (MsgWindowLostMouseFocus t _) = t - msgTime (MsgWindowGainedKeyboardFocus t _) = t - msgTime (MsgWindowLostKeyboardFocus t _) = t - msgTime (MsgWindowClosed t _) = t +import Affection.MessageBus.Message.Class as M +import Affection.MessageBus.Message.WindowMessage as M diff --git a/src/Affection/MessageBus/Message/Class.hs b/src/Affection/MessageBus/Message/Class.hs new file mode 100644 index 0000000..bd18c4b --- /dev/null +++ b/src/Affection/MessageBus/Message/Class.hs @@ -0,0 +1,7 @@ +module Affection.MessageBus.Message.Class where + +import Data.Word (Word32(..)) + + +class Message msg where + msgTime :: msg -> Word32 diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs new file mode 100644 index 0000000..95b9795 --- /dev/null +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -0,0 +1,89 @@ +{-# LANGUAGE RankNTypes #-} +module Affection.MessageBus.Message.WindowMessage where + +import Affection.MessageBus.Message.Class + +import Data.Word (Word32(..)) +import Data.Int (Int32(..)) + +import qualified SDL + +import Linear (V2(..)) + +data SystemMessage + = MsgEngineReady Word32 + | MsgWindowShown + { msgWSWhen :: Word32 + , msgWSWindow :: SDL.Window + } + | MsgWindowHidden + { msgWHWhen :: Word32 + , msgWHWindow :: SDL.Window + } + | MsgWindowExposed + { msgWEWhen :: Word32 + , msgWEWindow :: SDL.Window + } + | MsgWindowMoved + { msgWMWhen :: Word32 + , msgWMWindow :: SDL.Window + , msgWMNewPos :: V2 Int32 + } + | MsgWindowResized + { msgWRWhen :: Word32 + , msgWRWindow :: SDL.Window + , msgWRNewSize :: V2 Int32 + } + | MsgWindowSizeChanged + { msgWSCWhen :: Word32 + , msgWSCWindow :: SDL.Window + } + | MsgWindowMinimized + { msgWMinWhen :: Word32 + , msgWMinWindow :: SDL.Window + } + | MsgWindowMaximized + { msgWMaxWhen :: Word32 + , msgWMaxWindow :: SDL.Window + } + | MsgWindowRestored + { msgWRestWhen :: Word32 + , msgWRestWindow :: SDL.Window + } + | MsgWindowGainedMouseFocus + { msgWGMFWhen :: Word32 + , msgWGMFWindow :: SDL.Window + } + | MsgWindowLostMouseFocus + { msgWLMFWhen :: Word32 + , msgWLMFWindow :: SDL.Window + } + | MsgWindowGainedKeyboardFocus + { msgWGKFWhen :: Word32 + , msgWGKFWindow :: SDL.Window + } + | MsgWindowLostKeyboardFocus + { msgWLKFWhen :: Word32 + , msgWLKFWindow :: SDL.Window + } + | MsgWindowClosed + { msgWCWhen :: Word32 + , msgWCWindow :: SDL.Window + } + +instance Message SystemMessage where + msgTime (MsgEngineReady t) = t + msgTime (MsgWindowShown t _) = t + msgTime (MsgWindowHidden t _) = t + msgTime (MsgWindowExposed t _) = t + msgTime (MsgWindowMoved t _ _) = t + msgTime (MsgWindowResized t _ _) = t + msgTime (MsgWindowSizeChanged t _) = t + msgTime (MsgWindowMinimized t _) = t + msgTime (MsgWindowMaximized t _) = t + msgTime (MsgWindowRestored t _) = t + msgTime (MsgWindowGainedMouseFocus t _) = t + msgTime (MsgWindowLostMouseFocus t _) = t + msgTime (MsgWindowGainedKeyboardFocus t _) = t + msgTime (MsgWindowLostKeyboardFocus t _) = t + msgTime (MsgWindowClosed t _) = t From d6f80ed1f01d32b70647669681f7d6c17879b65c Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 6 Dec 2017 06:36:25 +0100 Subject: [PATCH 168/261] rename message type --- src/Affection/MessageBus/Message/WindowMessage.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index 95b9795..b9e3727 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -10,7 +10,7 @@ import qualified SDL import Linear (V2(..)) -data SystemMessage +data WindowMessage = MsgEngineReady Word32 | MsgWindowShown { msgWSWhen :: Word32 @@ -71,7 +71,7 @@ data SystemMessage , msgWCWindow :: SDL.Window } -instance Message SystemMessage where +instance Message WindowMessage where msgTime (MsgEngineReady t) = t msgTime (MsgWindowShown t _) = t msgTime (MsgWindowHidden t _) = t From c33aa67c3e8e2f9ba89c066afb8fe2d00dbbdef8 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 10 Dec 2017 16:10:09 +0100 Subject: [PATCH 169/261] remove message type variable --- src/Affection/Types.hs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index bb0b1e9..d5bff6b 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -54,7 +54,7 @@ import Foreign.Ptr (Ptr) import Affection.MessageBus.Message -- | Configuration for the aplication. needed at startup. -data AffectionConfig us msg = AffectionConfig +data AffectionConfig us = AffectionConfig { initComponents :: InitComponents -- ^ SDL components to initialize at startup , windowTitle :: T.Text @@ -65,13 +65,13 @@ data AffectionConfig us msg = AffectionConfig -- ^ size of the texture canvas , initScreenMode :: SDL.WindowMode -- ^ Window mode to start in - , preLoop :: Affection us msg () + , preLoop :: Affection us () -- ^ Actions to be performed, before loop starts - , eventLoop :: SDL.EventPayload -> Affection us msg () + , eventLoop :: SDL.EventPayload -> Affection us () -- ^ Main update function. Takes fractions of a second as input. - , updateLoop :: Double -> Affection us msg () + , updateLoop :: Double -> Affection us () -- ^ Main update function. Takes fractions of a second as input. - , drawLoop :: Affection us msg () + , drawLoop :: Affection us () -- ^ Function for updating graphics. , loadState :: IO us -- ^ Provide your own load function to create this data. @@ -103,7 +103,7 @@ data AffectionData us msg = AffectionData , 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 + -- , 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 @@ -122,16 +122,16 @@ data AffectionData us msg = AffectionData -- | Inner 'StateT' monad for the update state -- type AffectionStateInner us m a = StateT (AffectionData us) m a -type AffectionStateInner us m a = StateT us m a +type AffectionStateInner us a = StateT us a -- | Affection's state monad -newtype AffectionState us m a = AffectionState - { runState :: AffectionStateInner us m a } +newtype AffectionState us a = AffectionState + { runState :: AffectionStateInner us a } deriving (Functor, Applicative, Monad, MonadIO, MonadState us) -instance MP.MonadParallel m => MP.MonadParallel (AffectionState us m) +instance MP.MonadParallel m => MP.MonadParallel (AffectionState us) -type Affection us msg a = AffectionState (AffectionData us msg) IO a +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 From c82325a1bd35d2ba65784d233d7c5cbca21bf050 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 12 Dec 2017 13:10:00 +0100 Subject: [PATCH 170/261] logging module --- affection.cabal | 22 ++++++++++++++++++++++ src/Affection/Logging.hs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/Affection/Logging.hs diff --git a/affection.cabal b/affection.cabal index 018d1ff..97fc92d 100644 --- a/affection.cabal +++ b/affection.cabal @@ -30,12 +30,34 @@ source-repository head type: git location: https://github.com/nek0/affection +flag debug + description: Enable debug messages + default: False + manual: True + +flag warn + description: Enable warning messages + default: False + manual: True + +flag error + description: Enable error messages + default: False + manual: True + flag examples description: Build example programs default: False library + if flag(debug) + cpp-options: -DDEBUG + if flag(warn) + cpp-options: -DWARN + if flag(error) + cpp-options: -DERROR exposed-modules: Affection + , Affection.Logging , Affection.Types , Affection.StateMachine , Affection.MouseInteractable diff --git a/src/Affection/Logging.hs b/src/Affection/Logging.hs new file mode 100644 index 0000000..0cf3032 --- /dev/null +++ b/src/Affection/Logging.hs @@ -0,0 +1,33 @@ +{-# LANGUAGE CPP #-} +module Affection.Logging where + +import Debug.Trace + +data LogLevel + = Debug + | Warn + | Error + +log :: LogLevel -> String -> a -> a +#if defined(DEBUG) +log Debug s = trace ("DEBUG: " ++ s) +#endif +#if defined(WARN) || defined(DEBUG) +log Warn s = trace ("WARN: " ++ s) +#endif +#if defined(ERROR) || defined(WARN) || defined(DEBUG) +log Error s = trace ("ERROR: " ++ s) +#endif +log _ _ = id + +logIO :: LogLevel -> String -> IO () +#if defined(DEBUG) +logIO Debug s = traceIO ("DEBUG: " ++ s) +#endif +#if defined(WARN) || defined(DEBUG) +logIO Warn s = traceIO ("WARN: " ++ s) +#endif +#if defined(ERROR) || defined(WARN) || defined(DEBUG) +logIO Error s = traceIO ("ERROR: " ++ s) +#endif +logIO _ _ = return () From 853951df5bab95315d3662e80fc725fa1dce9f50 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 12 Dec 2017 13:10:55 +0100 Subject: [PATCH 171/261] keyboard subsystem added --- affection.cabal | 2 + .../MessageBus/Message/KeyboardMessage.hs | 18 ++++++++ src/Affection/Subsystems/AffectionKeyboard.hs | 44 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/Affection/MessageBus/Message/KeyboardMessage.hs create mode 100644 src/Affection/Subsystems/AffectionKeyboard.hs diff --git a/affection.cabal b/affection.cabal index 97fc92d..0f144f5 100644 --- a/affection.cabal +++ b/affection.cabal @@ -68,8 +68,10 @@ library , Affection.MessageBus.Message , Affection.MessageBus.Message.Class , Affection.MessageBus.Message.WindowMessage + , Affection.MessageBus.Message.KeyboardMessage , Affection.Subsystems.Class , Affection.Subsystems.AffectionWindow + , Affection.Subsystems.AffectionKeyboard default-extensions: OverloadedStrings -- Modules included in this library but not exported. diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs new file mode 100644 index 0000000..e783592 --- /dev/null +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -0,0 +1,18 @@ +module Affection.MessageBus.Message.KeyboardMessage where + +import Affection.MessageBus.Message.Class + +import Data.Word (Word32(..)) + +import qualified SDL + +data KeyboardMessage = MsgKeyboardEvent + { msgKbdWhen :: Word32 + , msgKbdWindow :: Maybe SDL.Window + , msgKbdKeyMotion :: SDL.InputMotion + , msgKbdLeyRepeat :: Bool + , msgKbdKeysym :: SDL.Keysym + } + +instance Message KeyboardMessage where + msgTime (MsgKeyboardEvent t _ _ _ _) = t diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs new file mode 100644 index 0000000..de5ad62 --- /dev/null +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +module Affection.Subsystems.AffectionKeyboard where + +import Affection.MessageBus +import Affection.Subsystems.Class +import Affection.Types + +import Control.Monad.IO.Class (liftIO) + +import qualified SDL + +data AffectionKeyboard = AffectionKeyboard + { keyboardInChannel :: Channel KeyboardMessage + , keyboardOutChannel :: Channel KeyboardMessage + } + +instance Participant AffectionKeyboard KeyboardMessage where + partChannel = keyboardOutChannel + + partInit ichan = do + ochan <- liftIO $ newBroadcastChannel + return $ AffectionKeyboard ichan ochan + + partListen p = + liftIO $ tryPeekChannel (keyboardInChannel p) + + partEmit p m = + liftIO $ writeChannel (keyboardOutChannel p) m + +instance SDLSubsystem AffectionKeyboard KeyboardMessage where + consumeSDLEvents ak evs = doConsume evs + where + doConsume [] = return [] + doConsume (e:es) = case SDL.eventPayload e of + SDL.KeyboardEvent dat -> do + partEmit ak (MsgKeyboardEvent + (SDL.eventTimestamp e) + (SDL.keyboardEventWindow dat) + (SDL.keyboardEventKeyMotion dat) + (SDL.keyboardEventRepeat dat) + (SDL.keyboardEventKeysym dat) + ) + doConsume es + _ -> fmap (e :) (doConsume es) From f44021f03483b415cd0f6d733388491a1b1a96fe Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 12 Dec 2017 13:12:06 +0100 Subject: [PATCH 172/261] eliminate msg --- src/Affection.hs | 2 +- src/Affection/MessageBus/Message/Class.hs | 1 - src/Affection/MouseInteractable.hs | 4 ++-- src/Affection/StateMachine.hs | 10 +++++----- src/Affection/Types.hs | 8 ++++---- src/Affection/Util.hs | 14 +++++++------- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 59448a2..2917ec7 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -30,7 +30,7 @@ import Graphics.Rendering.OpenGL as GL (clear, flush, ClearBuffer(..)) -- | Main function which bootstraps everything else. withAffection - :: AffectionConfig us msg -- ^ Configuration of the Game and its engine. + :: AffectionConfig us -- ^ Configuration of the Game and its engine. -> IO () withAffection AffectionConfig{..} = do -- intialiaze SDL diff --git a/src/Affection/MessageBus/Message/Class.hs b/src/Affection/MessageBus/Message/Class.hs index bd18c4b..b97de31 100644 --- a/src/Affection/MessageBus/Message/Class.hs +++ b/src/Affection/MessageBus/Message/Class.hs @@ -2,6 +2,5 @@ module Affection.MessageBus.Message.Class where import Data.Word (Word32(..)) - class Message msg where msgTime :: msg -> Word32 diff --git a/src/Affection/MouseInteractable.hs b/src/Affection/MouseInteractable.hs index 5e0c1f5..e3a6436 100644 --- a/src/Affection/MouseInteractable.hs +++ b/src/Affection/MouseInteractable.hs @@ -17,7 +17,7 @@ class MouseClickable a us where -> (Int, Int) -- The coordinates of the click -> SDL.InputMotion -- The 'SDL.InputMotion' of the click -> Int -- The number of clicks - -> Affection us msg () + -> Affection us () -- | A helper function that checks wether provided clickables have been clicked. -- This function does not consume provided events, but passes them on. @@ -25,7 +25,7 @@ handleMouseClicks :: (Foldable t, MouseClickable clickable us) => SDL.EventPayload -- ^ Piped event in -> t clickable -- ^ 'MouseClickable' elemt to be checked - -> Affection us msg SDL.EventPayload -- ^ Unaltered event + -> Affection us SDL.EventPayload -- ^ Unaltered event handleMouseClicks e clickables = case e of SDL.MouseButtonEvent dat -> do diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs index c2f9f3d..067ae78 100644 --- a/src/Affection/StateMachine.hs +++ b/src/Affection/StateMachine.hs @@ -7,8 +7,8 @@ import Affection.Types import qualified SDL class StateMachine a us where - smLoad :: a -> Affection us msg () - smUpdate :: a -> Double -> Affection us msg () - smEvent :: a -> SDL.EventPayload -> Affection us msg () - smDraw :: a -> Affection us msg () - smClean :: a -> Affection us msg () + smLoad :: a -> Affection us () + smUpdate :: a -> Double -> Affection us () + smEvent :: a -> SDL.EventPayload -> Affection us () + smDraw :: a -> Affection us () + smClean :: a -> Affection us () diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index d5bff6b..8810fe1 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -85,7 +85,7 @@ data InitComponents | Only [SDL.InitFlag] -- | Main type for defining the look, feel and action of the whole application. -data AffectionData us msg = AffectionData +data AffectionData us = AffectionData -- { affectionConfig :: AffectionConfig us -- ^ Application configuration. { quitEvent :: Bool -- ^ Loop breaker. , userState :: us -- ^ State data provided by user @@ -125,11 +125,11 @@ data AffectionData us msg = AffectionData type AffectionStateInner us a = StateT us a -- | Affection's state monad -newtype AffectionState us a = AffectionState - { runState :: AffectionStateInner us a } +newtype AffectionState us m a = AffectionState + { runState :: AffectionStateInner us m a } deriving (Functor, Applicative, Monad, MonadIO, MonadState us) -instance MP.MonadParallel m => MP.MonadParallel (AffectionState us) +instance MP.MonadParallel m => MP.MonadParallel (AffectionState us m) type Affection us a = AffectionState (AffectionData us) IO a diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 84d0dd2..9cf794a 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -10,12 +10,12 @@ import System.Clock import Control.Monad.State -- Prehandle SDL events in case any window events occur -preHandleEvents :: [SDL.Event] -> Affection us msg [SDL.EventPayload] +preHandleEvents :: [SDL.Event] -> Affection us [SDL.EventPayload] preHandleEvents evs = return $ map SDL.eventPayload evs -- | Return the userstate to the user -getAffection :: Affection us msg us +getAffection :: Affection us us getAffection = do ad <- get return $ userState ad @@ -23,7 +23,7 @@ getAffection = do -- | Put altered user state back putAffection :: us -- User state - -> Affection us msg () + -> Affection us () putAffection us = do ad <- get put $ ad @@ -36,20 +36,20 @@ delaySec delaySec dur = SDL.delay (fromIntegral $ dur * 1000) -- | Get time since start but always the same in the current tick. -getElapsedTime :: Affection us msg Double +getElapsedTime :: Affection us Double getElapsedTime = elapsedTime <$> get -getDelta :: Affection us msg Double +getDelta :: Affection us Double getDelta = deltaTime <$> get -quit :: Affection us msg () +quit :: Affection us () quit = do ad <- get put $ ad { quitEvent = True } -toggleScreen :: Affection us msg () +toggleScreen :: Affection us () toggleScreen = do ad <- get newMode <- case screenMode ad of From aa7be8488399197ba2a70ab546dc4ba9c7721520 Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 12 Dec 2017 13:12:27 +0100 Subject: [PATCH 173/261] moar documentation --- src/Affection/MessageBus/Class.hs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 80a1c51..b83e99b 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,15 +1,33 @@ {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE AllowAmbiguousTypes #-} module Affection.MessageBus.Class where import Affection.MessageBus.Message import Affection.Types -class Participant prt m where - partChannel :: prt -> Channel m +class (Message m) => Participant prt m where + -- | get the outbound 'Channel' of the 'Participant' + partChannel + :: prt -- ^ The 'Participant' + -> Channel m -- ^ Outbound 'Channel' - partInit :: Affection sd m prt + -- | Initialize the 'Participant' with an inbound 'Channel' + partInit + :: Channel m -- ^ Inbound 'Channel' the 'Participant' will listen to + -> Affection sd prt -- ^ The constructed and initialized 'Participant' - partListen :: prt -> IO (Maybe m) + -- Get the 'Participant' to listen to its inbound 'Channel' + partListen + :: prt -- ^ The 'Participant' + -> Affection sd (Maybe m) -- ^ The optional 'Message' peeked from the 'Channel' - partEmit :: prt -> m -> IO () + -- Get the 'Participant' to emit a Message on its outbound 'Channel' + partEmit + :: prt -- ^ The 'Participant' + -> m -- ^ The 'Message' to emit + -> Affection sd () + +-- data Envelope = Envelope +-- { envFrom :: (Participant a _) => a +-- , envTo :: (Participant b _) => b +-- , envMessage :: (Message msg) => msg +-- } From 64537fe1c7ad108df0a2805bae2cbd859b040fbb Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 12 Dec 2017 13:12:47 +0100 Subject: [PATCH 174/261] generalize --- src/Affection/MessageBus/Message.hs | 1 + .../MessageBus/Message/WindowMessage.hs | 60 ++++++++--------- src/Affection/Subsystems/AffectionWindow.hs | 64 +++++++++---------- src/Affection/Subsystems/Class.hs | 5 +- 4 files changed, 64 insertions(+), 66 deletions(-) diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index b1d0d46..6712571 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -4,3 +4,4 @@ module Affection.MessageBus.Message import Affection.MessageBus.Message.Class as M import Affection.MessageBus.Message.WindowMessage as M +import Affection.MessageBus.Message.KeyboardMessage as M diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index b9e3727..d410b2e 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -11,79 +11,79 @@ import qualified SDL import Linear (V2(..)) data WindowMessage - = MsgEngineReady Word32 - | MsgWindowShown + -- = MsgEngineReady Word32 + = MsgWindowShow { msgWSWhen :: Word32 , msgWSWindow :: SDL.Window } - | MsgWindowHidden + | MsgWindowHide { msgWHWhen :: Word32 , msgWHWindow :: SDL.Window } - | MsgWindowExposed + | MsgWindowExpose { msgWEWhen :: Word32 , msgWEWindow :: SDL.Window } - | MsgWindowMoved + | MsgWindowMove { msgWMWhen :: Word32 , msgWMWindow :: SDL.Window , msgWMNewPos :: V2 Int32 } - | MsgWindowResized + | MsgWindowResize { msgWRWhen :: Word32 , msgWRWindow :: SDL.Window , msgWRNewSize :: V2 Int32 } - | MsgWindowSizeChanged + | MsgWindowSizeChange { msgWSCWhen :: Word32 , msgWSCWindow :: SDL.Window } - | MsgWindowMinimized + | MsgWindowMinimize { msgWMinWhen :: Word32 , msgWMinWindow :: SDL.Window } - | MsgWindowMaximized + | MsgWindowMaximize { msgWMaxWhen :: Word32 , msgWMaxWindow :: SDL.Window } - | MsgWindowRestored + | MsgWindowRestore { msgWRestWhen :: Word32 , msgWRestWindow :: SDL.Window } - | MsgWindowGainedMouseFocus + | MsgWindowGainMouseFocus { msgWGMFWhen :: Word32 , msgWGMFWindow :: SDL.Window } - | MsgWindowLostMouseFocus + | MsgWindowLoseMouseFocus { msgWLMFWhen :: Word32 , msgWLMFWindow :: SDL.Window } - | MsgWindowGainedKeyboardFocus + | MsgWindowGainKeyboardFocus { msgWGKFWhen :: Word32 , msgWGKFWindow :: SDL.Window } - | MsgWindowLostKeyboardFocus + | MsgWindowLoseKeyboardFocus { msgWLKFWhen :: Word32 , msgWLKFWindow :: SDL.Window } - | MsgWindowClosed + | MsgWindowClose { msgWCWhen :: Word32 , msgWCWindow :: SDL.Window } instance Message WindowMessage where - msgTime (MsgEngineReady t) = t - msgTime (MsgWindowShown t _) = t - msgTime (MsgWindowHidden t _) = t - msgTime (MsgWindowExposed t _) = t - msgTime (MsgWindowMoved t _ _) = t - msgTime (MsgWindowResized t _ _) = t - msgTime (MsgWindowSizeChanged t _) = t - msgTime (MsgWindowMinimized t _) = t - msgTime (MsgWindowMaximized t _) = t - msgTime (MsgWindowRestored t _) = t - msgTime (MsgWindowGainedMouseFocus t _) = t - msgTime (MsgWindowLostMouseFocus t _) = t - msgTime (MsgWindowGainedKeyboardFocus t _) = t - msgTime (MsgWindowLostKeyboardFocus t _) = t - msgTime (MsgWindowClosed t _) = t + -- 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 (MsgWindowMinimize t _) = t + msgTime (MsgWindowMaximize t _) = t + msgTime (MsgWindowRestore t _) = t + msgTime (MsgWindowGainMouseFocus t _) = t + msgTime (MsgWindowLoseMouseFocus t _) = t + msgTime (MsgWindowGainKeyboardFocus t _) = t + msgTime (MsgWindowLoseKeyboardFocus t _) = t + msgTime (MsgWindowClose t _) = t diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 2954ee5..26eb841 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -1,84 +1,80 @@ -{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE AllowAmbiguousTypes #-} module Affection.Subsystems.AffectionWindow where import Affection.MessageBus import Affection.Subsystems.Class import Affection.Types -import Control.Monad.State.Class (gets) import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionWindow m = AffectionWindow - { windowChannel :: Channel m +data AffectionWindow = AffectionWindow + { windowInChannel :: Channel WindowMessage + , windowOutChannel :: Channel WindowMessage } -instance (Message m) => Participant (AffectionWindow m) m where - partChannel = windowChannel +instance Participant AffectionWindow WindowMessage where + partChannel = windowOutChannel - partInit = do - chan <- gets messageChannel :: Affection sd m (Channel m) - nchan <- liftIO $ dupChannel $ chan + partInit ichan = do + ochan <- liftIO $ newBroadcastChannel return $ AffectionWindow - { windowChannel = nchan + { windowOutChannel = ochan + , windowInChannel = ichan } - partListen p = do - let chan = partChannel p - mmsg <- tryPeekChannel chan - return mmsg + partListen p = + liftIO $ tryPeekChannel (windowInChannel p) - partEmit p message = do - let chan = partChannel p - writeChannel chan message + partEmit p message = + liftIO $ writeChannel (windowOutChannel p) message -instance Subsystem (AffectionWindow SystemMessage) SystemMessage where - consumeEvents aw evs = doConsume evs +instance SDLSubsystem AffectionWindow WindowMessage where + consumeSDLEvents aw evs = doConsume evs where + doConsume [] = return [] doConsume (e:es) = case SDL.eventPayload e of SDL.WindowShownEvent (SDL.WindowShownEventData window) -> do - partEmit aw (MsgWindowShown (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowShow (SDL.eventTimestamp e) window) doConsume es SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) -> do - partEmit aw (MsgWindowHidden (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowHide (SDL.eventTimestamp e) window) doConsume es SDL.WindowExposedEvent (SDL.WindowExposedEventData window) -> do - partEmit aw (MsgWindowExposed (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowExpose (SDL.eventTimestamp e) window) doConsume es SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) -> do - partEmit aw (MsgWindowMoved (SDL.eventTimestamp e) window newPos) + partEmit aw (MsgWindowMove (SDL.eventTimestamp e) window newPos) doConsume es SDL.WindowResizedEvent (SDL.WindowResizedEventData window newSize) -> do - partEmit aw (MsgWindowResized (SDL.eventTimestamp e) window newSize) + partEmit aw (MsgWindowResize (SDL.eventTimestamp e) window newSize) doConsume es SDL.WindowSizeChangedEvent (SDL.WindowSizeChangedEventData window) -> do - partEmit aw (MsgWindowSizeChanged (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowSizeChange (SDL.eventTimestamp e) window) doConsume es SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) -> do - partEmit aw (MsgWindowMinimized (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowMinimize (SDL.eventTimestamp e) window) doConsume es SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) -> do - partEmit aw (MsgWindowMaximized (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowMaximize (SDL.eventTimestamp e) window) doConsume es SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) -> do - partEmit aw (MsgWindowRestored (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowRestore (SDL.eventTimestamp e) window) doConsume es SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) -> do - partEmit aw (MsgWindowGainedMouseFocus (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowGainMouseFocus (SDL.eventTimestamp e) window) doConsume es SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) -> do - partEmit aw (MsgWindowLostMouseFocus (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowLoseMouseFocus (SDL.eventTimestamp e) window) doConsume es SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) -> do - partEmit aw (MsgWindowGainedKeyboardFocus (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowGainKeyboardFocus (SDL.eventTimestamp e) window) doConsume es SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) -> do - partEmit aw (MsgWindowLostKeyboardFocus (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowLoseKeyboardFocus (SDL.eventTimestamp e) window) doConsume es SDL.WindowClosedEvent (SDL.WindowClosedEventData window) -> do - partEmit aw (MsgWindowClosed (SDL.eventTimestamp e) window) + partEmit aw (MsgWindowClose (SDL.eventTimestamp e) window) doConsume es _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index e104640..6dc42f4 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -2,9 +2,10 @@ {-# LANGUAGE AllowAmbiguousTypes #-} module Affection.Subsystems.Class where +import Affection.Types import Affection.MessageBus import qualified SDL -class (Message m, Participant s m) => Subsystem s m where - consumeEvents :: s -> [SDL.Event] -> IO [SDL.Event] +class (Message m, Participant s m) => SDLSubsystem s m where + consumeSDLEvents :: s -> [SDL.Event] -> Affection sd [SDL.Event] From 55d863e4cc7620a6c62355306e544ddb815c97a7 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 13 Dec 2017 04:37:16 +0100 Subject: [PATCH 175/261] it builds! --- affection.cabal | 3 + src/Affection/MessageBus/Class.hs | 56 ++++++++++------ src/Affection/MessageBus/Message.hs | 1 + .../MessageBus/Message/KeyboardMessage.hs | 2 +- .../MessageBus/Message/MouseMessage.hs | 40 ++++++++++++ .../MessageBus/Message/WindowMessage.hs | 2 +- src/Affection/MessageBus/Util.hs | 30 +-------- src/Affection/Subsystems/AffectionKeyboard.hs | 23 +++---- src/Affection/Subsystems/AffectionMouse.hs | 64 +++++++++++++++++++ src/Affection/Subsystems/AffectionWindow.hs | 30 ++++----- src/Affection/Types.hs | 3 - 11 files changed, 173 insertions(+), 81 deletions(-) create mode 100644 src/Affection/MessageBus/Message/MouseMessage.hs create mode 100644 src/Affection/Subsystems/AffectionMouse.hs diff --git a/affection.cabal b/affection.cabal index 0f144f5..9055740 100644 --- a/affection.cabal +++ b/affection.cabal @@ -69,9 +69,11 @@ library , Affection.MessageBus.Message.Class , Affection.MessageBus.Message.WindowMessage , Affection.MessageBus.Message.KeyboardMessage + , Affection.MessageBus.Message.MouseMessage , Affection.Subsystems.Class , Affection.Subsystems.AffectionWindow , Affection.Subsystems.AffectionKeyboard + , Affection.Subsystems.AffectionMouse default-extensions: OverloadedStrings -- Modules included in this library but not exported. @@ -100,6 +102,7 @@ library , bytestring , OpenGL , stm + , uuid -- executable example00 -- hs-source-dirs: examples diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index b83e99b..3aa1fea 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,33 +1,49 @@ {-# LANGUAGE MultiParamTypeClasses #-} -module Affection.MessageBus.Class where +{-# LANGUAGE AllowAmbiguousTypes #-} +module Affection.MessageBus.Class + ( Participant(..) + , genUUID + , UUID + ) where import Affection.MessageBus.Message import Affection.Types -class (Message m) => Participant prt m where - -- | get the outbound 'Channel' of the 'Participant' - partChannel - :: prt -- ^ The 'Participant' - -> Channel m -- ^ Outbound 'Channel' +import Control.Monad.IO.Class (liftIO) - -- | Initialize the 'Participant' with an inbound 'Channel' - partInit - :: Channel m -- ^ Inbound 'Channel' the 'Participant' will listen to - -> Affection sd prt -- ^ The constructed and initialized 'Participant' +import Data.UUID +import Data.UUID.V4 - -- Get the 'Participant' to listen to its inbound 'Channel' - partListen - :: prt -- ^ The 'Participant' - -> Affection sd (Maybe m) -- ^ The optional 'Message' peeked from the 'Channel' +import Affection.Logging - -- Get the 'Participant' to emit a Message on its outbound 'Channel' +class (Show m, Message m) => Participant prt m where + -- | Function to get the lsit of subscribers from the participant + partSubscribers + :: prt -- ^ the participant + -> Affection sd [(m -> Affection sd ())] -- ^ List of Subscriber functions + + -- | Subscribe to the 'Participant''s events + partSubscribe + :: prt -- ^ The 'Participant' to subscribe to + -> (m -> IO ()) -- ^ What to do in case of a 'Message' + -- (Subscriber function) + -> Affection sd UUID -- ^ 'UUID' of the registered subscriber Function + + -- | Unsubscribe a Subscriber function from Participant + partUnSubscribe + :: prt -- ^ The 'Participant' to unsubscribe from + -> UUID -- ^ The subscriber function's 'UUID' + -> Affection sd () + + -- | Get the 'Participant' to emit a 'Message' on all of its subscribers partEmit :: prt -- ^ The 'Participant' -> m -- ^ The 'Message' to emit -> Affection sd () + partEmit p m = do + liftIO $ logIO Debug $ "Emitting message: " ++ show m + l <- partSubscribers p + mapM_ ($ m) l --- data Envelope = Envelope --- { envFrom :: (Participant a _) => a --- , envTo :: (Participant b _) => b --- , envMessage :: (Message msg) => msg --- } +genUUID :: Affection sd UUID +genUUID = liftIO $ nextRandom diff --git a/src/Affection/MessageBus/Message.hs b/src/Affection/MessageBus/Message.hs index 6712571..0af45ec 100644 --- a/src/Affection/MessageBus/Message.hs +++ b/src/Affection/MessageBus/Message.hs @@ -5,3 +5,4 @@ module Affection.MessageBus.Message import Affection.MessageBus.Message.Class as M import Affection.MessageBus.Message.WindowMessage as M import Affection.MessageBus.Message.KeyboardMessage as M +import Affection.MessageBus.Message.MouseMessage as M diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs index e783592..e51b712 100644 --- a/src/Affection/MessageBus/Message/KeyboardMessage.hs +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -12,7 +12,7 @@ data KeyboardMessage = MsgKeyboardEvent , msgKbdKeyMotion :: SDL.InputMotion , msgKbdLeyRepeat :: Bool , msgKbdKeysym :: SDL.Keysym - } + } deriving (Show) instance Message KeyboardMessage where msgTime (MsgKeyboardEvent t _ _ _ _) = t diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs new file mode 100644 index 0000000..a3c60d8 --- /dev/null +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -0,0 +1,40 @@ +module Affection.MessageBus.Message.MouseMessage where + +import Affection.MessageBus.Message.Class + +import Data.Word (Word32(..), Word8(..)) +import Data.Int (Int32(..)) + +import qualified SDL + +import Linear (V2(..)) + +data MouseMessage + = MsgMouseMotion + { msgMMWhen :: Word32 + , msgMMWindow :: Maybe SDL.Window + , msgMMWhich :: SDL.MouseDevice + , msgMMState :: [SDL.MouseButton] + , msgMMPos :: V2 Int32 + , msgMMRelMotion :: V2 Int32 + } + | MsgMouseButton + { msgMBWhen :: Word32 + , msgMBWindow :: Maybe SDL.Window + , msgMBWhich :: SDL.MouseDevice + , msgMBButton :: SDL.MouseButton + , msgMBClicks :: Word8 + , msgMBPos :: V2 Int32 + } + | MsgMouseWheel + { msgMWWhen :: Word32 + , msgMWWhindow :: Maybe SDL.Window + , msgMWWhich :: SDL.MouseDevice + , msgMWPos :: V2 Int32 + , msgMWDIrection :: SDL.MouseScrollDirection + } + +instance Message MouseMessage where + msgTime (MsgMouseMotion t _ _ _ _ _) = t + msgTime (MsgMouseButton t _ _ _ _ _) = t + msgTime (MsgMouseWheel t _ _ _ _) = t diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index d410b2e..43c4c46 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE RankNTypes #-} module Affection.MessageBus.Message.WindowMessage where import Affection.MessageBus.Message.Class @@ -70,6 +69,7 @@ data WindowMessage { msgWCWhen :: Word32 , msgWCWindow :: SDL.Window } + deriving (Show) instance Message WindowMessage where -- msgTime (MsgEngineReady t) = t diff --git a/src/Affection/MessageBus/Util.hs b/src/Affection/MessageBus/Util.hs index 341f21b..b2cae3e 100644 --- a/src/Affection/MessageBus/Util.hs +++ b/src/Affection/MessageBus/Util.hs @@ -1,31 +1,3 @@ module Affection.MessageBus.Util where -import Affection.MessageBus.Class -import Affection.MessageBus.Message -import Affection.Types -import Control.Concurrent.STM as STM - --- | Build a new broadcast channel -newBroadcastChannel :: (Message msg) => IO (Channel msg) -newBroadcastChannel = atomically $ Channel <$> newBroadcastTChan - --- | Duplicate a broadcast channel, so it can be accessed by subsystems -dupChannel :: (Message msg) - => Channel msg -- ^ Original 'Channel' - -> IO (Channel msg) -- ^ 'Channel' duplicate -dupChannel (Channel c) = atomically $ Channel <$> dupTChan c - --- | Try to read a 'Channel' wihtout deleting the message. Returns 'Nothing' on --- an empty 'Channel' -tryPeekChannel :: (Message msg) - => Channel msg -- ^ Channel to read from - -> IO (Maybe msg) -- ^ Resulting message (or not) -tryPeekChannel (Channel c) = atomically $ tryPeekTChan c - --- | Write a message to a 'Channel' and thus broadcast it to all connected --- subsystems -writeChannel :: (Message msg) - => Channel msg -- ^ 'Channel' to write to - -> msg -- ^ The Message to emit - -> IO () -writeChannel (Channel c) m = atomically $ writeTChan c m +-- zuru zuru diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index de5ad62..094b559 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -6,26 +6,27 @@ import Affection.Subsystems.Class import Affection.Types import Control.Monad.IO.Class (liftIO) +import Control.Concurrent.STM as STM import qualified SDL data AffectionKeyboard = AffectionKeyboard - { keyboardInChannel :: Channel KeyboardMessage - , keyboardOutChannel :: Channel KeyboardMessage + { keyboardSubscribers :: TVar [(UUID, KeyboardMessage -> IO ())] } instance Participant AffectionKeyboard KeyboardMessage where - partChannel = keyboardOutChannel + partSubscribe p funct = do + uuid <- genUUID + liftIO $ atomically $ modifyTVar' (keyboardSubscribers p) ((uuid, funct) :) + return uuid - partInit ichan = do - ochan <- liftIO $ newBroadcastChannel - return $ AffectionKeyboard ichan ochan + partUnSubscribe p uuid = + liftIO $ atomically $ modifyTVar' (keyboardSubscribers p) + (filter (\(u, _) -> u /= uuid)) - partListen p = - liftIO $ tryPeekChannel (keyboardInChannel p) - - partEmit p m = - liftIO $ writeChannel (keyboardOutChannel p) m + partSubscribers p = do + subTups <- liftIO $ readTVarIO $ keyboardSubscribers p + return $ map snd subTups instance SDLSubsystem AffectionKeyboard KeyboardMessage where consumeSDLEvents ak evs = doConsume evs diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs new file mode 100644 index 0000000..8c9f144 --- /dev/null +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -0,0 +1,64 @@ +module Affection.Subsystems.AffectionMouse where + +import Affection.MessageBus +import Affection.Subsystem.Class +import Affection.Types + +import Control.Monad.IO.Class (liftIO) +import Control.Concurrent.STM + +import qualified SDL + +data AffectionMouse = AffectionMouse + { mouseSubscribers :: TVar (UUID, MouseMessage -> IO ()) + } + +instance Participant AffectionMouse MouseMessage where + partSubscribe p funct = do + uuid <- genUUID + liftIO $ atmoically $ modifyTVar' (mouseSubscribers p) ((uuid, funct) :) + return uuid + + partUnSubscribe p uuid = + liftIO $ atomically $ modifyTVar' (mouseSubscribers p) + (filter (\(u, _) -> u /= uuid)) + + partSubscribers p = + subTups <- liftIO $ readTVarIO $ mouseSubscribers p + return $ map snd subTups + +instance SDLSubsystem AffectionMouse where + consumeSDLEvents am evs = doComsume evs + where + doConsume [] = return [] + doConsume (e:es) = case SDL.EventPayload e of + SDL.MouseMotionEvent dat -> do + partEmit am (MsgMouseMotion + (SDL.eventTimestamp dat) + (SDL.mouseMotionEventWindow dat) + (SDL.mouseMotionEventWhich dat) + (SDL.mouseMotionEventState dat) + (SDL.mouseMotionEventPos dat) + (SDL.mouseMotionEventRelMotion dat) + ) + doComsume es + SDL.MouseButtonEvent dat -> do + partEmit am (MsgMouseButton + (SDL.eventTimestamp e) + (SDL.mouseButtonEventWindow dat) + (SDL.mouseButtonEventWhich dat) + (SDL.mouseButtonEventButton dat) + (SDL.mouseButtonEventClicks dat) + (SDL.mouseButtonEventPos dat) + ) + doConsume es + SDL.MouseWheelEvent dat -> do + partEmit am (MsgMouseWheel + (SDL.eventTimestamp e) + (SDL.mouseWheelEventWindow dat) + (SDL.mouseWheelEvntWhich dat) + (SDL.mouseWheelEventPos dat) + (SDL.mouseWheelEventDirection dat) + ) + doConsume es + _ -> fmap (e :) (doComsume es) diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 26eb841..90b560a 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -1,34 +1,32 @@ {-# LANGUAGE MultiParamTypeClasses #-} module Affection.Subsystems.AffectionWindow where +import Affection.Types import Affection.MessageBus import Affection.Subsystems.Class -import Affection.Types +import Control.Concurrent.STM as STM import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionWindow = AffectionWindow - { windowInChannel :: Channel WindowMessage - , windowOutChannel :: Channel WindowMessage +data AffectionWindow sd = AffectionWindow + { windowSubscribers :: TVar [(UUID, WindowMessage -> Affection sd ())] } instance Participant AffectionWindow WindowMessage where - partChannel = windowOutChannel + partSubscribe p funct = do + uuid <- genUUID + liftIO $ atomically $ modifyTVar' (windowSubscribers p) ((uuid, funct) :) + return uuid - partInit ichan = do - ochan <- liftIO $ newBroadcastChannel - return $ AffectionWindow - { windowOutChannel = ochan - , windowInChannel = ichan - } + partUnSubscribe p uuid = + liftIO $ atomically $ modifyTVar' (windowSubscribers p) + (filter (\(u, _) -> u /= uuid)) - partListen p = - liftIO $ tryPeekChannel (windowInChannel p) - - partEmit p message = - liftIO $ writeChannel (windowOutChannel p) message + partSubscribers p = do + subTups <- liftIO $ readTVarIO $ windowSubscribers p + return $ map snd subTups instance SDLSubsystem AffectionWindow WindowMessage where consumeSDLEvents aw evs = doConsume evs diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 8810fe1..6e89dd7 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -42,7 +42,6 @@ import Data.Map.Strict as M import Control.Monad.IO.Class import Control.Monad.State import qualified Control.Monad.Parallel as MP -import Control.Concurrent.STM as STM import System.Clock (TimeSpec) -- import Control.Monad.Reader @@ -232,5 +231,3 @@ type Angle = Double -- { partStorLatest :: Maybe Particle -- ^ The particle stored last -- , partStorList :: [Particle] -- ^ List of particles in ascending order of remaining lifetime -- } - -newtype (Message msg) => Channel msg = Channel (TChan msg) From 1cbda31499a7c15c71424165395882e3404dfd10 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 13 Dec 2017 05:05:18 +0100 Subject: [PATCH 176/261] fixing things up --- src/Affection/MessageBus/Class.hs | 3 +- .../MessageBus/Message/MouseMessage.hs | 1 + src/Affection/Subsystems/AffectionKeyboard.hs | 9 ++--- src/Affection/Subsystems/AffectionMouse.hs | 34 +++++++++++-------- src/Affection/Subsystems/AffectionWindow.hs | 7 ++-- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 3aa1fea..4ec6b7d 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,5 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE RankNTypes #-} module Affection.MessageBus.Class ( Participant(..) , genUUID @@ -25,7 +26,7 @@ class (Show m, Message m) => Participant prt m where -- | Subscribe to the 'Participant''s events partSubscribe :: prt -- ^ The 'Participant' to subscribe to - -> (m -> IO ()) -- ^ What to do in case of a 'Message' + -> (forall sd. m -> Affection sd ()) -- ^ What to do in case of a 'Message' -- (Subscriber function) -> Affection sd UUID -- ^ 'UUID' of the registered subscriber Function diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index a3c60d8..4ff12b6 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -33,6 +33,7 @@ data MouseMessage , msgMWPos :: V2 Int32 , msgMWDIrection :: SDL.MouseScrollDirection } + deriving (Show) instance Message MouseMessage where msgTime (MsgMouseMotion t _ _ _ _ _) = t diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index 094b559..188099a 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -1,4 +1,5 @@ {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} module Affection.Subsystems.AffectionKeyboard where import Affection.MessageBus @@ -10,11 +11,11 @@ import Control.Concurrent.STM as STM import qualified SDL -data AffectionKeyboard = AffectionKeyboard - { keyboardSubscribers :: TVar [(UUID, KeyboardMessage -> IO ())] +data AffectionKeyboard sd = AffectionKeyboard + { keyboardSubscribers :: forall sd. TVar [(UUID, KeyboardMessage -> Affection sd ())] } -instance Participant AffectionKeyboard KeyboardMessage where +instance Participant (AffectionKeyboard sd) KeyboardMessage where partSubscribe p funct = do uuid <- genUUID liftIO $ atomically $ modifyTVar' (keyboardSubscribers p) ((uuid, funct) :) @@ -28,7 +29,7 @@ instance Participant AffectionKeyboard KeyboardMessage where subTups <- liftIO $ readTVarIO $ keyboardSubscribers p return $ map snd subTups -instance SDLSubsystem AffectionKeyboard KeyboardMessage where +instance SDLSubsystem (AffectionKeyboard sd) KeyboardMessage where consumeSDLEvents ak evs = doConsume evs where doConsume [] = return [] diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index 8c9f144..ff47e06 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -1,47 +1,51 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} module Affection.Subsystems.AffectionMouse where import Affection.MessageBus -import Affection.Subsystem.Class +import Affection.Subsystems.Class import Affection.Types import Control.Monad.IO.Class (liftIO) import Control.Concurrent.STM +import Linear.Affine (unP) + import qualified SDL -data AffectionMouse = AffectionMouse - { mouseSubscribers :: TVar (UUID, MouseMessage -> IO ()) +data AffectionMouse sd = AffectionMouse + { mouseSubscribers :: forall sd. TVar [(UUID, MouseMessage -> Affection sd ())] } -instance Participant AffectionMouse MouseMessage where +instance Participant (AffectionMouse sd) MouseMessage where partSubscribe p funct = do uuid <- genUUID - liftIO $ atmoically $ modifyTVar' (mouseSubscribers p) ((uuid, funct) :) + liftIO $ atomically $ modifyTVar' (mouseSubscribers p) ((uuid, funct) :) return uuid partUnSubscribe p uuid = liftIO $ atomically $ modifyTVar' (mouseSubscribers p) (filter (\(u, _) -> u /= uuid)) - partSubscribers p = + partSubscribers p = do subTups <- liftIO $ readTVarIO $ mouseSubscribers p return $ map snd subTups -instance SDLSubsystem AffectionMouse where - consumeSDLEvents am evs = doComsume evs +instance SDLSubsystem (AffectionMouse sd) MouseMessage where + consumeSDLEvents am evs = doConsume evs where doConsume [] = return [] - doConsume (e:es) = case SDL.EventPayload e of + doConsume (e:es) = case SDL.eventPayload e of SDL.MouseMotionEvent dat -> do partEmit am (MsgMouseMotion - (SDL.eventTimestamp dat) + (SDL.eventTimestamp e) (SDL.mouseMotionEventWindow dat) (SDL.mouseMotionEventWhich dat) (SDL.mouseMotionEventState dat) - (SDL.mouseMotionEventPos dat) + (unP $ SDL.mouseMotionEventPos dat) (SDL.mouseMotionEventRelMotion dat) ) - doComsume es + doConsume es SDL.MouseButtonEvent dat -> do partEmit am (MsgMouseButton (SDL.eventTimestamp e) @@ -49,16 +53,16 @@ instance SDLSubsystem AffectionMouse where (SDL.mouseButtonEventWhich dat) (SDL.mouseButtonEventButton dat) (SDL.mouseButtonEventClicks dat) - (SDL.mouseButtonEventPos dat) + (unP $ SDL.mouseButtonEventPos dat) ) doConsume es SDL.MouseWheelEvent dat -> do partEmit am (MsgMouseWheel (SDL.eventTimestamp e) (SDL.mouseWheelEventWindow dat) - (SDL.mouseWheelEvntWhich dat) + (SDL.mouseWheelEventWhich dat) (SDL.mouseWheelEventPos dat) (SDL.mouseWheelEventDirection dat) ) doConsume es - _ -> fmap (e :) (doComsume es) + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 90b560a..aebe2ee 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -1,4 +1,5 @@ {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} module Affection.Subsystems.AffectionWindow where import Affection.Types @@ -11,10 +12,10 @@ import Control.Monad.IO.Class (liftIO) import qualified SDL data AffectionWindow sd = AffectionWindow - { windowSubscribers :: TVar [(UUID, WindowMessage -> Affection sd ())] + { windowSubscribers :: forall sd. TVar [(UUID, WindowMessage -> Affection sd ())] } -instance Participant AffectionWindow WindowMessage where +instance Participant (AffectionWindow sd) WindowMessage where partSubscribe p funct = do uuid <- genUUID liftIO $ atomically $ modifyTVar' (windowSubscribers p) ((uuid, funct) :) @@ -28,7 +29,7 @@ instance Participant AffectionWindow WindowMessage where subTups <- liftIO $ readTVarIO $ windowSubscribers p return $ map snd subTups -instance SDLSubsystem AffectionWindow WindowMessage where +instance SDLSubsystem (AffectionWindow sd) WindowMessage where consumeSDLEvents aw evs = doConsume evs where doConsume [] = return [] From 78f058db6b46f2e9e9b8b1cef79fdceca1cfbc66 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 13 Dec 2017 15:19:53 +0100 Subject: [PATCH 177/261] still won't work --- affection.cabal | 84 +++++----- examples/example00.hs | 150 ++++++------------ src/Affection.hs | 8 +- src/Affection/MessageBus.hs | 3 + src/Affection/MessageBus/Class.hs | 6 +- src/Affection/MessageBus/Message/Class.hs | 2 +- .../MessageBus/Message/KeyboardMessage.hs | 4 +- .../MessageBus/Message/MouseMessage.hs | 8 +- .../MessageBus/Message/WindowMessage.hs | 31 ++-- src/Affection/Subsystems.hs | 8 + src/Affection/Subsystems/AffectionKeyboard.hs | 28 ++-- src/Affection/Subsystems/AffectionMouse.hs | 68 ++++---- src/Affection/Subsystems/AffectionWindow.hs | 94 +++++------ src/Affection/Subsystems/Class.hs | 2 +- src/Affection/Types.hs | 6 +- 15 files changed, 239 insertions(+), 263 deletions(-) create mode 100644 src/Affection/Subsystems.hs diff --git a/affection.cabal b/affection.cabal index 9055740..00b97da 100644 --- a/affection.cabal +++ b/affection.cabal @@ -70,6 +70,7 @@ library , Affection.MessageBus.Message.WindowMessage , Affection.MessageBus.Message.KeyboardMessage , Affection.MessageBus.Message.MouseMessage + , Affection.Subsystems , Affection.Subsystems.Class , Affection.Subsystems.AffectionWindow , Affection.Subsystems.AffectionKeyboard @@ -104,23 +105,26 @@ library , stm , uuid --- executable example00 --- hs-source-dirs: examples --- main-is: example00.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 example00 + if flag(debug) + cpp-options: -DDEBUG + if flag(warn) + cpp-options: -DWARN + if flag(error) + cpp-options: -DERROR + hs-source-dirs: examples + main-is: example00.hs + ghc-options: -threaded -Wall + default-language: Haskell2010 + default-extensions: OverloadedStrings + if flag(examples) + build-depends: base + , affection + , sdl2 + , stm + else + buildable: False + -- executable example01 -- hs-source-dirs: examples -- main-is: example01.hs @@ -209,26 +213,26 @@ library -- , 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 +-- +-- 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 diff --git a/examples/example00.hs b/examples/example00.hs index def9f9e..9be083c 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -1,117 +1,71 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE RankNTypes #-} import Affection import qualified SDL -import qualified SDL.Raw as Raw -import qualified GEGL as G -import qualified BABL as B -import qualified Data.Map.Strict as M -import Control.Monad (when) +import Control.Concurrent.STM -import Foreign.Storable (peek) -import Foreign.C.Types (CInt(..)) +data StateData = StateData + { sdSubs :: Subsystems + } -import Debug.Trace - --- main :: IO () --- main = withAllAffection $ --- withDefaultWindow "test" $ do --- changeColor $ RGBA 255 255 255 255 --- clear --- present --- liftIO $ delaySec 2 +data Subsystems = Subsystems + { subWindow :: AffectionWindow StateData + , subMouse :: AffectionMouse StateData + , subKeyboard :: AffectionKeyboard StateData + } main :: IO () main = do - conf <- return $ AffectionConfig - { initComponents = All - , windowTitle = "Affection: example00" - , windowConfig = SDL.defaultWindow - , preLoop = return () - , eventLoop = handle - , updateLoop = update - , drawLoop = draw - , loadState = load - , cleanUp = clean - } + logIO Debug "Starting" + let conf = AffectionConfig + { initComponents = All + , windowTitle = "affection: example00" + , windowConfig = SDL.defaultWindow + , initScreenMode = SDL.Windowed + , canvasSize = Nothing + , loadState = load + , preLoop = pre + , eventLoop = handle + , updateLoop = update + , drawLoop = draw + , cleanUp = clean + } withAffection conf -data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - } - -load :: IO UserData +load :: IO StateData load = do - traceM "loading" - root <- G.gegl_node_new - traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ - props $ do - prop "color1" $ G.RGBA 0.4 0.4 0.4 1 - prop "color2" $ G.RGBA 0.6 0.6 0.6 1 - traceM "checkerboard" - over <- G.gegl_node_new_child root G.defaultOverOperation - traceM "over" - text <- G.gegl_node_new_child root $ G.textOperation $ - props $ do - prop "string" ("Hello world!"::String) - prop "color" $ G.RGBA 0 0 1 0.5 - prop "size" (40::Int) - traceM "text" - G.gegl_node_link checkerboard over - G.gegl_node_connect_to text "output" over "aux" - traceM "connections made" - myMap <- return $ M.fromList - [ ("root" , root) - , ("over" , over) - , ("checkerboard", checkerboard) - , ("text" , text) - ] - traceM "loading complete" - return $ UserData - { nodeGraph = myMap - } + empty1 <- newTVarIO [] -- ([] :: [(UUID, WindowMessage -> Affection StateData ())]) + empty2 <- newTVarIO [] -- ([] :: [(UUID, MouseMessage -> Affection StateData ())]) + empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection StateData ())]) + return $ StateData $ Subsystems + (AffectionWindow empty1) + (AffectionMouse empty2) + (AffectionKeyboard empty3) -draw :: Affection UserData () -draw = do - traceM "drawing" - AffectionData{..} <- get - let UserData{..} = userState - liftIO $ SDL.lockSurface drawSurface - pixels <- liftIO $ SDL.surfacePixels drawSurface - let SDL.Surface rawSurfacePtr _ = drawSurface - rawSurface <- liftIO $ peek rawSurfacePtr - pixelFormat <- liftIO $ peek $ Raw.surfaceFormat rawSurface - format <- liftIO $ (B.babl_format $ B.PixelFormat B.RGBA B.CFu8) - SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions drawSurface - let (w, h) = (fromIntegral rw, fromIntegral rh) - liftIO $ G.gegl_node_blit - (nodeGraph M.! "over" :: G.GeglNode) - 1 - (G.GeglRectangle 0 0 w h) - format - pixels - (fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w) - [G.GeglBlitDefault] - liftIO $ SDL.unlockSurface drawSurface - liftIO $ SDL.updateWindowSurface drawWindow +pre :: Affection StateData () +pre = do + sd <- getAffection + _ <- partSubscribe (subKeyboard $ sdSubs sd) exitOnQ + return () -handle :: SDL.EventPayload -> Affection UserData () -handle = const $ return () +exitOnQ :: KeyboardMessage -> Affection StateData () +exitOnQ (MsgKeyboardEvent _ _ _ _ sym) = + case SDL.keysymKeycode sym of + SDL.KeycodeQ -> quit + otherwise -> return () -update :: Double -> Affection UserData () -update sec = do - traceM "updating" - ad <- get - ud@UserData{..} <- getAffection +handle :: [SDL.EventPayload] -> Affection StateData () +handle es = do + (Subsystems a b c) <- sdSubs <$> getAffection + _ <- consumeSDLEvents a es + _ <- consumeSDLEvents b es + _ <- consumeSDLEvents c es + return () - -- sec <- getDelta - traceM $ (show $ 1 / sec) ++ " FPS" - when (elapsedTime ad > 5) $ - put $ ad - { quitEvent = True - } +update _ = return () + +draw = return () -clean :: UserData -> IO () clean _ = return () diff --git a/src/Affection.hs b/src/Affection.hs index 2917ec7..11dec7b 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -24,6 +24,9 @@ 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(..)) @@ -45,7 +48,7 @@ withAffection AffectionConfig{..} = do do renderQuality <- SDL.get SDL.HintRenderScaleQuality when (renderQuality /= SDL.ScaleLinear) $ - putStrLn "Warning: Linear texture filtering not enabled!" + logIO Warn "Linear texture filtering not enabled!" -- construct window window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window @@ -94,7 +97,8 @@ withAffection AffectionConfig{..} = do } -- poll events evs <- preHandleEvents =<< liftIO SDL.pollEvents - mapM_ eventLoop evs + -- mapM_ eventLoop evs + eventLoop evs -- execute user defined update loop unless (pausedTime ad) (updateLoop dt) -- execute user defined draw loop diff --git a/src/Affection/MessageBus.hs b/src/Affection/MessageBus.hs index 28d1bfa..d22edbb 100644 --- a/src/Affection/MessageBus.hs +++ b/src/Affection/MessageBus.hs @@ -1,7 +1,10 @@ module Affection.MessageBus ( module M + , module Msg ) where import Affection.MessageBus.Class as M import Affection.MessageBus.Message as M import Affection.MessageBus.Util as M + +import Affection.MessageBus.Message as Msg diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 4ec6b7d..a27bca3 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE Rank2Types #-} module Affection.MessageBus.Class ( Participant(..) , genUUID @@ -20,13 +20,13 @@ import Affection.Logging class (Show m, Message m) => Participant prt m where -- | Function to get the lsit of subscribers from the participant partSubscribers - :: prt -- ^ the participant + :: prt -- ^ the participant -> Affection sd [(m -> Affection sd ())] -- ^ List of Subscriber functions -- | Subscribe to the 'Participant''s events partSubscribe :: prt -- ^ The 'Participant' to subscribe to - -> (forall sd. m -> Affection sd ()) -- ^ What to do in case of a 'Message' + -> (m -> Affection sd ()) -- ^ What to do in case of a 'Message' -- (Subscriber function) -> Affection sd UUID -- ^ 'UUID' of the registered subscriber Function diff --git a/src/Affection/MessageBus/Message/Class.hs b/src/Affection/MessageBus/Message/Class.hs index b97de31..bcb2651 100644 --- a/src/Affection/MessageBus/Message/Class.hs +++ b/src/Affection/MessageBus/Message/Class.hs @@ -3,4 +3,4 @@ module Affection.MessageBus.Message.Class where import Data.Word (Word32(..)) class Message msg where - msgTime :: msg -> Word32 + msgTime :: msg -> Double diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs index e51b712..5ef859a 100644 --- a/src/Affection/MessageBus/Message/KeyboardMessage.hs +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -2,12 +2,10 @@ module Affection.MessageBus.Message.KeyboardMessage where import Affection.MessageBus.Message.Class -import Data.Word (Word32(..)) - import qualified SDL data KeyboardMessage = MsgKeyboardEvent - { msgKbdWhen :: Word32 + { msgKbdWhen :: Double , msgKbdWindow :: Maybe SDL.Window , msgKbdKeyMotion :: SDL.InputMotion , msgKbdLeyRepeat :: Bool diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index 4ff12b6..5833ae1 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -2,7 +2,7 @@ module Affection.MessageBus.Message.MouseMessage where import Affection.MessageBus.Message.Class -import Data.Word (Word32(..), Word8(..)) +import Data.Word (Word8(..)) import Data.Int (Int32(..)) import qualified SDL @@ -11,7 +11,7 @@ import Linear (V2(..)) data MouseMessage = MsgMouseMotion - { msgMMWhen :: Word32 + { msgMMWhen :: Double , msgMMWindow :: Maybe SDL.Window , msgMMWhich :: SDL.MouseDevice , msgMMState :: [SDL.MouseButton] @@ -19,7 +19,7 @@ data MouseMessage , msgMMRelMotion :: V2 Int32 } | MsgMouseButton - { msgMBWhen :: Word32 + { msgMBWhen :: Double , msgMBWindow :: Maybe SDL.Window , msgMBWhich :: SDL.MouseDevice , msgMBButton :: SDL.MouseButton @@ -27,7 +27,7 @@ data MouseMessage , msgMBPos :: V2 Int32 } | MsgMouseWheel - { msgMWWhen :: Word32 + { msgMWWhen :: Double , msgMWWhindow :: Maybe SDL.Window , msgMWWhich :: SDL.MouseDevice , msgMWPos :: V2 Int32 diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index 43c4c46..d857ed2 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -2,7 +2,6 @@ module Affection.MessageBus.Message.WindowMessage where import Affection.MessageBus.Message.Class -import Data.Word (Word32(..)) import Data.Int (Int32(..)) import qualified SDL @@ -10,63 +9,63 @@ import qualified SDL import Linear (V2(..)) data WindowMessage - -- = MsgEngineReady Word32 + -- = MsgEngineReady Double = MsgWindowShow - { msgWSWhen :: Word32 + { msgWSWhen :: Double , msgWSWindow :: SDL.Window } | MsgWindowHide - { msgWHWhen :: Word32 + { msgWHWhen :: Double , msgWHWindow :: SDL.Window } | MsgWindowExpose - { msgWEWhen :: Word32 + { msgWEWhen :: Double , msgWEWindow :: SDL.Window } | MsgWindowMove - { msgWMWhen :: Word32 + { msgWMWhen :: Double , msgWMWindow :: SDL.Window , msgWMNewPos :: V2 Int32 } | MsgWindowResize - { msgWRWhen :: Word32 + { msgWRWhen :: Double , msgWRWindow :: SDL.Window , msgWRNewSize :: V2 Int32 } | MsgWindowSizeChange - { msgWSCWhen :: Word32 + { msgWSCWhen :: Double , msgWSCWindow :: SDL.Window } | MsgWindowMinimize - { msgWMinWhen :: Word32 + { msgWMinWhen :: Double , msgWMinWindow :: SDL.Window } | MsgWindowMaximize - { msgWMaxWhen :: Word32 + { msgWMaxWhen :: Double , msgWMaxWindow :: SDL.Window } | MsgWindowRestore - { msgWRestWhen :: Word32 + { msgWRestWhen :: Double , msgWRestWindow :: SDL.Window } | MsgWindowGainMouseFocus - { msgWGMFWhen :: Word32 + { msgWGMFWhen :: Double , msgWGMFWindow :: SDL.Window } | MsgWindowLoseMouseFocus - { msgWLMFWhen :: Word32 + { msgWLMFWhen :: Double , msgWLMFWindow :: SDL.Window } | MsgWindowGainKeyboardFocus - { msgWGKFWhen :: Word32 + { msgWGKFWhen :: Double , msgWGKFWindow :: SDL.Window } | MsgWindowLoseKeyboardFocus - { msgWLKFWhen :: Word32 + { msgWLKFWhen :: Double , msgWLKFWindow :: SDL.Window } | MsgWindowClose - { msgWCWhen :: Word32 + { msgWCWhen :: Double , msgWCWindow :: SDL.Window } deriving (Show) diff --git a/src/Affection/Subsystems.hs b/src/Affection/Subsystems.hs new file mode 100644 index 0000000..ce9929f --- /dev/null +++ b/src/Affection/Subsystems.hs @@ -0,0 +1,8 @@ +module Affection.Subsystems + ( module S + ) where + +import Affection.Subsystems.Class as S +import Affection.Subsystems.AffectionKeyboard as S +import Affection.Subsystems.AffectionWindow as S +import Affection.Subsystems.AffectionMouse as S diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index 188099a..dc62706 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -5,6 +5,7 @@ module Affection.Subsystems.AffectionKeyboard where import Affection.MessageBus import Affection.Subsystems.Class import Affection.Types +import Affection.Util import Control.Monad.IO.Class (liftIO) import Control.Concurrent.STM as STM @@ -30,17 +31,18 @@ instance Participant (AffectionKeyboard sd) KeyboardMessage where return $ map snd subTups instance SDLSubsystem (AffectionKeyboard sd) KeyboardMessage where - consumeSDLEvents ak evs = doConsume evs + consumeSDLEvents ak eps = doConsume eps where - doConsume [] = return [] - doConsume (e:es) = case SDL.eventPayload e of - SDL.KeyboardEvent dat -> do - partEmit ak (MsgKeyboardEvent - (SDL.eventTimestamp e) - (SDL.keyboardEventWindow dat) - (SDL.keyboardEventKeyMotion dat) - (SDL.keyboardEventRepeat dat) - (SDL.keyboardEventKeysym dat) - ) - doConsume es - _ -> fmap (e :) (doConsume es) + doConsume (e:es) = do + ts <- getElapsedTime + case e of + SDL.KeyboardEvent dat -> do + partEmit ak (MsgKeyboardEvent + ts + (SDL.keyboardEventWindow dat) + (SDL.keyboardEventKeyMotion dat) + (SDL.keyboardEventRepeat dat) + (SDL.keyboardEventKeysym dat) + ) + doConsume es + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index ff47e06..35689c1 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -5,6 +5,7 @@ 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 @@ -32,37 +33,38 @@ instance Participant (AffectionMouse sd) MouseMessage where return $ map snd subTups instance SDLSubsystem (AffectionMouse sd) MouseMessage where - consumeSDLEvents am evs = doConsume evs + consumeSDLEvents am eps = doConsume eps where - doConsume [] = return [] - doConsume (e:es) = case SDL.eventPayload e of - SDL.MouseMotionEvent dat -> do - partEmit am (MsgMouseMotion - (SDL.eventTimestamp e) - (SDL.mouseMotionEventWindow dat) - (SDL.mouseMotionEventWhich dat) - (SDL.mouseMotionEventState dat) - (unP $ SDL.mouseMotionEventPos dat) - (SDL.mouseMotionEventRelMotion dat) - ) - doConsume es - SDL.MouseButtonEvent dat -> do - partEmit am (MsgMouseButton - (SDL.eventTimestamp e) - (SDL.mouseButtonEventWindow dat) - (SDL.mouseButtonEventWhich dat) - (SDL.mouseButtonEventButton dat) - (SDL.mouseButtonEventClicks dat) - (unP $ SDL.mouseButtonEventPos dat) - ) - doConsume es - SDL.MouseWheelEvent dat -> do - partEmit am (MsgMouseWheel - (SDL.eventTimestamp e) - (SDL.mouseWheelEventWindow dat) - (SDL.mouseWheelEventWhich dat) - (SDL.mouseWheelEventPos dat) - (SDL.mouseWheelEventDirection dat) - ) - doConsume es - _ -> fmap (e :) (doConsume es) + doConsume (e:es) = do + ts <- getElapsedTime + case e of + SDL.MouseMotionEvent dat -> do + partEmit am (MsgMouseMotion + ts + (SDL.mouseMotionEventWindow dat) + (SDL.mouseMotionEventWhich dat) + (SDL.mouseMotionEventState dat) + (unP $ SDL.mouseMotionEventPos dat) + (SDL.mouseMotionEventRelMotion dat) + ) + doConsume es + SDL.MouseButtonEvent dat -> do + partEmit am (MsgMouseButton + ts + (SDL.mouseButtonEventWindow dat) + (SDL.mouseButtonEventWhich dat) + (SDL.mouseButtonEventButton dat) + (SDL.mouseButtonEventClicks dat) + (unP $ SDL.mouseButtonEventPos dat) + ) + doConsume es + SDL.MouseWheelEvent dat -> do + partEmit am (MsgMouseWheel + ts + (SDL.mouseWheelEventWindow dat) + (SDL.mouseWheelEventWhich dat) + (SDL.mouseWheelEventPos dat) + (SDL.mouseWheelEventDirection dat) + ) + doConsume es + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index aebe2ee..9b432c8 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -3,6 +3,7 @@ module Affection.Subsystems.AffectionWindow where import Affection.Types +import Affection.Util import Affection.MessageBus import Affection.Subsystems.Class @@ -30,50 +31,51 @@ instance Participant (AffectionWindow sd) WindowMessage where return $ map snd subTups instance SDLSubsystem (AffectionWindow sd) WindowMessage where - consumeSDLEvents aw evs = doConsume evs + consumeSDLEvents aw eps = doConsume eps where - doConsume [] = return [] - doConsume (e:es) = case SDL.eventPayload e of - SDL.WindowShownEvent (SDL.WindowShownEventData window) -> do - partEmit aw (MsgWindowShow (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) -> do - partEmit aw (MsgWindowHide (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowExposedEvent (SDL.WindowExposedEventData window) -> do - partEmit aw (MsgWindowExpose (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) -> do - partEmit aw (MsgWindowMove (SDL.eventTimestamp e) window newPos) - doConsume es - SDL.WindowResizedEvent (SDL.WindowResizedEventData window newSize) -> do - partEmit aw (MsgWindowResize (SDL.eventTimestamp e) window newSize) - doConsume es - SDL.WindowSizeChangedEvent (SDL.WindowSizeChangedEventData window) -> do - partEmit aw (MsgWindowSizeChange (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) -> do - partEmit aw (MsgWindowMinimize (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) -> do - partEmit aw (MsgWindowMaximize (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) -> do - partEmit aw (MsgWindowRestore (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) -> do - partEmit aw (MsgWindowGainMouseFocus (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) -> do - partEmit aw (MsgWindowLoseMouseFocus (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) -> do - partEmit aw (MsgWindowGainKeyboardFocus (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) -> do - partEmit aw (MsgWindowLoseKeyboardFocus (SDL.eventTimestamp e) window) - doConsume es - SDL.WindowClosedEvent (SDL.WindowClosedEventData window) -> do - partEmit aw (MsgWindowClose (SDL.eventTimestamp e) window) - doConsume es - _ -> fmap (e :) (doConsume es) + doConsume (e:es) = do + ts <- getElapsedTime + case e of + SDL.WindowShownEvent (SDL.WindowShownEventData window) -> do + partEmit aw (MsgWindowShow ts window) + doConsume es + SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) -> do + partEmit aw (MsgWindowHide ts window) + doConsume es + SDL.WindowExposedEvent (SDL.WindowExposedEventData window) -> do + partEmit aw (MsgWindowExpose ts window) + doConsume es + SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) -> do + partEmit aw (MsgWindowMove ts window newPos) + doConsume es + 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) + doConsume es + SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) -> do + partEmit aw (MsgWindowMinimize ts window) + doConsume es + SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) -> do + partEmit aw (MsgWindowMaximize ts window) + doConsume es + SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) -> do + partEmit aw (MsgWindowRestore ts window) + doConsume es + SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) -> do + partEmit aw (MsgWindowGainMouseFocus ts window) + doConsume es + SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) -> do + partEmit aw (MsgWindowLoseMouseFocus ts window) + doConsume es + SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) -> do + partEmit aw (MsgWindowGainKeyboardFocus ts window) + doConsume es + SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) -> do + partEmit aw (MsgWindowLoseKeyboardFocus ts window) + doConsume es + SDL.WindowClosedEvent (SDL.WindowClosedEventData window) -> do + partEmit aw (MsgWindowClose ts window) + doConsume es + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index 6dc42f4..f9f3d27 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -8,4 +8,4 @@ import Affection.MessageBus import qualified SDL class (Message m, Participant s m) => SDLSubsystem s m where - consumeSDLEvents :: s -> [SDL.Event] -> Affection sd [SDL.Event] + consumeSDLEvents :: s -> [SDL.EventPayload] -> Affection sd [SDL.EventPayload] diff --git a/src/Affection/Types.hs b/src/Affection/Types.hs index 6e89dd7..350c285 100644 --- a/src/Affection/Types.hs +++ b/src/Affection/Types.hs @@ -64,16 +64,16 @@ data AffectionConfig us = AffectionConfig -- ^ 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 () + , 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. - , loadState :: IO us - -- ^ Provide your own load function to create this data. , cleanUp :: us -> IO () -- ^ Provide your own finisher function to clean your data. } From 3199d034016811df3e1093f45079f9eb58947a39 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 13 Dec 2017 15:53:51 +0100 Subject: [PATCH 178/261] blabla --- src/Affection/MessageBus/Class.hs | 16 ++++++++-------- src/Affection/Subsystems/AffectionKeyboard.hs | 14 +++++++------- src/Affection/Subsystems/AffectionWindow.hs | 9 +++++---- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index a27bca3..aaac555 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE Rank2Types #-} +{-# LANGUAGE RankNTypes #-} module Affection.MessageBus.Class ( Participant(..) , genUUID @@ -20,31 +20,31 @@ import Affection.Logging class (Show m, Message m) => Participant prt m where -- | Function to get the lsit of subscribers from the participant partSubscribers - :: prt -- ^ the participant - -> Affection sd [(m -> Affection sd ())] -- ^ List of Subscriber functions + :: prt -- ^ the participant + -> forall us. Affection us [(m -> Affection us ())] -- ^ List of Subscriber functions -- | Subscribe to the 'Participant''s events partSubscribe :: prt -- ^ The 'Participant' to subscribe to - -> (m -> Affection sd ()) -- ^ What to do in case of a 'Message' + -> (forall us. m -> Affection us ()) -- ^ What to do in case of a 'Message' -- (Subscriber function) - -> Affection sd UUID -- ^ 'UUID' of the registered subscriber Function + -> Affection us UUID -- ^ 'UUID' of the registered subscriber Function -- | Unsubscribe a Subscriber function from Participant partUnSubscribe :: prt -- ^ The 'Participant' to unsubscribe from -> UUID -- ^ The subscriber function's 'UUID' - -> Affection sd () + -> Affection us () -- | Get the 'Participant' to emit a 'Message' on all of its subscribers partEmit :: prt -- ^ The 'Participant' -> m -- ^ The 'Message' to emit - -> Affection sd () + -> Affection us () partEmit p m = do liftIO $ logIO Debug $ "Emitting message: " ++ show m l <- partSubscribers p mapM_ ($ m) l -genUUID :: Affection sd UUID +genUUID :: Affection us UUID genUUID = liftIO $ nextRandom diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index dc62706..6f822d2 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -2,21 +2,21 @@ {-# LANGUAGE RankNTypes #-} module Affection.Subsystems.AffectionKeyboard where -import Affection.MessageBus -import Affection.Subsystems.Class import Affection.Types import Affection.Util +import Affection.MessageBus +import Affection.Subsystems.Class -import Control.Monad.IO.Class (liftIO) import Control.Concurrent.STM as STM +import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionKeyboard sd = AffectionKeyboard - { keyboardSubscribers :: forall sd. TVar [(UUID, KeyboardMessage -> Affection sd ())] +data AffectionKeyboard us = AffectionKeyboard + { keyboardSubscribers :: forall us. TVar [(UUID, KeyboardMessage -> Affection us ())] } -instance Participant (AffectionKeyboard sd) KeyboardMessage where +instance Participant (AffectionKeyboard us) KeyboardMessage where partSubscribe p funct = do uuid <- genUUID liftIO $ atomically $ modifyTVar' (keyboardSubscribers p) ((uuid, funct) :) @@ -30,7 +30,7 @@ instance Participant (AffectionKeyboard sd) KeyboardMessage where subTups <- liftIO $ readTVarIO $ keyboardSubscribers p return $ map snd subTups -instance SDLSubsystem (AffectionKeyboard sd) KeyboardMessage where +instance SDLSubsystem (AffectionKeyboard us) KeyboardMessage where consumeSDLEvents ak eps = doConsume eps where doConsume (e:es) = do diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 9b432c8..1fe4775 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -1,5 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE FlexibleInstances #-} module Affection.Subsystems.AffectionWindow where import Affection.Types @@ -12,11 +13,11 @@ import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionWindow sd = AffectionWindow - { windowSubscribers :: forall sd. TVar [(UUID, WindowMessage -> Affection sd ())] +data AffectionWindow us = AffectionWindow + { windowSubscribers :: forall us. TVar [(UUID, WindowMessage -> Affection us ())] } -instance Participant (AffectionWindow sd) WindowMessage where +instance Participant (AffectionWindow us) WindowMessage where partSubscribe p funct = do uuid <- genUUID liftIO $ atomically $ modifyTVar' (windowSubscribers p) ((uuid, funct) :) @@ -30,7 +31,7 @@ instance Participant (AffectionWindow sd) WindowMessage where subTups <- liftIO $ readTVarIO $ windowSubscribers p return $ map snd subTups -instance SDLSubsystem (AffectionWindow sd) WindowMessage where +instance SDLSubsystem (AffectionWindow us) WindowMessage where consumeSDLEvents aw eps = doConsume eps where doConsume (e:es) = do From f12e62d938f0e63d152634543e02780070448321 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 15 Dec 2017 17:48:12 +0100 Subject: [PATCH 179/261] event system works! --- examples/example00.hs | 71 +++++++++- src/Affection/MessageBus/Class.hs | 37 ++++-- src/Affection/Subsystems/AffectionKeyboard.hs | 57 ++++---- src/Affection/Subsystems/AffectionMouse.hs | 97 ++++++-------- src/Affection/Subsystems/AffectionWindow.hs | 123 ++++++++---------- src/Affection/Subsystems/Class.hs | 4 +- 6 files changed, 209 insertions(+), 180 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index 9be083c..00d5c69 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -1,21 +1,74 @@ {-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE MultiParamTypeClasses #-} import Affection import qualified SDL import Control.Concurrent.STM +import Control.Monad.IO.Class (liftIO) data StateData = StateData { sdSubs :: Subsystems } data Subsystems = Subsystems - { subWindow :: AffectionWindow StateData - , subMouse :: AffectionMouse StateData - , subKeyboard :: AffectionKeyboard StateData + { subWindow :: Window + , subMouse :: Mouse + , subKeyboard :: Keyboard } +data Window = Window (TVar [(UUID, WindowMessage -> Affection StateData ())]) +data Mouse = Mouse (TVar [(UUID, MouseMessage -> Affection StateData ())]) +data 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 uuid + + partUnSubscribe (Window t) 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 uuid + + partUnSubscribe (Mouse t) 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 uuid + + partUnSubscribe (Keyboard t) uuid = + liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) + +instance SDLSubsystem Keyboard StateData where + consumeSDLEvents = consumeSDLKeyboardEvents + main :: IO () main = do logIO Debug "Starting" @@ -23,6 +76,10 @@ main = do { 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 @@ -40,9 +97,9 @@ load = do empty2 <- newTVarIO [] -- ([] :: [(UUID, MouseMessage -> Affection StateData ())]) empty3 <- newTVarIO [] -- ([] :: [(UUID, KeyboardMessage -> Affection StateData ())]) return $ StateData $ Subsystems - (AffectionWindow empty1) - (AffectionMouse empty2) - (AffectionKeyboard empty3) + (Window empty1) + (Mouse empty2) + (Keyboard empty3) pre :: Affection StateData () pre = do diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index aaac555..4530278 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE RankNTypes #-} module Affection.MessageBus.Class ( Participant(..) , genUUID @@ -17,34 +17,45 @@ import Data.UUID.V4 import Affection.Logging -class (Show m, Message m) => Participant prt m where - -- | Function to get the lsit of subscribers from the participant +class (Show m, Message m) => Participant prt m us where + + -- | Function to get the list of subscribers from the participant partSubscribers - :: prt -- ^ the participant - -> forall us. Affection us [(m -> Affection us ())] -- ^ List of Subscriber functions + :: prt + -- ^ the 'Participant''s subscriber storage + -> Affection us [(m -> Affection us ())] + -- ^ List of Subscriber functions -- | Subscribe to the 'Participant''s events partSubscribe - :: prt -- ^ The 'Participant' to subscribe to - -> (forall us. m -> Affection us ()) -- ^ What to do in case of a 'Message' - -- (Subscriber function) - -> Affection us UUID -- ^ 'UUID' of the registered subscriber Function + :: prt + -- ^ The 'Participant''s subscriber storage + -> (m -> Affection us ()) + -- ^ What to do in case of a 'Message' + -- (Subscriber function) + -> Affection us UUID + -- ^ 'UUID' of the registered subscriber Function -- | Unsubscribe a Subscriber function from Participant partUnSubscribe - :: prt -- ^ The 'Participant' to unsubscribe from - -> UUID -- ^ The subscriber function's 'UUID' + :: prt + -- ^ The 'Participant''s subscriber storage to unsubscribe from + -> UUID + -- ^ The subscriber function's 'UUID' -> Affection us () -- | Get the 'Participant' to emit a 'Message' on all of its subscribers partEmit - :: prt -- ^ The 'Participant' - -> m -- ^ The 'Message' to emit + :: prt + -- ^ The 'Participant''s subscriber storage + -> m + -- ^ The 'Message' to emit -> Affection us () partEmit p m = do liftIO $ logIO Debug $ "Emitting message: " ++ show m l <- partSubscribers p mapM_ ($ m) l +-- | Helper function to generate new 'UUID's genUUID :: Affection us UUID genUUID = liftIO $ nextRandom diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index 6f822d2..4d19ea4 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -1,5 +1,5 @@ {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE FlexibleContexts #-} module Affection.Subsystems.AffectionKeyboard where import Affection.Types @@ -12,37 +12,24 @@ import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionKeyboard us = AffectionKeyboard - { keyboardSubscribers :: forall us. TVar [(UUID, KeyboardMessage -> Affection us ())] - } - -instance Participant (AffectionKeyboard us) KeyboardMessage where - partSubscribe p funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' (keyboardSubscribers p) ((uuid, funct) :) - return uuid - - partUnSubscribe p uuid = - liftIO $ atomically $ modifyTVar' (keyboardSubscribers p) - (filter (\(u, _) -> u /= uuid)) - - partSubscribers p = do - subTups <- liftIO $ readTVarIO $ keyboardSubscribers p - return $ map snd subTups - -instance SDLSubsystem (AffectionKeyboard us) KeyboardMessage where - consumeSDLEvents ak eps = doConsume eps - where - doConsume (e:es) = do - ts <- getElapsedTime - case e of - SDL.KeyboardEvent dat -> do - partEmit ak (MsgKeyboardEvent - ts - (SDL.keyboardEventWindow dat) - (SDL.keyboardEventKeyMotion dat) - (SDL.keyboardEventRepeat dat) - (SDL.keyboardEventKeysym dat) - ) - doConsume es - _ -> fmap (e :) (doConsume es) +consumeSDLKeyboardEvents + :: (Participant ak KeyboardMessage us) + => ak + -> [SDL.EventPayload] + -> Affection us [SDL.EventPayload] +consumeSDLKeyboardEvents ak eps = doConsume eps + where + doConsume [] = return [] + doConsume (e:es) = do + ts <- getElapsedTime + case e of + SDL.KeyboardEvent dat -> do + partEmit ak (MsgKeyboardEvent + ts + (SDL.keyboardEventWindow dat) + (SDL.keyboardEventKeyMotion dat) + (SDL.keyboardEventRepeat dat) + (SDL.keyboardEventKeysym dat) + ) + doConsume es + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index 35689c1..a4e83f9 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -1,5 +1,5 @@ {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE FlexibleContexts #-} module Affection.Subsystems.AffectionMouse where import Affection.MessageBus @@ -14,57 +14,44 @@ import Linear.Affine (unP) import qualified SDL -data AffectionMouse sd = AffectionMouse - { mouseSubscribers :: forall sd. TVar [(UUID, MouseMessage -> Affection sd ())] - } - -instance Participant (AffectionMouse sd) MouseMessage where - partSubscribe p funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' (mouseSubscribers p) ((uuid, funct) :) - return uuid - - partUnSubscribe p uuid = - liftIO $ atomically $ modifyTVar' (mouseSubscribers p) - (filter (\(u, _) -> u /= uuid)) - - partSubscribers p = do - subTups <- liftIO $ readTVarIO $ mouseSubscribers p - return $ map snd subTups - -instance SDLSubsystem (AffectionMouse sd) MouseMessage where - consumeSDLEvents am eps = doConsume eps - where - doConsume (e:es) = do - ts <- getElapsedTime - case e of - SDL.MouseMotionEvent dat -> do - partEmit am (MsgMouseMotion - ts - (SDL.mouseMotionEventWindow dat) - (SDL.mouseMotionEventWhich dat) - (SDL.mouseMotionEventState dat) - (unP $ SDL.mouseMotionEventPos dat) - (SDL.mouseMotionEventRelMotion dat) - ) - doConsume es - SDL.MouseButtonEvent dat -> do - partEmit am (MsgMouseButton - ts - (SDL.mouseButtonEventWindow dat) - (SDL.mouseButtonEventWhich dat) - (SDL.mouseButtonEventButton dat) - (SDL.mouseButtonEventClicks dat) - (unP $ SDL.mouseButtonEventPos dat) - ) - doConsume es - SDL.MouseWheelEvent dat -> do - partEmit am (MsgMouseWheel - ts - (SDL.mouseWheelEventWindow dat) - (SDL.mouseWheelEventWhich dat) - (SDL.mouseWheelEventPos dat) - (SDL.mouseWheelEventDirection dat) - ) - doConsume es - _ -> fmap (e :) (doConsume es) +consumeSDLMouseEvents + :: (Participant am MouseMessage us) + => am + -> [SDL.EventPayload] + -> Affection us [SDL.EventPayload] +consumeSDLMouseEvents am eps = doConsume eps + where + doConsume [] = return [] + doConsume (e:es) = do + ts <- getElapsedTime + case e of + SDL.MouseMotionEvent dat -> do + partEmit am (MsgMouseMotion + ts + (SDL.mouseMotionEventWindow dat) + (SDL.mouseMotionEventWhich dat) + (SDL.mouseMotionEventState dat) + (unP $ SDL.mouseMotionEventPos dat) + (SDL.mouseMotionEventRelMotion dat) + ) + doConsume es + SDL.MouseButtonEvent dat -> do + partEmit am (MsgMouseButton + ts + (SDL.mouseButtonEventWindow dat) + (SDL.mouseButtonEventWhich dat) + (SDL.mouseButtonEventButton dat) + (SDL.mouseButtonEventClicks dat) + (unP $ SDL.mouseButtonEventPos dat) + ) + doConsume es + SDL.MouseWheelEvent dat -> do + partEmit am (MsgMouseWheel + ts + (SDL.mouseWheelEventWindow dat) + (SDL.mouseWheelEventWhich dat) + (SDL.mouseWheelEventPos dat) + (SDL.mouseWheelEventDirection dat) + ) + doConsume es + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 1fe4775..0dea121 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -1,6 +1,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FlexibleContexts #-} module Affection.Subsystems.AffectionWindow where import Affection.Types @@ -13,70 +13,57 @@ import Control.Monad.IO.Class (liftIO) import qualified SDL -data AffectionWindow us = AffectionWindow - { windowSubscribers :: forall us. TVar [(UUID, WindowMessage -> Affection us ())] - } - -instance Participant (AffectionWindow us) WindowMessage where - partSubscribe p funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' (windowSubscribers p) ((uuid, funct) :) - return uuid - - partUnSubscribe p uuid = - liftIO $ atomically $ modifyTVar' (windowSubscribers p) - (filter (\(u, _) -> u /= uuid)) - - partSubscribers p = do - subTups <- liftIO $ readTVarIO $ windowSubscribers p - return $ map snd subTups - -instance SDLSubsystem (AffectionWindow us) WindowMessage where - consumeSDLEvents aw eps = doConsume eps - where - doConsume (e:es) = do - ts <- getElapsedTime - case e of - SDL.WindowShownEvent (SDL.WindowShownEventData window) -> do - partEmit aw (MsgWindowShow ts window) - doConsume es - SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) -> do - partEmit aw (MsgWindowHide ts window) - doConsume es - SDL.WindowExposedEvent (SDL.WindowExposedEventData window) -> do - partEmit aw (MsgWindowExpose ts window) - doConsume es - SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) -> do - partEmit aw (MsgWindowMove ts window newPos) - doConsume es - 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) - doConsume es - SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) -> do - partEmit aw (MsgWindowMinimize ts window) - doConsume es - SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) -> do - partEmit aw (MsgWindowMaximize ts window) - doConsume es - SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) -> do - partEmit aw (MsgWindowRestore ts window) - doConsume es - SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) -> do - partEmit aw (MsgWindowGainMouseFocus ts window) - doConsume es - SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) -> do - partEmit aw (MsgWindowLoseMouseFocus ts window) - doConsume es - SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) -> do - partEmit aw (MsgWindowGainKeyboardFocus ts window) - doConsume es - SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) -> do - partEmit aw (MsgWindowLoseKeyboardFocus ts window) - doConsume es - SDL.WindowClosedEvent (SDL.WindowClosedEventData window) -> do - partEmit aw (MsgWindowClose ts window) - doConsume es - _ -> fmap (e :) (doConsume es) +consumeSDLWindowEvents + :: (Participant aw WindowMessage us) + => aw + -> [SDL.EventPayload] + -> Affection us [SDL.EventPayload] +consumeSDLWindowEvents aw eps = doConsume eps + where + doConsume [] = return [] + doConsume (e:es) = do + ts <- getElapsedTime + case e of + SDL.WindowShownEvent (SDL.WindowShownEventData window) -> do + partEmit aw (MsgWindowShow ts window) + doConsume es + SDL.WindowHiddenEvent (SDL.WindowHiddenEventData window) -> do + partEmit aw (MsgWindowHide ts window) + doConsume es + SDL.WindowExposedEvent (SDL.WindowExposedEventData window) -> do + partEmit aw (MsgWindowExpose ts window) + doConsume es + SDL.WindowMovedEvent (SDL.WindowMovedEventData window (SDL.P newPos)) -> do + partEmit aw (MsgWindowMove ts window newPos) + doConsume es + 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) + doConsume es + SDL.WindowMinimizedEvent (SDL.WindowMinimizedEventData window) -> do + partEmit aw (MsgWindowMinimize ts window) + doConsume es + SDL.WindowMaximizedEvent (SDL.WindowMaximizedEventData window) -> do + partEmit aw (MsgWindowMaximize ts window) + doConsume es + SDL.WindowRestoredEvent (SDL.WindowRestoredEventData window) -> do + partEmit aw (MsgWindowRestore ts window) + doConsume es + SDL.WindowGainedMouseFocusEvent (SDL.WindowGainedMouseFocusEventData window) -> do + partEmit aw (MsgWindowGainMouseFocus ts window) + doConsume es + SDL.WindowLostMouseFocusEvent (SDL.WindowLostMouseFocusEventData window) -> do + partEmit aw (MsgWindowLoseMouseFocus ts window) + doConsume es + SDL.WindowGainedKeyboardFocusEvent (SDL.WindowGainedKeyboardFocusEventData window) -> do + partEmit aw (MsgWindowGainKeyboardFocus ts window) + doConsume es + SDL.WindowLostKeyboardFocusEvent (SDL.WindowLostKeyboardFocusEventData window) -> do + partEmit aw (MsgWindowLoseKeyboardFocus ts window) + doConsume es + SDL.WindowClosedEvent (SDL.WindowClosedEventData window) -> do + partEmit aw (MsgWindowClose ts window) + doConsume es + _ -> fmap (e :) (doConsume es) diff --git a/src/Affection/Subsystems/Class.hs b/src/Affection/Subsystems/Class.hs index f9f3d27..7c52f8e 100644 --- a/src/Affection/Subsystems/Class.hs +++ b/src/Affection/Subsystems/Class.hs @@ -7,5 +7,5 @@ import Affection.MessageBus import qualified SDL -class (Message m, Participant s m) => SDLSubsystem s m where - consumeSDLEvents :: s -> [SDL.EventPayload] -> Affection sd [SDL.EventPayload] +class SDLSubsystem s us where + consumeSDLEvents :: s -> [SDL.EventPayload] -> Affection us [SDL.EventPayload] From 295c4b333c76877e191970d90aec05e32eb95724 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 15 Dec 2017 17:58:55 +0100 Subject: [PATCH 180/261] eta reductions --- examples/example00.hs | 6 ++++-- src/Affection/Subsystems/AffectionKeyboard.hs | 2 +- src/Affection/Subsystems/AffectionMouse.hs | 2 +- src/Affection/Subsystems/AffectionWindow.hs | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index 00d5c69..7cdcf83 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -110,8 +110,10 @@ pre = do exitOnQ :: KeyboardMessage -> Affection StateData () exitOnQ (MsgKeyboardEvent _ _ _ _ sym) = case SDL.keysymKeycode sym of - SDL.KeycodeQ -> quit - otherwise -> return () + SDL.KeycodeQ -> do + liftIO $ logIO Debug "Yo dog I heard..." + quit + _ -> return () handle :: [SDL.EventPayload] -> Affection StateData () handle es = do diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index 4d19ea4..3df62fd 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -17,7 +17,7 @@ consumeSDLKeyboardEvents => ak -> [SDL.EventPayload] -> Affection us [SDL.EventPayload] -consumeSDLKeyboardEvents ak eps = doConsume eps +consumeSDLKeyboardEvents ak = doConsume where doConsume [] = return [] doConsume (e:es) = do diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index a4e83f9..dcbcf3a 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -19,7 +19,7 @@ consumeSDLMouseEvents => am -> [SDL.EventPayload] -> Affection us [SDL.EventPayload] -consumeSDLMouseEvents am eps = doConsume eps +consumeSDLMouseEvents am = doConsume where doConsume [] = return [] doConsume (e:es) = do diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 0dea121..6aadf0d 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -18,7 +18,7 @@ consumeSDLWindowEvents => aw -> [SDL.EventPayload] -> Affection us [SDL.EventPayload] -consumeSDLWindowEvents aw eps = doConsume eps +consumeSDLWindowEvents aw = doConsume where doConsume [] = return [] doConsume (e:es) = do From 22e08e3f9b2a6da7d0ce277b3ffeebc2b7b48f6e Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 15 Dec 2017 18:01:05 +0100 Subject: [PATCH 181/261] brackets --- src/Affection.hs | 2 +- src/Affection/Actor.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 11dec7b..6852a4d 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -52,7 +52,7 @@ withAffection AffectionConfig{..} = do -- construct window window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window - context <- SDL.glCreateContext(window) + context <- SDL.glCreateContext windw let SDL.V2 (CInt rw) (CInt rh) = SDL.windowInitialSize windowConfig (w, h) = case canvasSize of Just (cw, ch) -> (cw, ch) diff --git a/src/Affection/Actor.hs b/src/Affection/Actor.hs index cff094f..0f98598 100644 --- a/src/Affection/Actor.hs +++ b/src/Affection/Actor.hs @@ -48,7 +48,7 @@ updateProperties ps act@Actor{..} = applyProperties :: (Show a, Ord a) => Actor a -> Affection us () applyProperties Actor{..} = - MP.mapM_ (\(ActorProperty{..}) -> + MP.mapM_ (\ActorProperty{..} -> maybe (return ()) (\m -> liftIO $ G.gegl_node_set (actorNodes M.! fst m) $ G.Operation "" $ (G.Property (snd m) apValue) : [] From 004579eeddee5c6c9a03b7f4a12cebcdb822cc7c Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 15 Dec 2017 18:02:14 +0100 Subject: [PATCH 182/261] unused pragma --- examples/example00.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/example00.hs b/examples/example00.hs index 7cdcf83..a53b441 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE MultiParamTypeClasses #-} import Affection From ca817955853e15558b4a8af931b75d7baf0a4bea Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 15 Dec 2017 18:12:16 +0100 Subject: [PATCH 183/261] linted --- examples/example00.hs | 34 ++++++++++++++----------------- src/Affection.hs | 4 ++-- src/Affection/MessageBus/Class.hs | 4 ++-- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index a53b441..d49eb94 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -6,7 +6,7 @@ import qualified SDL import Control.Concurrent.STM import Control.Monad.IO.Class (liftIO) -data StateData = StateData +newtype StateData = StateData { sdSubs :: Subsystems } @@ -16,19 +16,16 @@ data Subsystems = Subsystems , subKeyboard :: Keyboard } -data Window = Window (TVar [(UUID, WindowMessage -> Affection StateData ())]) -data Mouse = Mouse (TVar [(UUID, MouseMessage -> Affection StateData ())]) -data Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection StateData ())]) +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 + subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Window t) funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return uuid + partSubscribe (Window t) = generalSubscribe t partUnSubscribe (Window t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) @@ -38,13 +35,10 @@ instance SDLSubsystem Window StateData where instance Participant Mouse MouseMessage StateData where partSubscribers (Mouse t) = do - subTups <- liftIO $ readTVarIO $ t + subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Mouse t) funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return uuid + partSubscribe (Mouse t) = generalSubscribe t partUnSubscribe (Mouse t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) @@ -54,13 +48,10 @@ instance SDLSubsystem Mouse StateData where instance Participant Keyboard KeyboardMessage StateData where partSubscribers (Keyboard t) = do - subTups <- liftIO $ readTVarIO $ t + subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Keyboard t) funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return uuid + partSubscribe (Keyboard t) = generalSubscribe t partUnSubscribe (Keyboard t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) @@ -68,6 +59,11 @@ instance Participant Keyboard KeyboardMessage StateData where instance SDLSubsystem Keyboard StateData where consumeSDLEvents = consumeSDLKeyboardEvents +generalSubscribe t funct = do + uuid <- genUUID + liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) + return uuid + main :: IO () main = do logIO Debug "Starting" diff --git a/src/Affection.hs b/src/Affection.hs index 6852a4d..4988362 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -52,7 +52,7 @@ withAffection AffectionConfig{..} = do -- construct window window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window - context <- SDL.glCreateContext windw + context <- SDL.glCreateContext window let SDL.V2 (CInt rw) (CInt rh) = SDL.windowInitialSize windowConfig (w, h) = case canvasSize of Just (cw, ch) -> (cw, ch) @@ -104,7 +104,7 @@ withAffection AffectionConfig{..} = do -- execute user defined draw loop liftIO $ GL.clear [ColorBuffer, DepthBuffer] drawLoop - liftIO $ flush + liftIO flush -- handle all new draw requests ad2 <- get -- actual drawing diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 4530278..a715970 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -23,7 +23,7 @@ class (Show m, Message m) => Participant prt m us where partSubscribers :: prt -- ^ the 'Participant''s subscriber storage - -> Affection us [(m -> Affection us ())] + -> Affection us [m -> Affection us ()] -- ^ List of Subscriber functions -- | Subscribe to the 'Participant''s events @@ -58,4 +58,4 @@ class (Show m, Message m) => Participant prt m us where -- | Helper function to generate new 'UUID's genUUID :: Affection us UUID -genUUID = liftIO $ nextRandom +genUUID = liftIO nextRandom From 39632ce05cbc7a7d4a8f9efa5a4db07dc8baf952 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 15 Dec 2017 18:20:04 +0100 Subject: [PATCH 184/261] typo --- src/Affection/MessageBus/Message/KeyboardMessage.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs index 5ef859a..6b90b0f 100644 --- a/src/Affection/MessageBus/Message/KeyboardMessage.hs +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -8,7 +8,7 @@ data KeyboardMessage = MsgKeyboardEvent { msgKbdWhen :: Double , msgKbdWindow :: Maybe SDL.Window , msgKbdKeyMotion :: SDL.InputMotion - , msgKbdLeyRepeat :: Bool + , msgKbdKeyRepeat :: Bool , msgKbdKeysym :: SDL.Keysym } deriving (Show) From 54018ff71ee3f5ecacdcbf2bbe5a39c5dfd466dc Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 16 Dec 2017 11:57:58 +0100 Subject: [PATCH 185/261] close window on close (duh) --- examples/example00.hs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/example00.hs b/examples/example00.hs index d49eb94..e57e5f7 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -100,6 +100,7 @@ pre :: Affection StateData () pre = do sd <- getAffection _ <- partSubscribe (subKeyboard $ sdSubs sd) exitOnQ + _ <- partSubscribe (subWindow $ sdSubs sd) exitOnWindowClose return () exitOnQ :: KeyboardMessage -> Affection StateData () @@ -110,6 +111,14 @@ exitOnQ (MsgKeyboardEvent _ _ _ _ sym) = 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 From 714bef562c52995087869876ab7233bfc110d59a Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 16 Dec 2017 19:33:43 +0100 Subject: [PATCH 186/261] changed input of state machine --- src/Affection/StateMachine.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Affection/StateMachine.hs b/src/Affection/StateMachine.hs index 067ae78..abbe3d9 100644 --- a/src/Affection/StateMachine.hs +++ b/src/Affection/StateMachine.hs @@ -9,6 +9,6 @@ import qualified SDL class StateMachine a us where smLoad :: a -> Affection us () smUpdate :: a -> Double -> Affection us () - smEvent :: a -> SDL.EventPayload -> Affection us () + smEvent :: a -> [SDL.EventPayload] -> Affection us () smDraw :: a -> Affection us () smClean :: a -> Affection us () From 40d877087879340fdd3022dfc2c6e6d2db4637f6 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 16 Dec 2017 19:40:22 +0100 Subject: [PATCH 187/261] version bump --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index 00b97da..888305d 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.6 +version: 0.0.0.7 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL and GEGL. From e75735bbf6a8a1393dfef007e0c472614818916f Mon Sep 17 00:00:00 2001 From: nek0 Date: Sat, 16 Dec 2017 23:48:45 +0100 Subject: [PATCH 188/261] clear more buffers --- src/Affection.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Affection.hs b/src/Affection.hs index 4988362..3f913cc 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -102,7 +102,7 @@ withAffection AffectionConfig{..} = do -- execute user defined update loop unless (pausedTime ad) (updateLoop dt) -- execute user defined draw loop - liftIO $ GL.clear [ColorBuffer, DepthBuffer] + liftIO $ GL.clear [ColorBuffer, DepthBuffer, StencilBuffer] drawLoop liftIO flush -- handle all new draw requests From 13991556dc8aa60df768634c6c49171bacce74ff Mon Sep 17 00:00:00 2001 From: nek0 Date: Tue, 19 Dec 2017 21:53:47 +0100 Subject: [PATCH 189/261] logging and more beautiful fullscreen toggle --- src/Affection.hs | 9 +++++++++ src/Affection/Util.hs | 12 +++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Affection.hs b/src/Affection.hs index 3f913cc..7efabd7 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -36,6 +36,8 @@ withAffection :: AffectionConfig us -- ^ Configuration of the Game and its engine. -> IO () withAffection AffectionConfig{..} = do + liftIO $ logIO Debug "Affection starting" + liftIO $ logIO Debug "Initializing SDL" -- intialiaze SDL case initComponents of All -> @@ -50,6 +52,7 @@ withAffection AffectionConfig{..} = do when (renderQuality /= SDL.ScaleLinear) $ logIO Warn "Linear texture filtering not enabled!" -- construct window + liftIO $ logIO Debug "Creating Window" window <- SDL.createWindow windowTitle windowConfig SDL.showWindow window context <- SDL.glCreateContext window @@ -59,8 +62,10 @@ withAffection AffectionConfig{..} = do Nothing -> (fromIntegral rw, fromIntegral rh) SDL.setWindowMode window initScreenMode SDL.swapInterval $= SDL.SynchronizedUpdates + liftIO $ logIO Debug "Getting Time" -- get current time execTime <- getTime Monotonic + liftIO $ logIO Debug "Loading initial data container" initContainer <- (\x -> AffectionData { quitEvent = False , userState = x @@ -76,6 +81,7 @@ withAffection AffectionConfig{..} = do , pausedTime = False }) <$> loadState (_, nState) <- runStateT ( A.runState $ do + liftIO $ logIO Debug "Starting Loop" preLoop whileM_ (do current <- get @@ -118,6 +124,9 @@ withAffection AffectionConfig{..} = do ) ) ) initContainer + liftIO $ logIO Debug "Loop ended. Cleaning" cleanUp $ userState nState + liftIO $ logIO Debug "Destroying Window" SDL.destroyWindow window SDL.quit + liftIO $ logIO Debug "This is the end" diff --git a/src/Affection/Util.hs b/src/Affection/Util.hs index 9cf794a..879aa3b 100644 --- a/src/Affection/Util.hs +++ b/src/Affection/Util.hs @@ -2,6 +2,7 @@ module Affection.Util where import Affection.Types +import Affection.Logging import qualified SDL @@ -49,16 +50,21 @@ 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 ad <- get newMode <- case screenMode ad of SDL.Windowed -> do - SDL.setWindowMode (drawWindow ad) SDL.Fullscreen - return SDL.Fullscreen - SDL.Fullscreen -> do + SDL.setWindowMode (drawWindow ad) SDL.FullscreenDesktop + return SDL.FullscreenDesktop + SDL.FullscreenDesktop -> do SDL.setWindowMode (drawWindow ad) SDL.Windowed return SDL.Windowed + x -> do + liftIO $ logIO Warn ("Unknown Screen mode: " ++ show x) + return x now <- liftIO $ getTime Monotonic put ad { sysTime = now From ea90f8f9cd5b74aa44f9df9239529f99e510a70c Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 20 Dec 2017 02:00:48 +0100 Subject: [PATCH 190/261] rebuilding participant system --- src/Affection/MessageBus/Class.hs | 17 +++++++++++------ .../MessageBus/Message/KeyboardMessage.hs | 17 ++++++++++------- .../MessageBus/Message/MouseMessage.hs | 1 + .../MessageBus/Message/WindowMessage.hs | 1 + 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index a715970..df2b958 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -1,10 +1,13 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ExistentialQuantification #-} module Affection.MessageBus.Class ( Participant(..) , genUUID , UUID + , MsgId(..) ) where import Affection.MessageBus.Message @@ -17,30 +20,30 @@ import Data.UUID.V4 import Affection.Logging -class (Show m, Message m) => Participant prt m us where +class (Message msg, Show msg) => Participant prt msg us where -- | Function to get the list of subscribers from the participant partSubscribers :: prt -- ^ the 'Participant''s subscriber storage - -> Affection us [m -> Affection us ()] + -> Affection us [msg -> Affection us ()] -- ^ List of Subscriber functions -- | Subscribe to the 'Participant''s events partSubscribe :: prt -- ^ The 'Participant''s subscriber storage - -> (m -> Affection us ()) + -> (msg -> Affection us ()) -- ^ What to do in case of a 'Message' -- (Subscriber function) - -> Affection us UUID + -> Affection us (MsgId msg) -- ^ 'UUID' of the registered subscriber Function -- | Unsubscribe a Subscriber function from Participant partUnSubscribe :: prt -- ^ The 'Participant''s subscriber storage to unsubscribe from - -> UUID + -> (MsgId msg) -- ^ The subscriber function's 'UUID' -> Affection us () @@ -48,7 +51,7 @@ class (Show m, Message m) => Participant prt m us where partEmit :: prt -- ^ The 'Participant''s subscriber storage - -> m + -> msg -- ^ The 'Message' to emit -> Affection us () partEmit p m = do @@ -59,3 +62,5 @@ class (Show m, Message m) => Participant prt m us where -- | Helper function to generate new 'UUID's genUUID :: Affection us UUID genUUID = liftIO nextRandom + +data MsgId msg = (Message msg, Show msg) => MsgId UUID msg diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs index 6b90b0f..ae4b277 100644 --- a/src/Affection/MessageBus/Message/KeyboardMessage.hs +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -4,13 +4,16 @@ import Affection.MessageBus.Message.Class import qualified SDL -data KeyboardMessage = MsgKeyboardEvent - { msgKbdWhen :: Double - , msgKbdWindow :: Maybe SDL.Window - , msgKbdKeyMotion :: SDL.InputMotion - , msgKbdKeyRepeat :: Bool - , msgKbdKeysym :: SDL.Keysym - } deriving (Show) +data KeyboardMessage + = MsgKeyboardEvent + { msgKbdWhen :: Double + , msgKbdWindow :: Maybe SDL.Window + , msgKbdKeyMotion :: SDL.InputMotion + , msgKbdKeyRepeat :: Bool + , msgKbdKeysym :: SDL.Keysym + } + | MsgKeyboardEmptyEvent + deriving (Show) instance Message KeyboardMessage where msgTime (MsgKeyboardEvent t _ _ _ _) = t diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index 5833ae1..a28676a 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -33,6 +33,7 @@ data MouseMessage , msgMWPos :: V2 Int32 , msgMWDIrection :: SDL.MouseScrollDirection } + | MsgMouseEmptyMessage deriving (Show) instance Message MouseMessage where diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index d857ed2..410ac39 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -68,6 +68,7 @@ data WindowMessage { msgWCWhen :: Double , msgWCWindow :: SDL.Window } + | MsgWindowEmptyEvent deriving (Show) instance Message WindowMessage where From 09f9ac07aececfa9f5f25b4ca54362e16b906b7f Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 22 Dec 2017 06:28:58 +0100 Subject: [PATCH 191/261] adding verbose logging level --- affection.cabal | 7 +++++++ src/Affection/Logging.hs | 21 ++++++++++++++------- src/Affection/MessageBus/Class.hs | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/affection.cabal b/affection.cabal index 888305d..2257745 100644 --- a/affection.cabal +++ b/affection.cabal @@ -30,6 +30,11 @@ source-repository head type: git location: https://github.com/nek0/affection +flag verbse + description: Enable verbose debug messages + default: False + manual: True + flag debug description: Enable debug messages default: False @@ -50,6 +55,8 @@ flag examples default: False library + if flag(verbose) + cpp-options: -DVERBOSE if flag(debug) cpp-options: -DDEBUG if flag(warn) diff --git a/src/Affection/Logging.hs b/src/Affection/Logging.hs index 0cf3032..b450774 100644 --- a/src/Affection/Logging.hs +++ b/src/Affection/Logging.hs @@ -4,30 +4,37 @@ module Affection.Logging where import Debug.Trace data LogLevel - = Debug + = Verbose + | Debug | Warn | Error log :: LogLevel -> String -> a -> a -#if defined(DEBUG) +#if defined(VERBOSE) +log Verbose s = trace ("VERBOSE: " ++ s) +#endif +#if defined(DEBUG) || defined(VERBOSE) log Debug s = trace ("DEBUG: " ++ s) #endif -#if defined(WARN) || defined(DEBUG) +#if defined(WARN) || defined(DEBUG) || defined(VERBOSE) log Warn s = trace ("WARN: " ++ s) #endif -#if defined(ERROR) || defined(WARN) || defined(DEBUG) +#if defined(ERROR) || defined(WARN) || defined(DEBUG) || defined(VERBOSE) log Error s = trace ("ERROR: " ++ s) #endif log _ _ = id logIO :: LogLevel -> String -> IO () -#if defined(DEBUG) +#if defined(VERBOSE) +logIO Verbose s = traceIO ("VERBOSE: " ++ s) +#endif +#if defined(DEBUG) || defined(VERBOSE) logIO Debug s = traceIO ("DEBUG: " ++ s) #endif -#if defined(WARN) || defined(DEBUG) +#if defined(WARN) || defined(DEBUG) || defined(VERBOSE) logIO Warn s = traceIO ("WARN: " ++ s) #endif -#if defined(ERROR) || defined(WARN) || defined(DEBUG) +#if defined(ERROR) || defined(WARN) || defined(DEBUG) || defined(VERBOSE) logIO Error s = traceIO ("ERROR: " ++ s) #endif logIO _ _ = return () diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index df2b958..66e1815 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -55,7 +55,7 @@ class (Message msg, Show msg) => Participant prt msg us where -- ^ The 'Message' to emit -> Affection us () partEmit p m = do - liftIO $ logIO Debug $ "Emitting message: " ++ show m + liftIO $ logIO Verbose $ "Emitting message: " ++ show m l <- partSubscribers p mapM_ ($ m) l From 07d53ed1dedda4b87faf2c4336c06fdedfcf6182 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 22 Dec 2017 06:29:15 +0100 Subject: [PATCH 192/261] I'll stay in OpenGL for now --- src/Affection.hs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Affection.hs b/src/Affection.hs index 7efabd7..d89caec 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -55,6 +55,32 @@ withAffection AffectionConfig{..} = do 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 context <- SDL.glCreateContext window let SDL.V2 (CInt rw) (CInt rh) = SDL.windowInitialSize windowConfig (w, h) = case canvasSize of @@ -115,6 +141,8 @@ withAffection AffectionConfig{..} = do ad2 <- get -- actual drawing SDL.glSwapWindow window + -- SDL.copy renderer texture Nothing Nothing + -- SDL.present renderer -- save new time ad3 <- get when (sysTime ad == sysTime ad3) ( From ecd1943a39636cd612ee89bad7aa39323d7164e6 Mon Sep 17 00:00:00 2001 From: nek0 Date: Fri, 22 Dec 2017 06:30:24 +0100 Subject: [PATCH 193/261] typo --- affection.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/affection.cabal b/affection.cabal index 2257745..13c6e21 100644 --- a/affection.cabal +++ b/affection.cabal @@ -30,7 +30,7 @@ source-repository head type: git location: https://github.com/nek0/affection -flag verbse +flag verbose description: Enable verbose debug messages default: False manual: True From e8fe302208597e20c15062af08d82ed061ed8f8b Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 24 Dec 2017 00:39:52 +0100 Subject: [PATCH 194/261] new version --- affection.cabal | 15 +++++------ examples/example00.hs | 26 +++++++++++-------- src/Affection.hs | 2 +- .../MessageBus/Message/MouseMessage.hs | 2 +- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/affection.cabal b/affection.cabal index 13c6e21..a290eb8 100644 --- a/affection.cabal +++ b/affection.cabal @@ -9,7 +9,7 @@ name: affection version: 0.0.0.7 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine - written in Haskell using SDL and GEGL. + written in Haskell using SDL. This Engine is still work in progress and even minor version bumps may contain breaking api changes. homepage: https://github.com/nek0/affection#readme @@ -19,6 +19,7 @@ author: nek0 maintainer: nek0@chelnok.de category: Game build-type: Simple +extra-source-files: ChangeLog.md -- Extra files to be distributed with the package, such as examples or a -- README. @@ -97,7 +98,7 @@ library default-language: Haskell2010 ghc-options: -Wall -- Other library packages from which modules are imported. - build-depends: base >=4.9 + build-depends: base >=4.9 && < 5 , sdl2 , linear , text @@ -112,20 +113,16 @@ library , stm , uuid +-- This example shows the message system. only makes sense when compiling with +-- verbose flag. executable example00 - if flag(debug) - cpp-options: -DDEBUG - if flag(warn) - cpp-options: -DWARN - if flag(error) - cpp-options: -DERROR hs-source-dirs: examples main-is: example00.hs ghc-options: -threaded -Wall default-language: Haskell2010 default-extensions: OverloadedStrings if flag(examples) - build-depends: base + build-depends: base >=4.9 && < 5 , affection , sdl2 , stm diff --git a/examples/example00.hs b/examples/example00.hs index e57e5f7..8d12f6f 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -25,9 +25,12 @@ instance Participant Window WindowMessage StateData where subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Window t) = generalSubscribe t + partSubscribe (Window t) funct = do + uuid <- genUUID + liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) + return $ MsgId uuid MsgWindowEmptyEvent - partUnSubscribe (Window t) uuid = + partUnSubscribe (Window t) (MsgId uuid _) = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) instance SDLSubsystem Window StateData where @@ -38,9 +41,12 @@ instance Participant Mouse MouseMessage StateData where subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Mouse t) = generalSubscribe t + partSubscribe (Mouse t) funct = do + uuid <- genUUID + liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) + return $ MsgId uuid MsgMouseEmptyEvent - partUnSubscribe (Mouse t) uuid = + partUnSubscribe (Mouse t) (MsgId uuid _) = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) instance SDLSubsystem Mouse StateData where @@ -51,19 +57,17 @@ instance Participant Keyboard KeyboardMessage StateData where subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Keyboard t) = generalSubscribe t + partSubscribe (Keyboard t) funct = do + uuid <- genUUID + liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) + return $ MsgId uuid MsgKeyboardEmptyEvent - partUnSubscribe (Keyboard t) uuid = + partUnSubscribe (Keyboard t) (MsgId uuid _) = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) instance SDLSubsystem Keyboard StateData where consumeSDLEvents = consumeSDLKeyboardEvents -generalSubscribe t funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return uuid - main :: IO () main = do logIO Debug "Starting" diff --git a/src/Affection.hs b/src/Affection.hs index d89caec..aa842a5 100644 --- a/src/Affection.hs +++ b/src/Affection.hs @@ -87,7 +87,7 @@ withAffection AffectionConfig{..} = do 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 diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index a28676a..ba56ae2 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -33,7 +33,7 @@ data MouseMessage , msgMWPos :: V2 Int32 , msgMWDIrection :: SDL.MouseScrollDirection } - | MsgMouseEmptyMessage + | MsgMouseEmptyEvent deriving (Show) instance Message MouseMessage where From 1700734aa4d14dad94ec5ff1b07f81a7f413eea3 Mon Sep 17 00:00:00 2001 From: nek0 Date: Sun, 24 Dec 2017 00:40:23 +0100 Subject: [PATCH 195/261] amends --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ChangeLog.md diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..dd649b3 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,5 @@ +# Revision history for affection + +## 0.0.0.7 -- 2017-12-23 + +* Release with working scaffold for engine subsystems. From 616cea5f9086c443d7af302158f5f33412334101 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 27 Dec 2017 17:33:31 +0100 Subject: [PATCH 196/261] better messages with type families --- src/Affection/MessageBus/Class.hs | 17 +++++++++-------- .../MessageBus/Message/KeyboardMessage.hs | 1 - .../MessageBus/Message/MouseMessage.hs | 1 - .../MessageBus/Message/WindowMessage.hs | 1 - src/Affection/Subsystems/AffectionKeyboard.hs | 4 +++- src/Affection/Subsystems/AffectionMouse.hs | 7 ++++++- src/Affection/Subsystems/AffectionWindow.hs | 9 ++++++++- 7 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/Affection/MessageBus/Class.hs b/src/Affection/MessageBus/Class.hs index 66e1815..9be0fb4 100644 --- a/src/Affection/MessageBus/Class.hs +++ b/src/Affection/MessageBus/Class.hs @@ -7,7 +7,7 @@ module Affection.MessageBus.Class ( Participant(..) , genUUID , UUID - , MsgId(..) + -- , MsgId(..) ) where import Affection.MessageBus.Message @@ -20,30 +20,31 @@ import Data.UUID.V4 import Affection.Logging -class (Message msg, Show msg) => Participant prt msg us where +class (Message (Mesg prt us), Show (Mesg prt us)) => Participant prt us where + 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 +52,7 @@ class (Message msg, Show msg) => Participant prt msg us where partEmit :: prt -- ^ The 'Participant''s subscriber storage - -> msg + -> Mesg prt us -- ^ The 'Message' to emit -> Affection us () partEmit p m = do @@ -63,4 +64,4 @@ class (Message msg, Show msg) => Participant prt msg us where genUUID :: Affection us UUID genUUID = liftIO nextRandom -data MsgId msg = (Message msg, Show msg) => MsgId UUID msg +-- data MsgId msg = (Message msg, Show msg) => MsgId UUID msg diff --git a/src/Affection/MessageBus/Message/KeyboardMessage.hs b/src/Affection/MessageBus/Message/KeyboardMessage.hs index ae4b277..c2af0f1 100644 --- a/src/Affection/MessageBus/Message/KeyboardMessage.hs +++ b/src/Affection/MessageBus/Message/KeyboardMessage.hs @@ -12,7 +12,6 @@ data KeyboardMessage , msgKbdKeyRepeat :: Bool , msgKbdKeysym :: SDL.Keysym } - | MsgKeyboardEmptyEvent deriving (Show) instance Message KeyboardMessage where diff --git a/src/Affection/MessageBus/Message/MouseMessage.hs b/src/Affection/MessageBus/Message/MouseMessage.hs index ba56ae2..5833ae1 100644 --- a/src/Affection/MessageBus/Message/MouseMessage.hs +++ b/src/Affection/MessageBus/Message/MouseMessage.hs @@ -33,7 +33,6 @@ data MouseMessage , msgMWPos :: V2 Int32 , msgMWDIrection :: SDL.MouseScrollDirection } - | MsgMouseEmptyEvent deriving (Show) instance Message MouseMessage where diff --git a/src/Affection/MessageBus/Message/WindowMessage.hs b/src/Affection/MessageBus/Message/WindowMessage.hs index 410ac39..d857ed2 100644 --- a/src/Affection/MessageBus/Message/WindowMessage.hs +++ b/src/Affection/MessageBus/Message/WindowMessage.hs @@ -68,7 +68,6 @@ data WindowMessage { msgWCWhen :: Double , msgWCWindow :: SDL.Window } - | MsgWindowEmptyEvent deriving (Show) instance Message WindowMessage where diff --git a/src/Affection/Subsystems/AffectionKeyboard.hs b/src/Affection/Subsystems/AffectionKeyboard.hs index 3df62fd..d73ec1e 100644 --- a/src/Affection/Subsystems/AffectionKeyboard.hs +++ b/src/Affection/Subsystems/AffectionKeyboard.hs @@ -1,5 +1,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} module Affection.Subsystems.AffectionKeyboard where import Affection.Types @@ -13,7 +15,7 @@ import Control.Monad.IO.Class (liftIO) import qualified SDL consumeSDLKeyboardEvents - :: (Participant ak KeyboardMessage us) + :: forall ak us. (Participant ak us, Mesg ak us ~ KeyboardMessage) => ak -> [SDL.EventPayload] -> Affection us [SDL.EventPayload] diff --git a/src/Affection/Subsystems/AffectionMouse.hs b/src/Affection/Subsystems/AffectionMouse.hs index dcbcf3a..7ff73c8 100644 --- a/src/Affection/Subsystems/AffectionMouse.hs +++ b/src/Affection/Subsystems/AffectionMouse.hs @@ -1,5 +1,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} module Affection.Subsystems.AffectionMouse where import Affection.MessageBus @@ -15,12 +17,15 @@ import Linear.Affine (unP) import qualified SDL consumeSDLMouseEvents - :: (Participant am MouseMessage us) + :: forall am us. (Participant am us, Mesg am us ~ MouseMessage) => am -> [SDL.EventPayload] -> Affection us [SDL.EventPayload] consumeSDLMouseEvents am = doConsume where + doConsume + :: [SDL.EventPayload] + -> Affection us [SDL.EventPayload] doConsume [] = return [] doConsume (e:es) = do ts <- getElapsedTime diff --git a/src/Affection/Subsystems/AffectionWindow.hs b/src/Affection/Subsystems/AffectionWindow.hs index 6aadf0d..6e28ff6 100644 --- a/src/Affection/Subsystems/AffectionWindow.hs +++ b/src/Affection/Subsystems/AffectionWindow.hs @@ -1,6 +1,9 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE AllowAmbiguousTypes #-} module Affection.Subsystems.AffectionWindow where import Affection.Types @@ -14,12 +17,16 @@ import Control.Monad.IO.Class (liftIO) import qualified SDL consumeSDLWindowEvents - :: (Participant aw WindowMessage us) + :: forall aw us. (Participant aw us, Mesg aw us ~ WindowMessage) => aw -> [SDL.EventPayload] -> Affection us [SDL.EventPayload] consumeSDLWindowEvents aw = doConsume where + doConsume + :: (Mesg aw us ~ WindowMessage) + => [SDL.EventPayload] + -> Affection us [SDL.EventPayload] doConsume [] = return [] doConsume (e:es) = do ts <- getElapsedTime From a1ab48009fe0d307d30f80f3dcc8a318b23dd9cf Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 27 Dec 2017 18:43:13 +0100 Subject: [PATCH 197/261] version bump --- affection.cabal | 2 +- examples/example00.hs | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/affection.cabal b/affection.cabal index a290eb8..03853c7 100644 --- a/affection.cabal +++ b/affection.cabal @@ -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.7 +version: 0.0.0.8 synopsis: A simple Game Engine using SDL description: This package contains Affection, a simple game engine written in Haskell using SDL. diff --git a/examples/example00.hs b/examples/example00.hs index 8d12f6f..a8bf470 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -1,4 +1,5 @@ {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeFamilies #-} import Affection import qualified SDL @@ -20,7 +21,9 @@ 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 +instance Participant Window StateData where + type Mesg Window StateData = WindowMessage + partSubscribers (Window t) = do subTups <- liftIO $ readTVarIO t return $ map snd subTups @@ -28,15 +31,17 @@ instance Participant Window WindowMessage StateData where partSubscribe (Window t) funct = do uuid <- genUUID liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return $ MsgId uuid MsgWindowEmptyEvent + return uuid - partUnSubscribe (Window t) (MsgId uuid _) = + partUnSubscribe (Window t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) instance SDLSubsystem Window StateData where consumeSDLEvents = consumeSDLWindowEvents -instance Participant Mouse MouseMessage StateData where +instance Participant Mouse StateData where + type Mesg Mouse StateData = MouseMessage + partSubscribers (Mouse t) = do subTups <- liftIO $ readTVarIO t return $ map snd subTups @@ -44,15 +49,17 @@ instance Participant Mouse MouseMessage StateData where partSubscribe (Mouse t) funct = do uuid <- genUUID liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return $ MsgId uuid MsgMouseEmptyEvent + return uuid - partUnSubscribe (Mouse t) (MsgId uuid _) = + partUnSubscribe (Mouse t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) instance SDLSubsystem Mouse StateData where consumeSDLEvents = consumeSDLMouseEvents -instance Participant Keyboard KeyboardMessage StateData where +instance Participant Keyboard StateData where + type Mesg Keyboard StateData = KeyboardMessage + partSubscribers (Keyboard t) = do subTups <- liftIO $ readTVarIO t return $ map snd subTups @@ -60,9 +67,9 @@ instance Participant Keyboard KeyboardMessage StateData where partSubscribe (Keyboard t) funct = do uuid <- genUUID liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return $ MsgId uuid MsgKeyboardEmptyEvent + return uuid - partUnSubscribe (Keyboard t) (MsgId uuid _) = + partUnSubscribe (Keyboard t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) instance SDLSubsystem Keyboard StateData where From 11bb7b69e5ca282013e501b818ccf07df9929479 Mon Sep 17 00:00:00 2001 From: nek0 Date: Wed, 27 Dec 2017 20:27:38 +0100 Subject: [PATCH 198/261] drop repetitive code --- examples/example00.hs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/example00.hs b/examples/example00.hs index a8bf470..c9090b8 100644 --- a/examples/example00.hs +++ b/examples/example00.hs @@ -21,6 +21,15 @@ newtype Window = Window (TVar [(UUID, WindowMessage -> Affection StateData ())]) newtype Mouse = Mouse (TVar [(UUID, MouseMessage -> Affection StateData ())]) newtype Keyboard = Keyboard (TVar [(UUID, KeyboardMessage -> Affection StateData ())]) +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 + instance Participant Window StateData where type Mesg Window StateData = WindowMessage @@ -28,10 +37,7 @@ instance Participant Window StateData where subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Window t) funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return uuid + partSubscribe (Window t) = generalSubscribe t partUnSubscribe (Window t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) @@ -46,10 +52,7 @@ instance Participant Mouse StateData where subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Mouse t) funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return uuid + partSubscribe (Mouse t) = generalSubscribe t partUnSubscribe (Mouse t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) @@ -64,10 +67,7 @@ instance Participant Keyboard StateData where subTups <- liftIO $ readTVarIO t return $ map snd subTups - partSubscribe (Keyboard t) funct = do - uuid <- genUUID - liftIO $ atomically $ modifyTVar' t ((uuid, funct) :) - return uuid + partSubscribe (Keyboard t) = generalSubscribe t partUnSubscribe (Keyboard t) uuid = liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) From 59b60f58c347a82ca5dedc8926cdabd57914e267 Mon Sep 17 00:00:00 2001 From: nek0 Date: Thu, 28 Dec 2017 16:56:25 +0100 Subject: [PATCH 199/261] removing old examples --- examples/example00.hs | 145 --------------- examples/example01.hs | 110 ----------- examples/example02.1.hs | 170 ----------------- examples/example02.hs | 154 ---------------- examples/example03.hs | 202 --------------------- examples/example04.hs | 222 ---------------------- examples/example04/panorama.jpg | Bin 409609 -> 0 bytes examples/example05.1.hs | 294 ------------------------------ examples/example05.hs | 313 -------------------------------- 9 files changed, 1610 deletions(-) delete mode 100644 examples/example00.hs delete mode 100644 examples/example01.hs delete mode 100644 examples/example02.1.hs delete mode 100644 examples/example02.hs delete mode 100644 examples/example03.hs delete mode 100644 examples/example04.hs delete mode 100644 examples/example04/panorama.jpg delete mode 100644 examples/example05.1.hs delete mode 100644 examples/example05.hs diff --git a/examples/example00.hs b/examples/example00.hs deleted file mode 100644 index c9090b8..0000000 --- a/examples/example00.hs +++ /dev/null @@ -1,145 +0,0 @@ -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE TypeFamilies #-} - -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 ())]) - -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 - -instance Participant Window StateData where - type Mesg Window StateData = WindowMessage - - partSubscribers (Window t) = do - subTups <- liftIO $ readTVarIO t - return $ map snd subTups - - partSubscribe (Window t) = generalSubscribe t - - partUnSubscribe (Window t) uuid = - liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) - -instance SDLSubsystem Window StateData where - consumeSDLEvents = consumeSDLWindowEvents - -instance Participant Mouse StateData where - type Mesg Mouse StateData = MouseMessage - - partSubscribers (Mouse t) = do - subTups <- liftIO $ readTVarIO t - return $ map snd subTups - - partSubscribe (Mouse t) = generalSubscribe t - - partUnSubscribe (Mouse t) uuid = - liftIO $ atomically $ modifyTVar' t (filter (\(u, _) -> u /= uuid)) - -instance SDLSubsystem Mouse StateData where - consumeSDLEvents = consumeSDLMouseEvents - -instance Participant Keyboard StateData where - type Mesg Keyboard StateData = KeyboardMessage - - partSubscribers (Keyboard t) = do - subTups <- liftIO $ readTVarIO t - return $ map snd subTups - - partSubscribe (Keyboard t) = generalSubscribe t - - partUnSubscribe (Keyboard t) 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 () diff --git a/examples/example01.hs b/examples/example01.hs deleted file mode 100644 index 3380349..0000000 --- a/examples/example01.hs +++ /dev/null @@ -1,110 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} - -import Affection -import qualified SDL -import qualified SDL.Raw as Raw -import qualified GEGL as G -import qualified BABL as B -import qualified Data.Map.Strict as M - -import Control.Monad (when) - -import Foreign.Storable (peek) -import Foreign.C.Types - -import Debug.Trace - --- main :: IO () --- main = withAllAffection $ --- withDefaultWindow "test" $ do --- changeColor $ RGBA 255 255 255 255 --- clear --- present --- liftIO $ delaySec 2 - -main :: IO () -main = do - conf <- return $ AffectionConfig - { initComponents = All - , windowTitle = "Affection: example00" - , windowConfig = SDL.defaultWindow - , preLoop = return () - , eventLoop = const $ return () - , updateLoop = update - , drawLoop = draw - , loadState = load - , cleanUp = clean - , canvasSize = Nothing - } - withAffection conf - -data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - , foreground :: G.GeglBuffer - -- , lastTick :: Double - } - -load :: IO UserData -load = do - traceM "loading" - root <- G.gegl_node_new - traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ - props $ do - prop "color1" $ G.RGBA 0.4 0.4 0.4 1 - prop "color2" $ G.RGBA 0.6 0.6 0.6 1 - traceM "checkerboard" - over <- G.gegl_node_new_child root G.defaultOverOperation - traceM "over" - buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< - B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ - props $ - prop "buffer" buffer - traceM "buffer-source" - nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] - traceM "nop" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ - props $ do - prop "width" (800::Double) - prop "height" (600.0::Double) - G.gegl_node_link_many [checkerboard, over, crop, sink] - G.gegl_node_connect_to nop "output" over "aux" - traceM "connections made" - myMap <- return $ M.fromList - [ ("root" , root) - , ("over" , over) - , ("background" , checkerboard) - , ("sink" , sink) - , ("nop" , nop) - ] - let roi = G.GeglRectangle 0 0 20 20 - traceM "loading complete" - return $ UserData - { nodeGraph = myMap - , foreground = buffer - -- , lastTick = 0 - } - -draw :: Affection UserData () -draw = do - traceM "drawing" - UserData{..} <- getAffection - drawRect (nodeGraph M.! "nop") (G.RGB 1 0 0) Fill (G.GeglRectangle 10 10 500 500) foreground - process $ nodeGraph M.! "sink" - render Nothing Nothing - -update :: Double -> Affection UserData () -update dt = do - traceM "updating" - ud <- getAffection - -- let last = lastTick ud - -- tick <- getTick - -- putAffection $ ud { lastTick = tick } - traceM $ (show $ 1 / dt) ++ " FPS" - elapsed <- getElapsedTime - when (elapsed > 20) $ - quit - -clean :: UserData -> IO () -clean _ = return () diff --git a/examples/example02.1.hs b/examples/example02.1.hs deleted file mode 100644 index 2f2c48c..0000000 --- a/examples/example02.1.hs +++ /dev/null @@ -1,170 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} - -import Affection -import qualified SDL -import qualified GEGL as G -import qualified BABL as B -import qualified Data.Map.Strict as M - -import Debug.Trace - -main :: IO () -main = do - conf <- return $ AffectionConfig - { initComponents = All - , windowTitle = "Affection: example00" - , windowConfig = SDL.defaultWindow - , preLoop = return () - , eventLoop = handle - , updateLoop = update - , drawLoop = draw - , loadState = load - , cleanUp = clean - , canvasSize = Nothing - } - withAffection conf - -data UserData = UserData - -- { nodeGraph :: M.Map String G.GeglNode - -- , foreground :: G.GeglBuffer - -- , lastTick :: Double - -- } - { coordinates :: (Double, Double) - , lastTick :: Double - } - -load :: IO UserData -load = do - -- traceM "loading" - -- root <- G.gegl_node_new - -- traceM "new root node" - -- checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ - -- props $ do - -- prop "color1" $ G.RGBA 0.4 0.4 0.4 1 - -- prop "color2" $ G.RGBA 0.6 0.6 0.6 1 - -- traceM "checkerboard" - -- over <- G.gegl_node_new_child root G.defaultOverOperation - -- traceM "over" - -- buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< - -- B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - -- sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ - -- props $ - -- prop "buffer" buffer - -- traceM "buffer-sink" - -- rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ - -- props $ do - -- prop "x" (0::Double) - -- prop "y" (0::Double) - -- prop "width" (20::Double) - -- prop "height" (20::Double) - -- prop "color" $ G.RGBA 1 0 0 0.5 - -- traceM "rect" - -- crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ - -- props $ do - -- prop "width" (800::Double) - -- prop "height" (600::Double) - -- G.gegl_node_link_many [checkerboard, over, crop, sink] - -- _ <- G.gegl_node_connect_to rect "output" over "aux" - -- traceM "connections made" - -- myMap <- return $ M.fromList - -- [ ("root" , root) - -- , ("over" , over) - -- , ("background" , checkerboard) - -- , ("sink" , sink) - -- , ("rect" , rect) - -- , ("crop" , crop) - -- ] - -- traceM "loading complete" - -- return $ UserData - -- { nodeGraph = myMap - -- , foreground = buffer - -- , lastTick = 0 - -- } - return $ UserData - { coordinates = (400, 300) - , lastTick = 0 - } - --- drawInit :: Affection UserData () --- drawInit = do --- UserData{..} <- getAffection --- present (GeglRectangle 0 0 800 600) foreground True - -draw :: Affection UserData () -draw = do - UserData{..} <- getAffection - traceM "loading" - root <- liftIO $ G.gegl_node_new - traceM "new root node" - checkerboard <- liftIO $ G.gegl_node_new_child root $ G.checkerboardOperation $ - props $ do - prop "color1" $ G.RGBA 0.4 0.4 0.4 1 - prop "color2" $ G.RGBA 0.6 0.6 0.6 1 - traceM "checkerboard" - over <- liftIO $ G.gegl_node_new_child root G.defaultOverOperation - traceM "over" - buffer <- liftIO $ G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< - B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- liftIO $ G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ - props $ - prop "buffer" buffer - traceM "buffer-sink" - rect <- liftIO $ G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ - props $ do - prop "x" $ fst coordinates - prop "y" $ snd coordinates - prop "width" (20::Double) - prop "height" (20::Double) - prop "color" $ G.RGBA 1 0 0 0.5 - traceM "rect" - crop <- liftIO $ G.gegl_node_new_child root $ G.Operation "gegl:crop" $ - props $ do - prop "width" (800::Double) - prop "height" (600::Double) - liftIO $ G.gegl_node_link_many [checkerboard, over, crop, sink] - _ <- liftIO $ G.gegl_node_connect_to rect "output" over "aux" - traceM "connections made" - myMap <- return $ M.fromList - [ ("root" , root) - , ("over" , over) - , ("background" , checkerboard) - , ("sink" , sink) - , ("rect" , rect) - , ("crop" , crop) - ] - traceM "loading complete" - process (myMap M.! "sink") - present (GeglRectangle 0 0 800 600) buffer True - render Nothing Nothing - -update :: Double -> Affection UserData () -update dt = do - traceM "updating" - - tick <- getElapsedTime - ud <- getAffection - putAffection $ ud { lastTick = tick } - - return () - traceM $ (show $ 1 / dt) ++ " FPS" - -handle (SDL.MouseMotionEvent dat) = do - let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat - ud <- getAffection - putAffection ud - { coordinates = (fromIntegral (x - 10), fromIntegral (y - 10)) - } - -- liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" $ - -- props $ do - -- prop "x" (fromIntegral (x - 10) :: Double) - -- prop "y" $ (fromIntegral (y - 10) :: Double) - -handle (SDL.WindowClosedEvent _) = do - traceM "seeya!" - quit - -handle _ = - return () - -clean :: UserData -> IO () -clean _ = return () diff --git a/examples/example02.hs b/examples/example02.hs deleted file mode 100644 index 1fe808c..0000000 --- a/examples/example02.hs +++ /dev/null @@ -1,154 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} - -import Affection -import qualified SDL -import qualified GEGL as G -import qualified BABL as B -import qualified Data.Map.Strict as M -import qualified Control.Monad.Parallel as MP - -import Debug.Trace - -main :: IO () -main = do - conf <- return AffectionConfig - { initComponents = All - , windowTitle = "Affection: example00" - , windowConfig = SDL.defaultWindow - , preLoop = return () - , eventLoop = handle - , updateLoop = update - , drawLoop = draw - , loadState = load - , cleanUp = clean - , canvasSize = Nothing - } - withAffection conf - -data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - , actors :: M.Map String (Actor String) - , foreground :: G.GeglBuffer - , lastTick :: Double - , updateActors :: [Actor String] - } - -load :: IO UserData -load = do - traceM "loading" - root <- G.gegl_node_new - traceM "new root node" - checkerboard <- G.gegl_node_new_child root $ G.checkerboardOperation $ - props $ do - prop "color1" $ G.RGBA 0.4 0.4 0.4 1 - prop "color2" $ G.RGBA 0.6 0.6 0.6 1 - traceM "checkerboard" - over <- G.gegl_node_new_child root G.defaultOverOperation - traceM "over" - buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< - B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ - props $ - prop "buffer" buffer - traceM "buffer-sink" - rectProps <- return $ - props $ do - prop "x" (0::Double) - prop "y" (0::Double) - prop "width" (20::Double) - prop "height" (20::Double) - prop "color" $ G.RGBA 1 0 0 0.5 - rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" rectProps - traceM "rect" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ - props $ do - prop "width" (800::Double) - prop "height" (600::Double) - G.gegl_node_link_many [checkerboard, over, crop, sink] - _ <- G.gegl_node_connect_to rect "output" over "aux" - rectActor <- return $ Actor (map - (\p -> ActorProperty - (G.propertyName p) - (G.propertyValue p) - (Just "rect") - )rectProps - ) (M.singleton "rect" rect) - traceM "connections made" - myMap <- return $ M.fromList - [ ("root" , root) - , ("over" , over) - , ("background" , checkerboard) - , ("sink" , sink) - , ("rect" , rect) - , ("crop" , crop) - ] - traceM "loading complete" - actorMap <- return $ M.fromList - [ ("rect", rectActor) - ] - return UserData - { nodeGraph = myMap - , actors = actorMap - , foreground = buffer - , lastTick = 0 - , updateActors = [] - } - --- drawInit :: Affection UserData () --- drawInit = do --- UserData{..} <- getAffection --- present (GeglRectangle 0 0 800 600) foreground True - -draw :: Affection UserData () -draw = do - ud@UserData{..} <- getAffection - MP.mapM_ applyProperties updateActors - putAffection ud - { updateActors = [] - } - process (nodeGraph M.! "sink") - present (GeglRectangle 0 0 800 600) foreground True - render Nothing Nothing - -update :: Double -> Affection UserData () -update dt = do - traceM "updating" - - tick <- getElapsedTime - ud <- getAffection - putAffection $ ud { lastTick = tick } - - -- let dt = tick - lastTick ud - return () - traceM $ show (1 / dt) ++ " FPS" - -handle :: SDL.EventPayload -> Affection UserData () -handle (SDL.MouseMotionEvent dat) = do - let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat - ud <- getAffection - - nmap <- return $ M.adjust - (updateProperties $ props $ do - prop "x" (fromIntegral (x - 10) :: Double) - prop "y" (fromIntegral (y - 10) :: Double) - ) - "rect" - (actors ud) - putAffection ud - { actors = nmap - , updateActors = (actors ud M.! "rect") : [] - } - -- liftIO $ G.gegl_node_set (nodeGraph ud M.! "rect") $ G.Operation "" $ - -- props $ do - -- prop "x" (fromIntegral (x - 10) :: Double) - -- prop "y" $ (fromIntegral (y - 10) :: Double) - -handle (SDL.WindowClosedEvent _) = do - traceM "seeya!" - quit - -handle _ = - return () - -clean :: UserData -> IO () -clean _ = return () diff --git a/examples/example03.hs b/examples/example03.hs deleted file mode 100644 index 120b6ee..0000000 --- a/examples/example03.hs +++ /dev/null @@ -1,202 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE BangPatterns #-} - -import Affection -import qualified SDL -import qualified GEGL as G -import qualified BABL as B - -import qualified Data.Map.Strict as M - -import Control.Monad (when) - -import Foreign.C.Types - -import System.Random (randomRIO) - -import Debug.Trace - -main :: IO () -main = do - conf <- return $ AffectionConfig - { initComponents = All - , windowTitle = "Affection: example00" - , windowConfig = SDL.defaultWindow - , preLoop = drawInit - , eventLoop = handle - , updateLoop = update - , drawLoop = draw - , loadState = load - , cleanUp = clean - , canvasSize = Nothing - } - withAffection conf - -data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - , foreground :: G.GeglBuffer - , partsys :: ParticleSystem - } - -load :: IO UserData -load = do - traceM "loading" - root <- G.gegl_node_new - traceM "new root node" - rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" $ - props $ do - prop "x" (0 :: Double) - prop "y" (0 :: Double) - prop "width" (800 :: Double) - prop "height" (600 :: Double) - prop "color" $ G.RGB 0 0 0 - traceM "rect" - traceM "checkerboard" - over <- G.gegl_node_new_child root G.defaultOverOperation - traceM "over" - buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 800 600) =<< - B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" - [G.Property "buffer" $ G.PropertyBuffer buffer - ] - traceM "buffer-sink" - nop <- G.gegl_node_new_child root $ G.Operation "gegl:nop" [] - traceM "nop" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" - [ G.Property "width" $ G.PropertyDouble 800 - , G.Property "height" $ G.PropertyDouble 600 - ] - traceM "crop" - G.gegl_node_link_many [rect, over, crop, sink] - G.gegl_node_connect_to nop "output" over "aux" - traceM "connections made" - myMap <- return $ M.fromList - [ ("root" , root) - , ("over" , over) - , ("sink" , sink) - , ("nop" , nop) - , ("crop" , crop) - ] - traceM "loading complete" - return $ UserData - { nodeGraph = myMap - , foreground = buffer - , partsys = ParticleSystem (ParticleStorage Nothing []) nop buffer - } - -drawInit :: Affection UserData () -drawInit = return () --- drawInit = do --- UserData{..} <- getAffection --- present (GeglRectangle 0 0 800 600) foreground True - -draw :: Affection UserData () -draw = do - traceM "drawing" - UserData{..} <- getAffection - drawParticleSystem partsys partDraw - process $ nodeGraph M.! "sink" - present - (G.GeglRectangle 0 0 800 600) - foreground - True - render Nothing Nothing - -update :: Double -> Affection UserData () -update dt = do - traceM "updating" - ad <- get - ud <- getAffection - delta <- getDelta - traceM $ (show $ 1 / delta) ++ " FPS" - ud2 <- getAffection - !nps <- updateParticleSystem (partsys ud2) delta partUpd - putAffection $ ud2 { partsys = nps } - -handle (SDL.MouseMotionEvent dat) = - when (SDL.ButtonLeft `elem` SDL.mouseMotionEventState dat) - $ do - ad <- get - ud <- getAffection - let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat - vx <- liftIO $ randomRIO (-20, 20) - vy <- liftIO $ randomRIO (-20, 20) - life <- liftIO $ randomRIO (1, 5) - r <- liftIO $ randomRIO (0,1) - g <- liftIO $ randomRIO (0,1) - b <- liftIO $ randomRIO (0,1) - tempRoot <- liftIO $ G.gegl_node_new - tempOver <- liftIO $ G.gegl_node_new_child tempRoot - G.defaultOverOperation - tempRect <- liftIO $ G.gegl_node_new_child tempRoot $ G.Operation - "gegl:rectangle" - [ G.Property "x" $ G.PropertyDouble $ fromIntegral x - 10 - , G.Property "y" $ G.PropertyDouble $ fromIntegral y - 10 - , G.Property "width" $ G.PropertyDouble 20 - , G.Property "height" $ G.PropertyDouble 20 - , G.Property "color" $ G.PropertyColor $ (G.RGBA r g b 0.5) - ] - liftIO $ G.gegl_node_connect_to tempRect "output" tempOver "aux" - -- traceM $ "position is: " ++ show x ++ " " ++ show y - -- traceM $ "velocity is: " ++ show vx ++ " " ++ show vy - ips <- insertParticle (partsys ud) $ - Particle - { particleTimeToLive = life - , particleCreation = elapsedTime ad - , particlePosition = (fromIntegral x, fromIntegral y) - , particleRotation = Rad 0 - , particleVelocity = (vx, vy) - , particlePitchRate = Rad 0 - , particleRootNode = tempRoot - , particleNodeGraph = M.fromList - [ ("root", tempRoot) - , ("over", tempOver) - , ("rect", tempRect) - ] - , particleStackCont = tempOver - , particleDrawFlange = tempOver - } - putAffection $ ud - { partsys = ips - } - -- when (not $ null $ psParts $ partsys ud) $ - -- liftIO $ G.gegl_node_link - -- tempOver - -- (particleStackCont $ head $ psParts $ partsys ud) - -handle (SDL.WindowClosedEvent _) = do - traceM "seeya!" - quit - -handle _ = - return () - -clean :: UserData -> IO () -clean ud = do - G.gegl_node_drop (nodeGraph ud M.! "root") - -partUpd :: Double -> Particle -> Affection UserData Particle -partUpd sec p = do - let !newX = (fst $ particlePosition p) + sec * (fromIntegral $ fst $ particleVelocity p) - !newY = (snd $ particlePosition p) + sec * (fromIntegral $ snd $ particleVelocity p) - liftIO $ G.gegl_node_set (particleNodeGraph p M.! "rect") $ G.Operation "gegl:rectangle" - [ G.Property "x" $ G.PropertyDouble $ newX - 10 - , G.Property "y" $ G.PropertyDouble $ newY - 10 - ] - let !np = p {particlePosition = (newX, newY)} - return np - -partDraw :: G.GeglBuffer -> G.GeglNode -> Particle -> Affection UserData () -partDraw _ _ _ = return () --- partDraw buf node Particle{..} = do --- present --- (G.GeglRectangle (floor $ fst particlePosition - 10) (floor $ snd particlePosition - 10) 20 20) --- buf --- False --- -- ud <- getAffection --- -- drawRect' --- -- particleDrawFlange --- -- (G.RGBA 1 0 0 0.5) --- -- (Fill) --- -- (G.GeglRectangle ((floor $ fst particlePosition) - 10) ((floor $ snd particlePosition) -10) 20 20) --- -- buf diff --git a/examples/example04.hs b/examples/example04.hs deleted file mode 100644 index 9c926f9..0000000 --- a/examples/example04.hs +++ /dev/null @@ -1,222 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} - -import Affection -import qualified SDL -import qualified GEGL as G -import qualified BABL as B -import qualified Data.Map.Strict as M -import Data.List as L -import Data.Maybe (fromJust) -import Control.Monad (when) -import qualified Control.Monad.Parallel as MP - -import Foreign.C.Types - -import Debug.Trace - -main :: IO () -main = do - conf <- return AffectionConfig - { initComponents = All - , windowTitle = "Affection: example00" - , windowConfig = SDL.defaultWindow - , canvasSize = Just (3289, 600) - , preLoop = return () - , eventLoop = handle - , updateLoop = update - , drawLoop = draw - , loadState = load - , cleanUp = clean - } - withAffection conf - -data UserData = UserData - { nodeGraph :: M.Map String G.GeglNode - , actors :: M.Map String (Actor String) - , foreground :: G.GeglBuffer - , updateActors :: [Actor String] - , keysDown :: [SDL.Keycode] - , cameraX :: Double - } - -load :: IO UserData -load = do - traceM "loading" - root <- G.gegl_node_new - traceM "new root node" - let bgProps = props $ prop "path" ("examples/example04/panorama.jpg" :: String) - bg <- G.gegl_node_new_child root $ G.Operation "gegl:jpg-load" bgProps - bgScaleProps <- return $ props $ do - prop "y" (600 :: Double) - prop "sampler" (fromEnum G.GeglSamplerCubic) - bgScale <- G.gegl_node_new_child root $ - G.Operation "gegl:scale-size-keepaspect" bgScaleProps - bgTransProps <- return $ props $ do - prop "x" (0 :: Double) - prop "y" (0 :: Double) - prop "sampler" (fromEnum G.GeglSamplerCubic) - bgTrans <- G.gegl_node_new_child root $ G.Operation "gegl:translate" bgTransProps - traceM "background" - over <- G.gegl_node_new_child root G.defaultOverOperation - traceM "over" - buffer <- G.gegl_buffer_new (Just $ G.GeglRectangle 0 0 3289 600) =<< - B.babl_format (B.PixelFormat B.RGBA B.CFfloat) - sink <- G.gegl_node_new_child root $ G.Operation "gegl:copy-buffer" $ - props $ - prop "buffer" buffer - traceM "buffer-sink" - rectProps <- return $ props $ do - prop "x" (50 :: Double) - prop "y" (290 :: Double) - prop "width" (20::Double) - prop "height" (20::Double) - prop "color" $ G.RGBA 1 0 0 0.5 - rect <- G.gegl_node_new_child root $ G.Operation "gegl:rectangle" rectProps - traceM "rect" - crop <- G.gegl_node_new_child root $ G.Operation "gegl:crop" $ - props $ do - prop "width" (3289 :: Double) - prop "height" (600 :: Double) - G.gegl_node_link_many [bg, bgTrans, bgScale, over, crop, sink] - _ <- G.gegl_node_connect_to rect "output" over "aux" - rectActor <- return $ Actor (map - (\p -> ActorProperty - (G.propertyName p) - (G.propertyValue p) - (Just "rect") - )rectProps - ) (M.singleton "rect" rect) - bgActor <- return $ Actor (map - (\p -> ActorProperty - (G.propertyName p) - (G.propertyValue p) - (Just "bg") - )bgProps ++ - map (\p -> ActorProperty - (G.propertyName p) - (G.propertyValue p) - (Just "scale") - ) bgScaleProps ++ - map (\p -> ActorProperty - (G.propertyName p) - (G.propertyValue p) - (Just "trans") - ) bgTransProps - ) (M.fromList - [ ("bg", bg) - , ("trans", bgTrans) - , ("scale", bgScale) - ]) - traceM "connections made" - myMap <- return $ M.fromList - [ ("root" , root) - , ("over" , over) - , ("background" , bg) - , ("sink" , sink) - , ("rect" , rect) - , ("crop" , crop) - ] - traceM "loading complete" - actorMap <- return $ M.fromList - [ ("rect", rectActor) - , ("background", bgActor) - ] - return UserData - { nodeGraph = myMap - , actors = actorMap - , foreground = buffer - , updateActors = [] - , keysDown = [] - , cameraX = 0 - } - -drawInit :: Affection UserData () -drawInit = do - ad <- get - ud <- getAffection - process (nodeGraph ud M.! "sink") - present (GeglRectangle (round $ cameraX ud) 0 800 600) (foreground ud) True - render Nothing Nothing - -draw :: Affection UserData () -draw = do - ud@UserData{..} <- getAffection - MP.mapM_ applyProperties updateActors - putAffection ud - { updateActors = [] - } - process (nodeGraph M.! "sink") - present (GeglRectangle (round cameraX) 0 800 600) foreground True - render - (Just $ G.GeglRectangle (round cameraX) 0 800 600) - Nothing - -update :: Double -> Affection UserData () -update dt = do - traceM "updating" - - ud <- getAffection - - traceM $ show (1 / dt) ++ " FPS" - -- traceM $ show $ keysDown ud - - mapM_ (\code -> do - let vel = 400 -- velocity in Pixels per second - leg = vel * dt - (G.PropertyDouble xpos) <- - return $ apValue $ fromJust $ L.find (\a -> "x" == apName a) $ - actorProperties $ actors ud M.! "rect" - nmap <- return $ M.adjust - (updateProperties $ props $ prop "x" $ xpos + - case code of - SDL.KeycodeLeft -> - if xpos - leg > 0 - then (- leg) - else 0 - SDL.KeycodeRight -> - if xpos + leg < 3269 - then leg - else 0 - _ -> 0 - ) - "rect" - (actors ud) - let { - offset | xpos - cameraX ud > 750 && cameraX ud + leg < 2489 && code == SDL.KeycodeRight = - leg - | xpos - cameraX ud < 50 && cameraX ud - leg > 0 && code == SDL.KeycodeLeft = - (-leg) - | otherwise = 0 - } - putAffection ud - { actors = nmap - , updateActors = (nmap M.! "rect") : updateActors ud - , cameraX = cameraX ud + offset - } - ) (keysDown ud) - -handle :: SDL.EventPayload -> Affection UserData () -handle (SDL.KeyboardEvent dat) = - when (not (SDL.keyboardEventRepeat dat)) $ do - ud <- getAffection - if (SDL.keyboardEventKeyMotion dat == SDL.Pressed) - then - putAffection ud - { keysDown = - SDL.keysymKeycode (SDL.keyboardEventKeysym dat) : keysDown ud - } - else - putAffection ud - { keysDown = - delete (SDL.keysymKeycode $ SDL.keyboardEventKeysym dat) (keysDown ud) - } - -handle (SDL.WindowClosedEvent _) = do - traceM "seeya!" - quit - -handle _ = - return () - -clean :: UserData -> IO () -clean _ = return () diff --git a/examples/example04/panorama.jpg b/examples/example04/panorama.jpg deleted file mode 100644 index 9ed5014681f58d92a88bf97cb655adca19a187df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 409609 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 z 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

x` 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�g%2dcYBJaVVshYki&>#Y~mS#22|cVO}E_5 zuh?AvFip4WO#wO(#t?9)96<*YQ&Bo@GEl~*JrbIhgVjIn zCI0}ymw&9bPvJO!7M4!J8^W;ap0se?!Ix6zAbla`S39?1(2O8pG$T9?SobHy5@?-_ zD^Tp8A+