packages feed

affection-0.0.0.0: src/Affection.hs

{-# LANGUAGE RecordWildCards #-}
module Affection
    ( withAffection
    , getAffection
    , putAffection
    -- , withWindow
    -- , withDefaultWindow
    , delaySec
    , get
    , put
    , 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

import qualified Data.Text as T
import Data.Maybe

import System.Clock

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 Affection.Particle as A

import qualified BABL as B

-- | Main function which bootstraps everything else.
withAffection
  :: AffectionConfig us -- ^ Configuration of the Game and its engine.
  -> IO ()
withAffection AffectionConfig{..} = do
  case initComponents of
    All ->
      SDL.initializeAll
    Only is ->
      SDL.initialize is
  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
  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
    , drawSurface     = surface
    , drawFormat      = format
    , drawStack       = []
    , elapsedTime     = 0
    }) <$> 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 stride cpp) $ drawStack ad
        -- compute dt and update elapsedTime
        let dt = (fromIntegral $ toNanoSecs $ diffTimeSpec lastTime now) / (fromIntegral 10 ^ 9)
        put $ ad
          { drawStack = []
          , elapsedTime = elapsedTime ad + dt
          }
        -- execute user defined update loop
        updateLoop dt
        -- execute user defined draw loop
        drawLoop
        -- handle all new draw requests
        ad2 <- get
        clear <- catMaybes <$> mapM (handleDrawRequest pixels stride cpp) (drawStack ad2)
        -- save all draw requests to clear in next run
        put $ ad2
          { drawStack = clear }
        -- blit surface and update window
        liftIO $ SDL.surfaceBlit surface Nothing oldSurf Nothing
        liftIO $ SDL.updateWindowSurface $ drawWindow ad2
        -- save new time
        _ <- liftIO $ swapMVar execTime $ now
        return ()
      )
    ) initContainer
  G.gegl_exit
  cleanUp $ userState nState
  SDL.quit

-- | 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)