packages feed

gore-and-ash-lambdacube-0.2.0.0: src/Game/GoreAndAsh/LambdaCube/State.hs

{-|
Module      : Game.GoreAndAsh.LambdaCube.State
Description : Internal state of core module
Copyright   : (c) Anton Gushcha, 2016
License     : BSD3
Maintainer  : ncrashed@gmail.com
Stability   : experimental
Portability : POSIX
-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Game.GoreAndAsh.LambdaCube.State(
    LambdaCubeState(..)
  , PipelineId
  , StorageId(..)
  , LambdaCubeException(..)
  , emptyLambdaCubeState
  , freeLambdaCubeState
  -- | Internal API
  , updateStateViewportSize
  , isPipelineRegisteredInternal
  , registerPipelineInternal
  , unregisterPipelineInternal
  , getPipelineSchemeInternal
  , registerStorageInternal
  , unregisterStorageInternal
  , getStorageInternal
  , getRendererInternal
  , renderStorageLastInternal
  , renderStorageFirstInternal
  , stopRenderingInternal
  ) where

import Control.DeepSeq
import Control.Exception.Base (Exception)
import Data.Hashable
import Data.Text 
import GHC.Generics (Generic)

import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H 

import Data.Sequence (Seq)
import qualified Data.Sequence as S 

import LambdaCube.Compiler as LambdaCube
import LambdaCube.GL as LambdaCubeGL

-- | Exception type that could be thrown by the module
data LambdaCubeException =
  -- | Thrown when a pipeline compilation failed, first is pipeline main module, last is error message
    PipeLineCompileFailed String PipelineId String
  -- | Thrown when tries to register the same pipeline twice
  | PipeLineAlreadyRegistered PipelineId
  -- | Trhown when tries to create storage for unregistered pipeline
  | PipeLineNotFound PipelineId 
  -- | Thrown when tries to get unregistered storage 
  | StorageNotFound StorageId 
  -- | Thrown when failed to bind pipeline to context, contains pipeline name and error message
  | PipeLineIncompatible StorageId String
  deriving (Generic, Show)

instance Exception LambdaCubeException

-- | ID to uniquely identify LambdaCube rednering pipeline
type PipelineId = Text 

-- | ID to uniquely identify LambdaCube storage
data StorageId = StorageId { 
    storageId :: !Int 
  , storageScheme :: !PipelineId
  }
  deriving (Generic, Eq, Show)

instance NFData StorageId 
instance Hashable StorageId

-- | Check is storage binded to specified pipeline
isPipelineStorage :: PipelineId -> StorageId -> Bool 
isPipelineStorage pid sid = storageScheme sid == pid

-- | All info ablut pipeline
data PipelineInfo = PipelineInfo {
  pipeInfoRenderer :: !GLRenderer
, pipeInfoSchema :: !PipelineSchema
, pipeInfoPipeline :: !Pipeline   
} deriving Generic 

instance NFData PipelineInfo

-- | Internal state of core module
--
-- [@s@] - state of next module, they are chained until bottom, that is usually
--         an empty data type.
data LambdaCubeState s = LambdaCubeState {
  -- | Module storage for LambdaCube pipelines
  lambdacubePipelines :: !(HashMap PipelineId PipelineInfo)
  -- | Module storage for LambdaCube storages
, lambdacubeStorages :: !(HashMap StorageId GLStorage)
  -- | Defines in which order to render each object at next frame
, lambdacubeRenderOrder :: !(Seq StorageId)
  -- | Next storage id to use
, lambdacubeNextStorageId :: !Int
  -- | Next module state in chain of modules
, lambdacubeNextState :: !s
} deriving (Generic)

instance NFData s => NFData (LambdaCubeState s)

instance NFData PipelineSchema where 
  rnf a = a `seq` () 

instance NFData GLStorage where 
  rnf a = a `seq` () 

instance NFData GLRenderer where 
  rnf a = a `seq` () 

instance NFData Pipeline where 
  rnf a = a `seq` () 

-- | Create inital state of the core module
--
-- [@s@] -  state of next module
emptyLambdaCubeState :: s -> LambdaCubeState s 
emptyLambdaCubeState s = LambdaCubeState {
    lambdacubePipelines = H.empty 
  , lambdacubeStorages = H.empty 
  , lambdacubeRenderOrder = S.empty
  , lambdacubeNextStorageId = 0
  , lambdacubeNextState = s
  }

-- | Release module state resources
freeLambdaCubeState :: LambdaCubeState s -> IO ()
freeLambdaCubeState LambdaCubeState{..} = do 
  mapM_ LambdaCubeGL.disposeStorage lambdacubeStorages
  mapM_ (LambdaCubeGL.disposeRenderer . pipeInfoRenderer) lambdacubePipelines

-- | Update viewport size of all storages
updateStateViewportSize :: Word -> Word -> LambdaCubeState s -> IO ()
updateStateViewportSize w h LambdaCubeState{..} = 
  mapM_ (\s -> LambdaCubeGL.setScreenSize s w h) $ H.elems lambdacubeStorages

-- | Returns True if given pipeline is already exists
isPipelineRegisteredInternal :: PipelineId -> LambdaCubeState s -> Bool 
isPipelineRegisteredInternal pid LambdaCubeState{..} = case H.lookup pid lambdacubePipelines of 
    Nothing -> False 
    Just _ -> True 

-- | Register new pipeline with renderer in module
registerPipelineInternal :: PipelineId -> Pipeline -> PipelineSchema -> GLRenderer -> LambdaCubeState s -> LambdaCubeState s
registerPipelineInternal i ps pl r s = s {
    lambdacubePipelines = H.insert i info . lambdacubePipelines $! s
  }
  where 
    info = PipelineInfo {
        pipeInfoRenderer = r 
      , pipeInfoSchema = pl
      , pipeInfoPipeline = ps
      }

-- | Removes pipeline from state and deletes it, also destroys all storages of the pipeline
unregisterPipelineInternal :: PipelineId -> LambdaCubeState s -> IO (LambdaCubeState s)
unregisterPipelineInternal i s =
  case H.lookup i . lambdacubePipelines $! s of 
    Nothing -> return s
    Just (PipelineInfo{..}) -> do 
      let storages = H.filterWithKey (\k _ -> isPipelineStorage i k) . lambdacubeStorages $! s
      mapM_ LambdaCubeGL.disposeStorage . H.elems $! storages
      LambdaCubeGL.disposeRenderer pipeInfoRenderer 
      return $ s {
          lambdacubePipelines = H.delete i . lambdacubePipelines $! s
        , lambdacubeStorages = H.filterWithKey (\k _ -> not $ isPipelineStorage i k) . lambdacubeStorages $! s
        }

-- | Getter of pipeline scheme
getPipelineSchemeInternal :: PipelineId -> LambdaCubeState s -> Maybe PipelineSchema
getPipelineSchemeInternal i LambdaCubeState{..} = fmap pipeInfoSchema . H.lookup i $! lambdacubePipelines

-- | Registering gl storage for given pipeline
registerStorageInternal :: PipelineId -> GLStorage -> LambdaCubeState s -> (StorageId, LambdaCubeState s)
registerStorageInternal pid storage s = (i, s')
  where
  i = StorageId {
      storageId = lambdacubeNextStorageId s 
    , storageScheme = pid 
    }

  s' = s { 
      lambdacubeNextStorageId = lambdacubeNextStorageId s + 1 
    , lambdacubeStorages = H.insert i storage . lambdacubeStorages $! s
    }

-- | Remove and deallocate storage
unregisterStorageInternal :: StorageId -> LambdaCubeState s -> IO (LambdaCubeState s)
unregisterStorageInternal i s = case H.lookup i . lambdacubeStorages $! s of 
  Nothing -> return s
  Just storage -> do 
    LambdaCubeGL.disposeStorage storage 
    return $! s {
        lambdacubeStorages = H.delete i . lambdacubeStorages $! s 
      }

getRendererInternal :: PipelineId -> LambdaCubeState s -> Maybe GLRenderer
getRendererInternal i LambdaCubeState{..} = fmap pipeInfoRenderer $! H.lookup i lambdacubePipelines

-- | Find storage in state
getStorageInternal :: StorageId -> LambdaCubeState s -> Maybe GLStorage
getStorageInternal i LambdaCubeState{..} = H.lookup i lambdacubeStorages

-- | Puts storage at end of rendering queue
renderStorageLastInternal :: StorageId -> LambdaCubeState s -> LambdaCubeState s 
renderStorageLastInternal i s = s {
    lambdacubeRenderOrder = S.filter (/= i) (lambdacubeRenderOrder s) S.|> i
  }

-- | Puts storage at begining of rendering queue
renderStorageFirstInternal :: StorageId -> LambdaCubeState s -> LambdaCubeState s
renderStorageFirstInternal i s = s {
    lambdacubeRenderOrder = i S.<| S.filter (/= i) (lambdacubeRenderOrder s) 
  }

-- | Removes storage from rendering queue
stopRenderingInternal :: StorageId -> LambdaCubeState s -> LambdaCubeState s
stopRenderingInternal i s = s {
    lambdacubeRenderOrder = S.filter (/= i) (lambdacubeRenderOrder s) 
  }