Copyright | (c) Anton Gushcha, 2015-2016 |
---|---|
License | BSD3 |
Maintainer | ncrashed@gmail.com |
Stability | experimental |
Portability | POSIX |
Safe Haskell | None |
Language | Haskell2010 |
The core module contains API for actor based aproach of game development. The module doesn't depends on others core modules and could be place in any place in game monad stack.
The module is pure within first phase (see ModuleStack
docs) but requires MonadThrow
instance of end monad, therefore currently only IO
end monad can handler the module.
Example of embedding:
-- | Application monad is monad stack build from given list of modules over base monad (IO) type AppStack = ModuleStack [ActorT, ... other modules ... ] IO newtype AppState = AppState (ModuleState AppStack) deriving (Generic) instance NFData AppState -- | Wrapper around type family newtype AppMonad a = AppMonad (AppStack a) deriving (Functor, Applicative, Monad, MonadFix, MonadIO, ActorMonad, ... other modules monads ... ) instance GameModule AppMonad AppState where type ModuleState AppMonad = AppState runModule (AppMonad m) (AppState s) = do (a, s') <- runModule m s return (a, AppState s') newModuleState = AppState $ newModuleState withModule _ = withModule (Proxy :: Proxy AppStack) cleanupModule (AppState s) = cleanupModule s -- | Arrow that is build over the monad stack type AppWire a b = GameWire AppMonad a b -- | Action that makes indexed app wire type AppActor i a b = GameActor AppMonad i a b
Actor (GameActor
) is wire with its unique id. For instance you want actor for your player:
data Player = Player { playerId :: !PlayerId , playerPos :: !(Double, Double) } deriving (Generic) instance NFData Player newtype PlayerId = PlayerId { unPlayerId :: Int } deriving (Eq, Show, Generic) instance NFData PlayerId instance Hashable PlayerId instance Serialize PlayerId data PlayerMessage = -- | The player was shot by specified player PlayerShotMessage !PlayerId deriving (Typeable, Generic) instance NFData PlayerMessage instance ActorMessage PlayerId where type ActorMessageType PlayerId = PlayerMessage toCounter = unPlayerId fromCounter = PlayerId
Now you can create statefull actor:
playerActor :: ActorMonad m => (PlayerId -> Player) -> AppActor m PlayerId Game Player playerActor initialPlayer = makeActor $ i -> stateWire (initialPlayer i) $ mainController i where mainController i = proc (g, p) -> do emsg <- actorMessages i isPlayerShotMessage -< () -- do with emsg something returnA -< p
And you can have dynamic collection of actors:
processPlayers :: ActorMonad m => AppWire m Game [Player] processPlayer = proc g -> do addEvent <- periodic 4 -< newPlayer remEvent <- never -< () dynCollection [] -< (g, addEvent, remEvent)
- data ActorState s
- data ActorT s m a
- class MonadThrow m => ActorMonad m where
- actorRegisterM :: ActorMessage i => m i
- actorRegisterFixedM :: ActorMessage i => i -> m ()
- actorDeleteM :: ActorMessage i => i -> m ()
- actorRegisteredM :: ActorMessage i => i -> m Bool
- actorSendM :: (ActorMessage i, Typeable (ActorMessageType i)) => i -> ActorMessageType i -> m ()
- actorGetMessagesM :: (ActorMessage i, Typeable (ActorMessageType i)) => i -> m (Seq (ActorMessageType i))
- findActorTypeRepM :: String -> m (Maybe HashableTypeRep)
- registerActorTypeRepM :: forall proxy i. ActorMessage i => proxy i -> m ()
- data ActorException = ActorIdConflict TypeRep Int
- data GameWireIndexed m i a b = GameWireIndexed {
- indexedId :: i
- indexedWire :: GameWire m a b
- type GameActor m i a b = GameMonadT m (GameWireIndexed m i a b)
- class Typeable objectId => ActorMessage objectId where
- type ActorMessageType objectId :: *
- fromCounter :: Int -> objectId
- toCounter :: objectId -> Int
- postActorAction :: Monad m => GameActor m i a b -> (i -> GameWire m b c) -> GameActor m i a c
- preActorAction :: Monad m => (i -> GameWire m c a) -> GameActor m i a b -> GameActor m i c b
- makeActor :: (ActorMonad m, ActorMessage i) => (i -> GameWire m a b) -> GameActor m i a b
- makeFixedActor :: (ActorMonad m, ActorMessage i) => i -> GameWire m a b -> GameActor m i a b
- runActor :: ActorMonad m => GameActor m i a b -> GameWire m a (b, i)
- runActor' :: ActorMonad m => GameActor m i a b -> GameWire m a b
- getActorFingerprint :: forall i. ActorMessage i => i -> HashableTypeRep
- actorFingerprint :: forall proxy a. ActorMessage a => proxy a -> HashableTypeRep
- actorSend :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) => i -> GameWire m (Event (ActorMessageType i)) (Event ())
- actorSendMany :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i), Foldable t) => i -> GameWire m (Event (t (ActorMessageType i))) (Event ())
- actorSendDyn :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) => GameWire m (Event (i, ActorMessageType i)) (Event ())
- actorSendManyDyn :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i), Foldable t) => GameWire m (Event (t (i, ActorMessageType i))) (Event ())
- actorProcessMessages :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) => i -> (a -> ActorMessageType i -> a) -> GameWire m a a
- actorProcessMessagesM :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) => i -> (a -> ActorMessageType i -> GameMonadT m a) -> GameWire m a a
- actorMessages :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) => i -> (ActorMessageType i -> Bool) -> GameWire m a (Event (Seq (ActorMessageType i)))
- class (Filterable c, Foldable c, Functor c, Traversable c) => DynCollection c where
- type DynConsConstr c o :: Constraint
- concatDynColl :: c a -> c a -> c a
- unzipDynColl :: c (a, b) -> (c a, c b)
- zipDynColl :: c a -> c b -> c (a, b)
- emptyDynColl :: c a
- consDynColl :: DynConsConstr c a => a -> c a -> c a
- class (Hashable i, Eq i) => ElementWithId a i where
- elementId :: a -> i
- dynCollection :: forall m i a b c c2. (ActorMonad m, Eq i, DynCollection c, FilterConstraint c (GameWireIndexed m i a b), FilterConstraint c (Either () b), Foldable c2) => c (GameActor m i a b) -> GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b)
- dDynCollection :: forall m i a b c c2. (ActorMonad m, Eq i, DynCollection c, FilterConstraint c (GameWireIndexed m i a b), FilterConstraint c (Either () b), Foldable c2) => c (GameActor m i a b) -> GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b)
Low level
data ActorState s Source
Inner state of actor module.
s
- - State of next module, the states are chained via nesting.
Generic (ActorState s) Source | |
NFData s => NFData (ActorState s) Source | |
Monad m => MonadState (ActorState s) (ActorT s m) | |
type Rep (ActorState s) Source |
Monad transformer of actor core module.
s
- - State of next core module in modules chain;
m
- - Next monad in modules monad stack;
a
- - Type of result value;
How to embed module:
type AppStack = ModuleStack [ActorT, ... other modules ... ] IO newtype AppMonad a = AppMonad (AppStack a) deriving (Functor, Applicative, Monad, MonadFix, MonadIO, ActorMonad)
The module is pure within first phase (see ModuleStack
docs) but requires MonadThrow
instance of end monad, therefore currently only IO
end monad can handler the module.
MonadBase IO m => MonadBase IO (ActorT s m) Source | |
MonadError e m => MonadError e (ActorT s m) Source | |
MonadTrans (ActorT s) Source | |
Monad m => MonadState (ActorState s) (ActorT s m) Source | |
Monad m => Monad (ActorT s m) Source | |
Functor m => Functor (ActorT s m) Source | |
MonadFix m => MonadFix (ActorT s m) Source | |
Monad m => Applicative (ActorT s m) Source | |
MonadIO m => MonadIO (ActorT s m) Source | |
MonadThrow m => MonadThrow (ActorT s m) Source | |
MonadMask m => MonadMask (ActorT s m) Source | |
MonadCatch m => MonadCatch (ActorT s m) Source | |
MonadResource m => MonadResource (ActorT s m) Source | |
MonadThrow m => ActorMonad (ActorT s m) Source | |
type ModuleState (ActorT s m) = ActorState s Source |
class MonadThrow m => ActorMonad m where Source
Low level monadic API for module.
actorRegisterM :: ActorMessage i => m i Source
Registers new actor in message system
actorRegisterFixedM :: ActorMessage i => i -> m () Source
Registers specific id, throws ActorException if there is id clash
actorDeleteM :: ActorMessage i => i -> m () Source
Deletes actor with given id
actorRegisteredM :: ActorMessage i => i -> m Bool Source
Checks if given id is already taken
actorSendM :: (ActorMessage i, Typeable (ActorMessageType i)) => i -> ActorMessageType i -> m () Source
Sends typed message to actor with given id
actorGetMessagesM :: (ActorMessage i, Typeable (ActorMessageType i)) => i -> m (Seq (ActorMessageType i)) Source
Get all messages that were collected for given actor's id
Note: Doesn't clears the queue
findActorTypeRepM :: String -> m (Maybe HashableTypeRep) Source
Find type representation of actor by it type name
registerActorTypeRepM :: forall proxy i. ActorMessage i => proxy i -> m () Source
Register type representation for actor (sometimes this should be done before any actor is registered)
(MonadThrow (mt m), ActorMonad m, MonadTrans mt) => ActorMonad (mt m) Source | |
MonadThrow m => ActorMonad (ActorT s m) Source |
data ActorException Source
Exceptions thrown by ActorMonad
ActorIdConflict TypeRep Int | Tried to register already presented actor |
Actor API
data GameWireIndexed m i a b Source
Game wire that has its own id
GameWireIndexed | |
|
Eq i => Eq (GameWireIndexed m i a b) Source | Equality by equality of ids |
type GameActor m i a b = GameMonadT m (GameWireIndexed m i a b) Source
Common pattern in game for creating incapsulated objects
Usually wires that are actors need context to register themselfes in core. Major part of wire functions operates with such wrapped indexed arrows thats why the convinient type synonym is exists.
class Typeable objectId => ActorMessage objectId where Source
The typeclass separates message API's of different type of actors
In general you don't want to have one global type to handle all possible types of messages, it will break modularity. Thats why you creates (with newtype) separate types of ids for each actor and statically binds message type (usually algebraic type) to the id.
The class implies that your id is some integer type, but it could be not. Just provide way to stable convertion of you id to integer and vice-versa.
type ActorMessageType objectId :: * Source
Binded message type, mailbox with id type of objectId would accept only this message type
postActorAction :: Monad m => GameActor m i a b -> (i -> GameWire m b c) -> GameActor m i a c Source
Compose actor and wire, the wire is added at the end of actor controller
preActorAction :: Monad m => (i -> GameWire m c a) -> GameActor m i a b -> GameActor m i c b Source
Compose actor and wire, the wire is added at the beginning of actor controller
:: (ActorMonad m, ActorMessage i) | |
=> (i -> GameWire m a b) | Body wire |
-> GameActor m i a b | Operation that makes actual actor |
Registers new index for wire and makes an actor wire
:: (ActorMonad m, ActorMessage i) | |
=> i | Manual id of actor |
-> GameWire m a b | Body wire |
-> GameActor m i a b | Operation that makes actual actor |
Registers new actor with fixed id, can fail with ActorException if there is already registered actor for that id
:: ActorMonad m | |
=> GameActor m i a b | Actor creator |
-> GameWire m a (b, i) | Usual wire that also returns id of inner indexed wire |
If need no dynamic switching, you can use the function to embed index wire just at time
:: ActorMonad m | |
=> GameActor m i a b | Actor creator |
-> GameWire m a b | Usual wire |
Same as runActor, but doesn't return id of actor
Helpers for libraries
getActorFingerprint :: forall i. ActorMessage i => i -> HashableTypeRep Source
Helper to get actor fingerprint from id value
actorFingerprint :: forall proxy a. ActorMessage a => proxy a -> HashableTypeRep Source
Returns hashable fingerprint of actor that is stable across applications (unique by type name)
Message API
actorSend :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) => i -> GameWire m (Event (ActorMessageType i)) (Event ()) Source
Sends message to statically known actor
actorSendMany :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i), Foldable t) => i -> GameWire m (Event (t (ActorMessageType i))) (Event ()) Source
Sends many messages to statically known actor
actorSendDyn :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) => GameWire m (Event (i, ActorMessageType i)) (Event ()) Source
Sends message to actor with incoming id
actorSendManyDyn :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i), Foldable t) => GameWire m (Event (t (i, ActorMessageType i))) (Event ()) Source
Sends many messages, dynamic version of actorSendMany which takes actor id as arrow input
:: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) | |
=> i | Actor id known statically |
-> (a -> ActorMessageType i -> a) | Action that modifies accumulator |
-> GameWire m a a | Wire that updates input value using supplied function |
Helper to process all messages from message queue and update a state
:: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) | |
=> i | Actor id known statically |
-> (a -> ActorMessageType i -> GameMonadT m a) | Monadic action that modifies accumulator |
-> GameWire m a a | Wire that updates input value using supplied function |
Helper to process all messages from message queue and update a state (monadic version)
:: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) | |
=> i | Actor id which messages we look for |
-> (ActorMessageType i -> Bool) | Filter function, leaves only with True return value |
-> GameWire m a (Event (Seq (ActorMessageType i))) |
Non-centric style of subscribing to messages
Dynamic collections
class (Filterable c, Foldable c, Functor c, Traversable c) => DynCollection c where Source
Dynamic collection for control wire that automates handling collections of FRP actors. The class defines minimum set of actions that collection should support to be used as base for collection of actors.
type DynConsConstr c o :: Constraint Source
Instance specific constraint for appending function
concatDynColl :: c a -> c a -> c a Source
Concat of two collections
unzipDynColl :: c (a, b) -> (c a, c b) Source
Unzipping of collection
zipDynColl :: c a -> c b -> c (a, b) Source
Ziping collection
emptyDynColl :: c a Source
Getting empty collection
consDynColl :: DynConsConstr c a => a -> c a -> c a Source
Adding element to the begining of collection
DynCollection [] Source | |
DynCollection Seq Source | |
(Eq k, Hashable k) => DynCollection (HashMap k) Source | Order of elements is not preserved |
class (Hashable i, Eq i) => ElementWithId a i where Source
Elements that contains id
:: (ActorMonad m, Eq i, DynCollection c, FilterConstraint c (GameWireIndexed m i a b), FilterConstraint c (Either () b), Foldable c2) | |
=> c (GameActor m i a b) | Inital set of wires |
-> GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b) |
Makes dynamic collection of wires.
- First input of wire is input for each inner wire.
- Second input is event for adding several wires to collection.
- Third input is event for removing several wires from collection.
- Wire returns list of outputs of inner wires.
Note: if ihibits one of the wires, it is removed from output result during its inhibition
:: (ActorMonad m, Eq i, DynCollection c, FilterConstraint c (GameWireIndexed m i a b), FilterConstraint c (Either () b), Foldable c2) | |
=> c (GameActor m i a b) | Inital set of wires |
-> GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b) |
Makes dynamic collection of wires.
- First input of wire is input for each inner wire.
- Second input is event for adding several wires to collection.
- Third input is event for removing several wires from collection.
- Wire returns list of outputs of inner wires.
Note: it is delayed version of dynCollection, removing and adding of agents performs on next step after current.
Note: if ihibits one of the wires, it is removed from output result while it inhibits.