flaw-editor/Flaw/Editor/Processing.hs
{-|
Module: Flaw.Editor.Processing
Description: Entity processing.
License: MIT
-}
{-# LANGUAGE DefaultSignatures, GeneralizedNewtypeDeriving, FlexibleContexts, OverloadedStrings, TemplateHaskell, TypeFamilies #-}
module Flaw.Editor.Processing
( ProcessingContext(..)
, ProcessingException(..)
, ProcessingM(..)
, ProcessableEntity(..)
, readEntityPtr
, readInterfacedEntityPtr
) where
import Control.Concurrent.STM
import Control.Exception
import Control.Monad.Except
import Control.Monad.Reader
import qualified Data.Serialize as S
import Data.Typeable
import Flaw.Editor.BlobService
import Flaw.Editor.Entity
data ProcessingContext = ProcessingContext
{ processingContextEntityManager :: !EntityManager
, processingContextBlobService :: !BlobService
}
data ProcessingException
-- | General processing error.
= ProcessingException SomeException
-- | Error processing prerequisite.
| ProcessingPrerequisiteException ProcessingException
-- | Processing monad.
newtype ProcessingM a = ProcessingM (ExceptT ProcessingException (ReaderT ProcessingContext IO) a) deriving
( Functor
, Applicative
, Monad
, MonadIO
, MonadError ProcessingException
, MonadReader ProcessingContext
)
-- | Entity which require processing.
-- Entity's result can be cached and reused instead of processing entity again.
class (Entity a, S.Serialize (ProcessableEntityResult a)) => ProcessableEntity a where
{-# MINIMAL #-}
type ProcessableEntityResult a :: *
type ProcessableEntityResult a = a
processEntity :: a -> ProcessingM (ProcessableEntityResult a)
default processEntity :: ProcessableEntityResult a ~ a => a -> ProcessingM (ProcessableEntityResult a)
processEntity = return
instance EntityInterface ProcessableEntity where
getEntityInterfaceId _ = $(hashTextToEntityInterfaceId "ProcessableEntity")
-- | Read entity pointer.
readEntityPtr :: Entity a => EntityPtr a -> ProcessingM a
readEntityPtr (EntityPtr entityId) = do
ProcessingContext
{ processingContextEntityManager = entityManager
} <- ask
SomeEntity entity <- liftIO $ atomically . readSomeEntityVar =<< getSomeEntityVar entityManager entityId
case cast entity of
Just castedEntity -> return castedEntity
Nothing -> throwError $ ProcessingException $ SomeException EntityWrongTypeException
-- | Read interfaced entity pointer.
readInterfacedEntityPtr :: EntityInterface i => InterfacedEntityPtr i -> ProcessingM (SomeInterfacedEntity i)
readInterfacedEntityPtr (InterfacedEntityPtr entityId) = f Proxy where
f :: EntityInterface i => Proxy i -> ProcessingM (SomeInterfacedEntity i)
f proxy = do
ProcessingContext
{ processingContextEntityManager = entityManager
} <- ask
SomeEntity entity <- liftIO $ atomically . readSomeEntityVar =<< getSomeEntityVar entityManager entityId
case interfaceEntity proxy entity of
EntityInterfaced -> return $ SomeInterfacedEntity entity
EntityNotInterfaced -> throwError $ ProcessingException $ SomeException EntityWrongTypeException