Allow for concurrent effects

This commit is contained in:
2022-08-16 16:02:09 +01:00
parent 919a179283
commit 26e88f059a
14 changed files with 181 additions and 53 deletions
+10
View File
@@ -0,0 +1,10 @@
module Dodge.Concurrent where
import Dodge.Data.Universe
--import qualified Data.Set as S
--import Control.Lens
--import Control.Concurrent
--import SDL.Input.Keyboard.Codes
conTest :: Universe -> Maybe (IO (Universe -> Universe))
conTest = _uvConcEffects
+13
View File
@@ -0,0 +1,13 @@
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}
module Dodge.Data.SaveSlot where
import Control.Lens
data SaveSlot
= QuicksaveSlot
| LevelStartSlot Int
| SaveSlotNum Int
deriving (Eq, Ord, Show, Read)
makeLenses ''SaveSlot
+4 -7
View File
@@ -10,7 +10,7 @@ module Dodge.Data.Universe (
) where
import Control.Lens
import qualified Data.Map.Strict as M
--import qualified Data.Map.Strict as M
import Data.Preload
import qualified Data.Text as T
import Dodge.Data.Config
@@ -22,17 +22,14 @@ data Universe = Universe
{ _uvWorld :: World
, _preloadData :: PreloadData
, _menuLayers :: [ScreenLayer]
, _savedWorlds :: M.Map SaveSlot World
, _quickSave :: Maybe World
, _uvIOEffects :: Universe -> IO Universe
, _uvConcEffects :: Maybe (IO (Universe -> Universe))
, _uvConfig :: Configuration
, _uvTestString :: Universe -> [String]
, _uvConcMessage :: String
}
data SaveSlot
= QuicksaveSlot
| LevelStartSlot
| SaveSlotNum Int
deriving (Eq, Ord, Show, Read)
data OptionScreenFlag = NormalOptions | GameOverOptions
deriving (Eq, Ord, Show, Read)
+7
View File
@@ -3,12 +3,14 @@
module Dodge.Data.World (
module Dodge.Data.World,
module Dodge.Data.SaveSlot,
module Dodge.Data.CWorld,
module Dodge.Data.RightButtonOptions,
module Dodge.Data.SoundOrigin,
module Dodge.Data.Hammer,
) where
import Dodge.Data.SaveSlot
import Control.Lens
import qualified Data.Map.Strict as M
import qualified Data.Set as S
@@ -40,8 +42,13 @@ data World = World
, _backspaceTimer :: Int
, _timeFlow :: TimeFlowStatus
, _rbOptions :: RightButtonOptions
, _gameSlot :: GameSlot
}
data GameSlot = GameNum Int
| GameLoading SaveSlot
| GameStartScreen
data TimeFlowStatus
= RewindingNow
| RewindingLastFrame
+1
View File
@@ -31,6 +31,7 @@ defaultWorld =
, _backspaceTimer = 0
, _timeFlow = NormalTimeFlow
, _rbOptions = NoRightButtonOptions
, _gameSlot = GameStartScreen
}
defaultCWorld :: CWorld
+2 -2
View File
@@ -80,8 +80,8 @@ handlePressedKey True ScancodeBackspace u
| otherwise = return $ Just $ u & uvWorld . backspaceTimer -~ 1
handlePressedKey True _ u = return $ Just u
handlePressedKey _ scode u = case scode of
ScancodeF1 -> Just <$> (writeSaveSlot (SaveSlotNum 1) u >> return u)
ScancodeF2 -> Just <$> readSaveSlot (SaveSlotNum 1) u
-- ScancodeF1 -> Just <$> (writeSaveSlot (SaveSlotNum 1) u >> return u)
-- ScancodeF2 -> Just <$> readSaveSlot (SaveSlotNum 1) u
ScancodeF5 -> return . Just $ doQuicksave u
ScancodeF9 -> return . Just $ loadSaveSlot QuicksaveSlot u
ScancodeSemicolon -> return . Just $ gotoTerminal u
+2 -2
View File
@@ -36,7 +36,7 @@ pauseMenuOptions :: [MenuOption]
pauseMenuOptions =
basicKeyOptions
[ Toggle (return . Just . startNewGame) (opText "NEW LEVEL")
, Toggle (return . Just . loadSaveSlot LevelStartSlot) (opText "RESTART")
, Toggle (return . Just . reloadLevelStart) (opText "RESTART")
, Toggle (pushScreen $ seedStartMenu "START FROM SEED") (opText "START FROM SEED")
, Toggle (pushScreen optionMenu) (opText "OPTIONS")
, Toggle (pushScreen displayControls) (opText "CONTROLS")
@@ -63,7 +63,7 @@ trySeedFromClipboard u = do
pushScreen
(seedStartMenu "CLIPBOARD UNUSABLE, NEED INTEGER")
(u & menuLayers %~ tail)
Just i -> return . Just $ startSeedGame i u
Just i -> return . Just $ startSeedGame 0 i u
slTitleOptions :: String -> [MenuOption] -> ScreenLayer
slTitleOptions title ops = slTitleOptionsEff title ops (popScreen . writeConfig)
+15 -7
View File
@@ -2,6 +2,8 @@ module Dodge.Render.Picture (
fixedCoordPictures,
) where
import Dodge.Base.Window
import Dodge.Render.List
import Dodge.Base.WinScale
import Dodge.Data.Universe
import Dodge.Render.HUD
@@ -10,17 +12,23 @@ import Geometry
import Picture
fixedCoordPictures :: Universe -> Picture
fixedCoordPictures u = case _menuLayers u of
[] ->
pictures
[ hudDrawings u
, customMouseCursor cfig w
]
(lay : _) -> setDepth (-1) . winScale cfig $ menuScreen u lay
fixedCoordPictures u =
drawConcurrentMessage u <> case _menuLayers u of
[] ->
pictures
[ hudDrawings u
, customMouseCursor cfig w
]
(lay : _) -> setDepth (-1) . winScale cfig $ menuScreen u lay
where
w = _uvWorld u
cfig = _uvConfig u
drawConcurrentMessage :: Universe -> Picture
drawConcurrentMessage uv = listPicturesAt (halfWidth cfig) (_windowY cfig - 50) cfig [text (_uvConcMessage uv)]
where
cfig = _uvConfig uv
customMouseCursor :: Configuration -> World -> Picture
customMouseCursor cfig w =
winScale cfig
+30 -28
View File
@@ -1,71 +1,73 @@
module Dodge.Save (
saveWorldInEmptySlot,
saveWorldInSlot,
loadSaveSlot,
doQuicksave,
saveLevelStartSlot,
-- saveLevelStartSlot,
writeSaveSlot,
readSaveSlot,
reloadLevelStart,
) where
import Control.Lens
import Dodge.Data.SaveSlot
import Data.Aeson
import qualified Data.Aeson.Encode.Pretty as AEP
import qualified Data.ByteString.Lazy as BS
import qualified Data.Map.Strict as M
import Dodge.Data.Universe
--import qualified Data.Set as S
import System.Directory
writeSaveSlot :: SaveSlot -> Universe -> IO ()
writeSaveSlot :: SaveSlot -> Universe -> IO (Universe -> Universe)
writeSaveSlot ss u = do
createDirectoryIfMissing True "saveSlot"
BS.writeFile (saveSlotPath ss) $
AEP.encodePretty'
(AEP.Config (AEP.Spaces 2) compare AEP.Generic False)
(u ^. uvWorld . cWorld)
return id
--writeFile (saveSlotPath ss) (show $ u ^. uvWorld . cWorld)
readSaveSlot :: SaveSlot -> Universe -> IO Universe
readSaveSlot ss uv = do
readSaveSlot :: SaveSlot -> IO (Universe -> Universe)
readSaveSlot ss = do
fExists <- doesFileExist $ saveSlotPath ss
if fExists
then do
--cwstr <- readFile $ saveSlotPath ss
--case readMaybe cwstr of
cwstr <- decodeFileStrict $ saveSlotPath ss
case cwstr of
Nothing -> putStrLn "loadSaveSlot failed to read saved file" >> return uv
Just cw -> return $ uv & uvWorld . cWorld .~ cw
else putStrLn "loadSaveSlot failed to find saved file" >> return uv
Nothing -> putStrLn "loadSaveSlot failed to read saved file" >> return id
Just cw -> return $ \uv -> uv & uvWorld . cWorld .~ cw
else putStrLn "loadSaveSlot failed to find saved file" >> return id
saveSlotPath :: SaveSlot -> String
saveSlotPath (SaveSlotNum i) = "saveSlot/" ++ show i
saveSlotPath QuicksaveSlot = "saveSlot/QuickSave"
saveSlotPath LevelStartSlot = "saveSlot/LevelStartSave"
saveWorldInEmptySlot :: SaveSlot -> Universe -> Universe
saveWorldInEmptySlot slot w = case M.lookup slot $ _savedWorlds w of
Nothing -> w & savedWorlds %~ M.insert slot (clearKeys (_uvWorld w))
_ -> w
saveSlotPath (LevelStartSlot i) = "saveSlot/LevelStart" ++ show i
saveWorldInSlot :: SaveSlot -> Universe -> Universe
saveWorldInSlot slot u = u & savedWorlds %~ M.insert slot (clearKeys (_uvWorld u))
saveWorldInSlot slot u = u & uvConcEffects ?~
writeSaveSlot slot u
reloadLevelStart :: Universe -> Universe
reloadLevelStart u = case u ^. uvWorld . gameSlot of
GameNum i -> loadSaveSlot (LevelStartSlot i) u
_ -> u
loadSaveSlot :: SaveSlot -> Universe -> Universe
loadSaveSlot slot u =
maybe
u
(\w -> u & menuLayers .~ [] & uvWorld .~ w)
$ M.lookup slot (_savedWorlds u)
loadSaveSlot slot = (uvConcEffects ?~ readSaveSlot slot)
. (uvWorld . gameSlot .~ GameLoading slot)
-- maybe
-- u
-- (\w -> u & menuLayers .~ [] & uvWorld .~ w)
-- $ M.lookup slot (_savedWorlds u)
doQuicksave :: Universe -> Universe
doQuicksave = saveWorldInSlot QuicksaveSlot
clearKeys :: World -> World
clearKeys = (keys .~ mempty) . (mouseButtons .~ mempty)
--clearKeys :: World -> World
--clearKeys = (keys .~ mempty) . (mouseButtons .~ mempty)
saveLevelStartSlot :: Universe -> Universe
--saveLevelStartSlot = id
saveLevelStartSlot = saveWorldInSlot LevelStartSlot
--saveLevelStartSlot :: Universe -> Universe
----saveLevelStartSlot = id
--saveLevelStartSlot = saveWorldInSlot LevelStartSlot
--saveLevelStartSlot = sideEffects %~ (fmap (undefined) . )
+13 -4
View File
@@ -1,21 +1,30 @@
module Dodge.StartNewGame where
import Dodge.Save
import Control.Lens
import Dodge.Data.Universe
import Dodge.LevelGen
import System.Random
startNewGame :: Universe -> Universe
startNewGame u = startSeedGame i u
startNewGame u = case u ^. uvWorld . gameSlot of
GameNum i -> startNewGameInSlot i u
GameLoading{} -> u
GameStartScreen -> startNewGameInSlot 0 u -- this needs to be changed
startNewGameInSlot :: Int -> Universe -> Universe
startNewGameInSlot slot u = startSeedGame slot i u
where
i = fst $ randomR (0, maxBound) (_randGen (_uvWorld u))
startSeedGame :: Int -> Universe -> Universe
startSeedGame i u =
startSeedGame :: Int -> Int -> Universe -> Universe
startSeedGame slot i u =
u
& menuLayers .~ [WaitScreen (const "GENERATING...") 1]
& uvIOEffects .~ \_ -> do
w <- generateWorldFromSeed i
return $
u & menuLayers .~ [] & uvWorld .~ w
& savedWorlds . at LevelStartSlot ?~ w
& uvWorld . gameSlot .~ GameNum slot
& saveWorldInSlot (LevelStartSlot slot)
-- & savedWorlds . at LevelStartSlot ?~ w
+1
View File
@@ -4,3 +4,4 @@ import Dodge.Data.Universe
testStringInit :: Universe -> [String]
testStringInit _ = []
+1 -1
View File
@@ -63,7 +63,7 @@ import Sound.Data
{- For most menus the only way to change the world is using event handling. -}
updateUniverse :: Universe -> Universe
updateUniverse u = case _menuLayers u of
updateUniverse u = (uvConcEffects .~ Nothing) $ case _menuLayers u of
(WaitScreen s i : _)
| i < 1 -> u & over uvWorld doWorldEvents
| otherwise -> u & menuLayers %~ ((WaitScreen s (i -1) :) . tail)
+76
View File
@@ -8,6 +8,7 @@ This module sets up an SDL window which may be updated using a simple game loop.
-}
module Loop (
setupLoop,
setupConLoop,
) where
import Control.Concurrent
@@ -89,3 +90,78 @@ applyEventIO fn mw e = case eventPayload e of
mupdate = case mw of
Nothing -> return Nothing
Just w -> fn w e
-- | Create a game loop with an SDL window.
setupConLoop ::
-- | Target seconds per frame
Int ->
-- | Window title
T.Text ->
WindowConfig ->
-- | Function for cleaning up parameters, applied when exiting loop.
(world -> IO ()) ->
-- | Initial simulation state.
IO world ->
-- | Concurrent effects
(world -> Maybe (IO (world -> world))) ->
-- | update, called once per frame. Allows for side effects such as rendering.
(world -> IO world) ->
-- | SDL Event handling, once per frame. Evaluating 'Nothing' exits the loop.
(world -> Event -> IO (Maybe world)) ->
IO ()
setupConLoop spf title winconfig paramCleanup ioStartWorld coneffs sideEffects eventFn = do
initializeAll
themvar <- newEmptyMVar
bracket
(createWindow title winconfig)
destroyWindow
$ \window -> bracket
(glCreateContext window)
(\con -> GL.finish >> glDeleteContext con)
$ \_ ->
bracket
ioStartWorld
paramCleanup
(doConLoop themvar spf window coneffs sideEffects eventFn)
-- | The internal loop.
doConLoop ::
-- | The mvar for concurrency
MVar (world -> world) ->
-- | target msec per frame
Int ->
-- | The SDL window.
Window ->
-- | Concurrent effects
(world -> Maybe (IO (world -> world))) ->
-- | simulation update.
(world -> IO world) ->
-- | SDL Event handling.
(world -> Event -> IO (Maybe world)) ->
-- | Current simulation state.
world ->
IO ()
doConLoop themvar spf window coneffs worldSideEffects eventFn startWorld = do
startTicks <- ticks
mconupdate <- tryTakeMVar themvar
case coneffs startWorld of
Nothing -> return ()
Just acc -> do
_ <- forkIO $ do
up <- acc
putMVar themvar up
return ()
let startWorld' = case mconupdate of
Just conupdate -> conupdate startWorld
Nothing -> startWorld
worldAfterSimStep <- worldSideEffects startWorld'
glSwapWindow window
maybeUpdatedWorld <- pollEvents >>= foldM (applyEventIO eventFn) (Just worldAfterSimStep)
case maybeUpdatedWorld of
Just updatedWorld -> do
performGC
endTicks <- ticks -- it might be better to use System.Clock (monotonic)
let theDelay = max 0 (spf + fromIntegral startTicks - fromIntegral endTicks)
threadDelay (theDelay * 1000)
doConLoop themvar spf window coneffs worldSideEffects eventFn updatedWorld
Nothing -> return ()