gore-and-ash-actor (empty) → 1.1.0.0
raw patch · 12 files changed
+998/−0 lines, 12 filesdep +basedep +containersdep +deepseqsetup-changed
Dependencies added: base, containers, deepseq, exceptions, gore-and-ash, hashable, mtl, unordered-containers
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- gore-and-ash-actor.cabal +51/−0
- src/Game/GoreAndAsh/Actor.hs +135/−0
- src/Game/GoreAndAsh/Actor/API.hs +305/−0
- src/Game/GoreAndAsh/Actor/Collection.hs +115/−0
- src/Game/GoreAndAsh/Actor/Collection/Data.hs +87/−0
- src/Game/GoreAndAsh/Actor/Indexed.hs +59/−0
- src/Game/GoreAndAsh/Actor/Message.hs +37/−0
- src/Game/GoreAndAsh/Actor/Module.hs +59/−0
- src/Game/GoreAndAsh/Actor/State.hs +68/−0
- src/Game/GoreAndAsh/Actor/TypeRep.hs +50/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Gushcha Anton (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ gore-and-ash-actor.cabal view
@@ -0,0 +1,51 @@+name: gore-and-ash-actor+version: 1.1.0.0+synopsis: Gore&Ash engine extension that implements actor style of programming+description: Please see README.md+homepage: https://github.com/Teaspot-Studio/gore-and-ash-actor+license: BSD3+license-file: LICENSE+author: Anton Gushcha+maintainer: ncrashed@gmail.com+copyright: 2016 Anton Gushcha+category: Game+build-type: Simple+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Game.GoreAndAsh.Actor+ Game.GoreAndAsh.Actor.API+ Game.GoreAndAsh.Actor.Collection+ Game.GoreAndAsh.Actor.Collection.Data+ Game.GoreAndAsh.Actor.Indexed+ Game.GoreAndAsh.Actor.Message+ Game.GoreAndAsh.Actor.Module+ Game.GoreAndAsh.Actor.State+ Game.GoreAndAsh.Actor.TypeRep++ default-language: Haskell2010++ build-depends: base >= 4.7 && < 5+ , containers >= 0.5.6.2+ , deepseq >= 1.4+ , exceptions >= 0.8.0.2 + , gore-and-ash >= 1.1.0.0+ , hashable >= 1.2.3.3+ , mtl >= 2.2+ , unordered-containers >= 0.2.5.1+ + default-extensions: + Arrows+ BangPatterns+ DeriveGeneric+ FlexibleContexts+ FlexibleInstances+ GeneralizedNewtypeDeriving+ MultiParamTypeClasses+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ TupleSections+ TypeFamilies+ UndecidableInstances
+ src/Game/GoreAndAsh/Actor.hs view
@@ -0,0 +1,135 @@+{-|+Module : Game.GoreAndAsh.Actor+Description : Module that contains actor API for Gore&Ash+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX++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)+@+-}+module Game.GoreAndAsh.Actor(+ -- * Low level+ ActorState+ , ActorT + , ActorMonad(..)+ , ActorException(..)+ -- * Actor API+ , GameWireIndexed(..)+ , GameActor+ , ActorMessage(..)+ , postActorAction+ , preActorAction+ , makeActor+ , makeFixedActor+ , runActor+ , runActor'+ -- ** Helpers for libraries+ , getActorFingerprint+ , actorFingerprint+ -- * Message API+ , actorSend+ , actorSendMany+ , actorSendDyn+ , actorSendManyDyn+ , actorProcessMessages+ , actorProcessMessagesM+ , actorMessages+ -- * Dynamic collections+ , DynCollection(..)+ , ElementWithId(..)+ , dynCollection+ , dDynCollection+ ) where++import Game.GoreAndAsh.Actor.API as X+import Game.GoreAndAsh.Actor.Collection as X+import Game.GoreAndAsh.Actor.Indexed as X+import Game.GoreAndAsh.Actor.Message as X+import Game.GoreAndAsh.Actor.Module as X+import Game.GoreAndAsh.Actor.State as X
+ src/Game/GoreAndAsh/Actor/API.hs view
@@ -0,0 +1,305 @@+{-|+Module : Game.GoreAndAsh.Actor.API+Description : Monadic and arrow API for actor core module+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX++Module that contains monadic and arrow API of actor module.+-}+module Game.GoreAndAsh.Actor.API(+ ActorMonad(..)+ , ActorException(..)+ -- * Message API+ , actorSend+ , actorSendMany+ , actorSendDyn+ , actorSendManyDyn+ , actorProcessMessages+ , actorProcessMessagesM+ , actorMessages+ -- * Actor API+ , makeActor+ , makeFixedActor+ , runActor+ , runActor'+ -- * Helpers for libraries+ , getActorFingerprint+ ) where++import Control.Monad.Catch+import Control.Monad.State.Strict+import Control.Wire+import Control.Wire.Unsafe.Event+import Data.Dynamic+import Data.Maybe (isJust, fromJust)+import GHC.Generics +import Prelude hiding (id, (.))+import qualified Data.Foldable as F+import qualified Data.HashMap.Strict as H +import qualified Data.Sequence as S ++import Game.GoreAndAsh+import Game.GoreAndAsh.Actor.Indexed+import Game.GoreAndAsh.Actor.Message+import Game.GoreAndAsh.Actor.Module+import Game.GoreAndAsh.Actor.State+import Game.GoreAndAsh.Actor.TypeRep++-- | Exceptions thrown by ActorMonad+data ActorException = + ActorIdConflict TypeRep Int -- ^ Tried to register already presented actor+ deriving (Show, Generic)++instance Exception ActorException++-- | Low level monadic API for module.+class MonadThrow m => ActorMonad m where + -- | Registers new actor in message system+ actorRegisterM :: ActorMessage i => m i + + -- | Registers specific id, throws ActorException if there is id clash+ actorRegisterFixedM :: ActorMessage i => i -> m ()++ -- | Deletes actor with given id+ actorDeleteM :: ActorMessage i => i -> m ()++ -- | Checks if given id is already taken+ actorRegisteredM :: ActorMessage i => i -> m Bool++ -- | Sends typed message to actor with given id+ actorSendM :: (ActorMessage i, Typeable (ActorMessageType i)) + => i -> ActorMessageType i -> m ()++ -- | Get all messages that were collected for given actor's id+ --+ -- Note: Doesn't clears the queue+ actorGetMessagesM :: (ActorMessage i, Typeable (ActorMessageType i))+ => i -> m (S.Seq (ActorMessageType i))++ -- | Find type representation of actor by it type name+ findActorTypeRepM :: String -> m (Maybe HashableTypeRep)++ -- | Register type representation for actor (sometimes this should be done before+ -- any actor is registered)+ registerActorTypeRepM :: forall proxy i . ActorMessage i => proxy i -> m ()++instance {-# OVERLAPPING #-} MonadThrow m => ActorMonad (ActorT s m) where+ actorRegisterM = do + astate <- ActorT get + let (fpt, i, astate') = pushActorNextId astate + ActorT . put $! astate' {+ actorBoxes = H.insert (fpt, toCounter i) (S.empty, S.empty) $! actorBoxes astate'+ , actorNameMap = H.insert (show fpt) fpt $! actorNameMap astate'+ }+ return i ++ actorRegisterFixedM i = do + astate <- ActorT get+ case regActorFixedId i astate of + Nothing -> throwM $! ActorIdConflict tp (toCounter i)+ where tp = fromHashableTypeRep $ getActorFingerprint i+ Just astate' -> ActorT . put $! astate'++ actorDeleteM i = do + astate <- ActorT get + ActorT . put $! deleteActorId i astate+ + actorRegisteredM i = do + astate <- ActorT get + return . isActorIdRegistered i $! astate++ actorSendM i msg = do + astate <- ActorT get + ActorT . put $! putActorMessage i (toDyn msg) astate++ actorGetMessagesM i = do + astate <- ActorT get + let msgs = getActorMessages i astate + return . catMaybesSeq . fmap fromDynamic $! msgs++ findActorTypeRepM n = do + astate <- ActorT get + return . H.lookup n . actorNameMap $! astate++ registerActorTypeRepM p = do + astate <- ActorT get + let fp = actorFingerprint p+ ActorT . put $! astate {+ actorNameMap = H.insert (show fp) fp . actorNameMap $! astate+ }++instance {-# OVERLAPPABLE #-} (MonadThrow (mt m), ActorMonad m, MonadTrans mt) => ActorMonad (mt m) where + actorRegisterM = lift actorRegisterM+ actorRegisterFixedM = lift . actorRegisterFixedM+ actorDeleteM = lift . actorDeleteM+ actorRegisteredM = lift . actorRegisteredM+ actorSendM a b = lift $ actorSendM a b+ actorGetMessagesM = lift . actorGetMessagesM+ findActorTypeRepM = lift . findActorTypeRepM+ registerActorTypeRepM = lift . registerActorTypeRepM++-- | Leaves only Just values +catMaybesSeq :: S.Seq (Maybe a) -> S.Seq a +catMaybesSeq = fmap fromJust . S.filter isJust++-- | Helper to get actor fingerprint from id value+getActorFingerprint :: forall i . ActorMessage i => i -> HashableTypeRep+getActorFingerprint _ = actorFingerprint (Proxy :: Proxy i)++-- | Returns next unregistered id of actor and updates internal state+pushActorNextId :: forall i s . ActorMessage i => ActorState s -> (HashableTypeRep, i, ActorState s)+pushActorNextId !s = case H.lookup k (actorBoxes s) of+ Just _ -> pushActorNextId nextState+ Nothing -> (fingerprint, nextId, nextState)+ where + fingerprint = actorFingerprint (Proxy :: Proxy i)+ (nextId, nextState) = rawGet s + k = (fingerprint, toCounter nextId)++ -- | Update @actorNextId@ map+ rawGet :: ActorState s -> (i, ActorState s)+ rawGet s' = case H.lookup fingerprint (actorNextId s') of + Nothing -> (fromCounter 0, s' {+ actorNextId = H.insert fingerprint 1 (actorNextId s')+ })+ Just i -> (fromCounter i, s' {+ actorNextId = H.insert fingerprint (i+1) (actorNextId s')+ })+ +-- | Try to register given id in the mailbox map+regActorFixedId :: forall i s . ActorMessage i => i -> ActorState s -> Maybe (ActorState s)+regActorFixedId !i !s = case H.lookup k (actorBoxes s) of + Just _ -> Nothing+ Nothing -> Just $! s {+ actorBoxes = H.insert k (S.empty, S.empty) . actorBoxes $! s+ , actorNameMap = H.insert (show fingerprint) fingerprint . actorNameMap $! s+ }+ where+ fingerprint = actorFingerprint (Proxy :: Proxy i)+ k = (fingerprint, toCounter i)++-- | Remove actor id from mailbox map+deleteActorId :: forall i s . ActorMessage i => i -> ActorState s -> ActorState s +deleteActorId !i !s = s {+ actorBoxes = H.delete k . actorBoxes $! s + , actorNameMap = H.delete (show fingerprint) . actorNameMap $! s+ }+ where+ fingerprint = actorFingerprint (Proxy :: Proxy i)+ k = (fingerprint, toCounter i)++-- | Returns True if given ID is registered+isActorIdRegistered :: forall i s . ActorMessage i => i -> ActorState s -> Bool+isActorIdRegistered !i !s = case H.lookup k . actorBoxes $! s of + Nothing -> False + Just _ -> True + where+ fingerprint = actorFingerprint (Proxy :: Proxy i)+ k = (fingerprint, toCounter i)++-- | Appends given message in corresponding message queue+putActorMessage :: forall i s . ActorMessage i => i -> Dynamic -> ActorState s -> ActorState s+putActorMessage !i !msg !s = case H.lookup k . actorBoxes $! s of + Nothing -> s+ Just (msgsR, msgsS) -> s {+ actorBoxes = H.insert k (msgsR, msgsS S.|> msg) . actorBoxes $! s+ }+ where+ fingerprint = actorFingerprint (Proxy :: Proxy i)+ k = (fingerprint, toCounter i)++-- | Return all messages in corresponding mailbox+getActorMessages :: forall i s . ActorMessage i => i -> ActorState s -> S.Seq Dynamic+getActorMessages !i !s = case H.lookup k . actorBoxes $! s of + Nothing -> S.empty+ Just (msgs, _) -> msgs+ where+ fingerprint = actorFingerprint (Proxy :: Proxy i)+ k = (fingerprint, toCounter i)++-- | Sends message to statically known actor+actorSend :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) + => i -> GameWire m (Event (ActorMessageType i)) (Event ())+actorSend i = liftGameMonadEvent1 $ actorSendM i++-- | Sends many messages to statically known actor+actorSendMany :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i), F.Foldable t)+ => i -> GameWire m (Event (t (ActorMessageType i))) (Event ())+actorSendMany i = liftGameMonadEvent1 $ F.mapM_ (actorSendM i)++-- | Sends message to actor with incoming id+actorSendDyn :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i)) + => GameWire m (Event (i, ActorMessageType i)) (Event ())+actorSendDyn = liftGameMonadEvent1 $ \(i, m) -> actorSendM i m++-- | Sends many messages, dynamic version of actorSendMany which takes actor id as arrow input+actorSendManyDyn :: (ActorMonad m, ActorMessage i, Typeable (ActorMessageType i), F.Foldable t)+ => GameWire m (Event (t (i, ActorMessageType i))) (Event ())+actorSendManyDyn = liftGameMonadEvent1 $ F.mapM_ (uncurry actorSendM)++-- | Helper to process all messages from message queue and update a state+actorProcessMessages :: (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+actorProcessMessages i f = liftGameMonad1 $ \a -> do + msgs <- actorGetMessagesM i+ return . F.foldl' f a $! msgs++-- | Helper to process all messages from message queue and update a state (monadic version)+actorProcessMessagesM :: (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+actorProcessMessagesM i f = liftGameMonad1 $ \a -> do + msgs <- actorGetMessagesM i + foldM f a msgs++-- | Registers new index for wire and makes an actor wire+makeActor :: (ActorMonad m, ActorMessage i) + => (i -> GameWire m a b) -- ^ Body wire+ -> GameActor m i a b -- ^ Operation that makes actual actor+makeActor wbody = do + i <- actorRegisterM+ return $! GameWireIndexed i (wbody i)++-- | Registers new actor with fixed id, can fail with ActorException if there is already +-- registered actor for that id+makeFixedActor :: (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+makeFixedActor i wbody = do + actorRegisterFixedM i+ return $! GameWireIndexed i wbody++-- | If need no dynamic switching, you can use the function to embed index wire just at time+runActor :: ActorMonad m + => GameActor m i a b -- ^ Actor creator+ -> GameWire m a (b, i) -- ^ Usual wire that also returns id of inner indexed wire+runActor actor = switch makeWire+ where+ -- | Switches immidieatly to created wire, thats why error is used for+ -- value that should be returned in case where there is no event.+ makeWire = proc _ -> do + e <- mapE (\iw -> arr (, indexedId iw) . indexedWire iw) . now . liftGameMonadOnce actor -< ()+ returnA -< (error "runActor: impossible", e)++-- | Same as runActor, but doesn't return id of actor+runActor' :: ActorMonad m + => GameActor m i a b -- ^ Actor creator+ -> GameWire m a b -- ^ Usual wire+runActor' actor = arr fst . runActor actor++-- | Non-centric style of subscribing to messages+actorMessages :: (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 (S.Seq (ActorMessageType i)))+actorMessages i f = liftGameMonad $ do + msgs <- S.filter f <$> actorGetMessagesM i + return $! if S.null msgs then NoEvent+ else Event msgs
+ src/Game/GoreAndAsh/Actor/Collection.hs view
@@ -0,0 +1,115 @@+{-|+Module : Game.GoreAndAsh.Actor.Collection+Description : Handling dynamic collections of actors+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX+-}+module Game.GoreAndAsh.Actor.Collection(+ dynCollection+ , dDynCollection+ , DynCollection(..)+ , module ReExport+ ) where++import Control.Monad +import Control.Wire+import Control.Wire.Unsafe.Event +import Data.Filterable +import Prelude hiding ((.), id)+import qualified Data.Foldable as F ++import Game.GoreAndAsh+import Game.GoreAndAsh.Actor.API+import Game.GoreAndAsh.Actor.Indexed++import Game.GoreAndAsh.Actor.Collection.Data as ReExport++-- | Helper that performs monadic action over value of event or returns default value+--+-- Note: the function is isomorphic to @Data.Maybe.maybe@+onEvent :: Monad m => b -> Event a -> (a -> m b) -> m b+onEvent def e f = case e of + NoEvent -> return def + Event a -> f a ++-- | 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+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), F.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)+dynCollection initialActors = mkGen $ \ds input -> do + arrs <- sequence initialActors+ go arrs ds input+ where + go :: c (GameWireIndexed m i a b)+ -> GameTime+ -> (a, Event (c (GameActor m i a b)), Event (c2 i))+ -> GameMonadT m (Either () (c b), GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b))+ go currentWires ds (a, addEvent, removeEvent) = do++ -- Adding new wires+ newAddedWires <- onEvent currentWires addEvent $ \newActors -> do + addWires <- sequence newActors+ return $ currentWires `concatDynColl` addWires++ -- Removing wires+ newRemovedWires <- onEvent newAddedWires removeEvent $ \ids -> + return $ F.foldl' (\acc i -> fFilter ((/= i) . indexedId) acc) newAddedWires ids++ -- Calculating outputs+ (bs, newWiresCntrls) <- liftM unzipDynColl $ mapM (\w -> stepWire w ds (Right a)) $ indexedWire <$> newRemovedWires+ let newWires = uncurry updateIndexedWire <$> (fmap const newWiresCntrls `zipDynColl` newRemovedWires)++ return $ length newWires `seq` (Right (rightsDynColl bs), mkGen $ go newWires)++-- | 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.+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), F.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)+dDynCollection initialActors = mkGen $ \ds input -> do + arrs <- sequence initialActors+ go arrs ds input+ where + go :: c (GameWireIndexed m i a b)+ -> GameTime+ -> (a, Event (c (GameActor m i a b)), Event (c2 i))+ -> GameMonadT m (Either () (c b), GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b))+ go currentWires ds (a, addEvent, removeEvent) = do+ -- Calculating outputs+ (bs, newWiresCntrls) <- liftM unzipDynColl $ mapM (\w -> stepWire w ds (Right a)) $ indexedWire <$> currentWires+ let newWires = uncurry updateIndexedWire <$> (fmap const newWiresCntrls `zipDynColl` currentWires)++ -- Adding new wires+ newAddedWires <- onEvent newWires addEvent $ \newActors -> do + addWires <- sequence newActors + return $ newWires `concatDynColl` addWires++ -- Removing wires+ newRemovedWires <- onEvent newAddedWires removeEvent $ \ids -> + return $ F.foldl' (\acc i -> fFilter ((/= i) . indexedId) acc) newAddedWires ids++ return $ length newRemovedWires `seq` (Right (rightsDynColl bs), mkGen $ go newRemovedWires)
+ src/Game/GoreAndAsh/Actor/Collection/Data.hs view
@@ -0,0 +1,87 @@+{-|+Module : Game.GoreAndAsh.Actor.Collection.Data+Description : Handling dynamic collections of actors+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX+-}+module Game.GoreAndAsh.Actor.Collection.Data(+ DynCollection(..)+ , ElementWithId(..)+ , rightsDynColl+ ) where++import Control.Monad +import Control.Wire+import Data.Either (isRight)+import Data.Filterable +import Data.Hashable +import Data.List (nub)+import GHC.Exts+import Prelude hiding ((.), id)+import qualified Data.Foldable as F +import qualified Data.HashMap.Strict as H +import qualified Data.Sequence as S ++-- | 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.+class (Filterable c, F.Foldable c, Functor c, Traversable c) => DynCollection c where+ -- | Instance specific constraint for appending function+ type DynConsConstr c o :: Constraint + type DynConsConstr c o = ()++ -- | Concat of two collections+ concatDynColl :: c a -> c a -> c a+ -- | Unzipping of collection+ unzipDynColl :: c (a , b) -> (c a, c b)+ -- | Ziping collection+ zipDynColl :: c a -> c b -> c (a, b)+ -- | Getting empty collection+ emptyDynColl :: c a + -- | Adding element to the begining of collection+ consDynColl :: DynConsConstr c a => a -> c a -> c a ++instance DynCollection [] where+ concatDynColl = (++)+ unzipDynColl = unzip + zipDynColl = zip + emptyDynColl = []+ consDynColl = (:)++instance DynCollection S.Seq where + concatDynColl = (S.><)+ unzipDynColl = F.foldl' (\(as, bs) (a, b) -> (as S.|> a, bs S.|> b)) (S.empty, S.empty)+ zipDynColl = S.zip + emptyDynColl = S.empty+ consDynColl = (S.<|)++-- | Elements that contains id +class (Hashable i, Eq i) => ElementWithId a i where+ elementId :: a -> i ++-- | Order of elements is not preserved+instance (Eq k, Hashable k) => DynCollection (H.HashMap k) where+ type DynConsConstr (H.HashMap k) o = ElementWithId o k++ concatDynColl = H.union+ unzipDynColl = H.foldlWithKey' (\(as, bs) k (a, b) -> (H.insert k a as, H.insert k b bs)) (H.empty, H.empty) + zipDynColl as bs = F.foldl' mrg H.empty $ nub $ H.keys as ++ H.keys bs+ where + mrg acc k = case (H.lookup k as, H.lookup k bs) of + (Just a, Just b) -> H.insert k (a, b) acc+ _ -> acc++ emptyDynColl = H.empty+ consDynColl a = H.insert (elementId a) a++-- | Helper to filter out lefts+rightsDynColl :: (FilterConstraint c (Either e a), DynCollection c) + => c (Either e a) -> c a +rightsDynColl = fmap fromRight . fFilter isRight+ where+ fromRight e = case e of + Left _ -> error "rightsDynColl: left (impossible)"+ Right a -> a
+ src/Game/GoreAndAsh/Actor/Indexed.hs view
@@ -0,0 +1,59 @@+{-|+Module : Game.GoreAndAsh.Actor+Description : Module that contains actor API for Gore&Ash+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX+-}+module Game.GoreAndAsh.Actor.Indexed(+ GameWireIndexed(..)+ , GameActor+ , updateIndexedWire+ , postActorAction+ , preActorAction+ ) where++import Control.Wire +import Prelude hiding (id, (.))++import Game.GoreAndAsh.Core.Arrow+import Game.GoreAndAsh.Core.Monad ++-- | Game wire that has its own id+data GameWireIndexed m i a b = GameWireIndexed {+ indexedId :: i +, indexedWire :: GameWire m a b+}++-- | Equality by equality of ids+instance Eq i => Eq (GameWireIndexed m i a b) where+ gw1 == gw2 = indexedId gw1 == indexedId gw2++-- | Replaces controlling wire in indexed wire+updateIndexedWire :: (GameWire m a b -> GameWire m a b) -> GameWireIndexed m i a b -> GameWireIndexed m i a b +updateIndexedWire f wi = wi { indexedWire = f $ indexedWire wi }++-- | 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.+type GameActor m i a b = GameMonadT m (GameWireIndexed m i a b)++-- | Compose actor and wire, the wire is added at the end of actor controller+postActorAction :: Monad m => GameActor m i a b -> (i -> GameWire m b c) -> GameActor m i a c+postActorAction mindexed wi = do + indexed <- mindexed+ return $ indexed {+ indexedWire = wi (indexedId indexed) . indexedWire indexed+ }++-- | Compose actor and wire, the wire is added at the beginning of actor controller+preActorAction :: Monad m => (i -> GameWire m c a) -> GameActor m i a b -> GameActor m i c b+preActorAction wi mindexed = do + indexed <- mindexed+ return $ indexed {+ indexedWire = indexedWire indexed . wi (indexedId indexed)+ }
+ src/Game/GoreAndAsh/Actor/Message.hs view
@@ -0,0 +1,37 @@+{-|+Module : Game.GoreAndAsh.Actor+Description : Typeclass for type of actor message+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX+-}+module Game.GoreAndAsh.Actor.Message(+ ActorMessage(..)+ , actorFingerprint+ ) where++import Data.Typeable++import Game.GoreAndAsh.Actor.TypeRep++-- | 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.+class Typeable objectId => ActorMessage objectId where + -- | Binded message type, mailbox with id type of objectId would accept only this message type+ type ActorMessageType objectId :: *+ -- | Convertion from global counter. Don't use it in client code as it could break type safety.+ fromCounter :: Int -> objectId+ -- | Convertion to global counter. Don't use it in client code as it could break type safety.+ toCounter :: objectId -> Int++-- | Returns hashable fingerprint of actor that is stable across applications (unique by type name)+actorFingerprint :: forall proxy a . ActorMessage a => proxy a -> HashableTypeRep+actorFingerprint = hashableTypeRep
+ src/Game/GoreAndAsh/Actor/Module.hs view
@@ -0,0 +1,59 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+Module : Game.GoreAndAsh.Actor.Module+Description : Monad transformer for actor core module+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX++The module contains monad transformer for actor core module and +instance of 'GameModule' for it.+-}+module Game.GoreAndAsh.Actor.Module(+ ActorT(..)+ ) where++import Control.Monad.Catch+import Control.Monad.Fix +import Control.Monad.State.Strict++import Game.GoreAndAsh+import Game.GoreAndAsh.Actor.State++-- | 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.+newtype ActorT s m a = ActorT { runActorT :: StateT (ActorState s) m a }+ deriving (Functor, Applicative, Monad, MonadState (ActorState s), MonadFix, MonadTrans, MonadIO, MonadThrow, MonadCatch, MonadMask)++instance GameModule m s => GameModule (ActorT s m) (ActorState s) where + type ModuleState (ActorT s m) = ActorState s+ runModule (ActorT m) s = do+ ((a, s'), nextState) <- runModule (runStateT m s) (actorNextState s)+ let s'' = moveSendedMessages s'+ return (a, s'' { + actorNextState = nextState + })++ newModuleState = emptyActorState <$> newModuleState++ withModule _ = id+ cleanupModule _ = return ()
+ src/Game/GoreAndAsh/Actor/State.hs view
@@ -0,0 +1,68 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-|+Module : Game.GoreAndAsh.Actor.State+Description : State of actor core module+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX++Internal state of actor core module.+-}+module Game.GoreAndAsh.Actor.State(+ ActorState(..)+ , emptyActorState+ , moveSendedMessages+ ) where++import Control.DeepSeq +import Data.Dynamic+import GHC.Generics (Generic)+import qualified Data.HashMap.Strict as H +import qualified Data.Sequence as S ++import Game.GoreAndAsh.Actor.TypeRep++-- | Inner state of actor module.+--+-- [@s@] - State of next module, the states are chained via nesting.+data ActorState s = ActorState {+ -- | Stores messages for actor with specified id+ --+ -- Message has type of Dynamic as message manager doesn't know anything about message types.+ -- We don't need to serialization protocol due passing via memory. Type safety is forced + -- with Messagable type class with type family (see Actor.Message module). Id space is separate for each actor type+ --+ -- There are two sequences of messages, one for recieved messages from previous frame, and+ -- the second one for messages recieved at current frame. At the end of each frame first+ -- sequence is purged and filled with contents of first and the second one is replaced with+ -- empty sequence.+ actorBoxes :: !(H.HashMap (HashableTypeRep, Int) (S.Seq Dynamic, S.Seq Dynamic))+ -- | Next empty id of actor, id space is separate for each actor type+, actorNextId :: !(H.HashMap HashableTypeRep Int)+ -- | Search table for actor names+, actorNameMap :: !(H.HashMap String HashableTypeRep)+ -- | Next state in state chain of modules+, actorNextState :: !s+} deriving (Generic)++instance NFData Dynamic where + rnf = (`seq` ())++instance NFData s => NFData (ActorState s)++-- | Create empty actor state+emptyActorState :: s -> ActorState s +emptyActorState s = ActorState {+ actorBoxes = H.empty+ , actorNextId = H.empty+ , actorNameMap = H.empty+ , actorNextState = s+ }++-- | Perform rotation between sended messages and recieved ones+moveSendedMessages :: ActorState s -> ActorState s +moveSendedMessages s = s {+ actorBoxes = fmap (\(_, b) -> (b, S.empty)) . actorBoxes $! s+ }
+ src/Game/GoreAndAsh/Actor/TypeRep.hs view
@@ -0,0 +1,50 @@+{-|+Module : Game.GoreAndAsh.Actor.TypeRep+Description : Hashable type representation+Copyright : (c) Anton Gushcha, 2015-2016+License : BSD3+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : POSIX++-}+module Game.GoreAndAsh.Actor.TypeRep(+ HashableTypeRep+ , toHashableTypeRep+ , fromHashableTypeRep+ , hashableTypeRep+ ) where++import Control.DeepSeq+import Data.Hashable +import Data.Typeable+import GHC.Generics ++-- | Wrapper around TypeRep that supports Hashable and Eq, that are performed over type name+-- Note: the implentation is choosen to support equality of actors between several applications+newtype HashableTypeRep = HashableTypeRep { unHashableTypeRep :: TypeRep }+ deriving (Generic, Typeable)++instance Eq HashableTypeRep where + (HashableTypeRep a) == (HashableTypeRep b) = show a == show b++instance Show HashableTypeRep where + showsPrec i = showsPrec i . unHashableTypeRep++instance Hashable HashableTypeRep where+ hashWithSalt salt (HashableTypeRep tr) = salt `hashWithSalt` show tr++-- | Helper to transform usual 'TypeRep' to hashable one.+toHashableTypeRep :: TypeRep -> HashableTypeRep+toHashableTypeRep = HashableTypeRep++-- | Helper to transform hashable type rep to usual 'TypeRep'.+fromHashableTypeRep :: HashableTypeRep -> TypeRep +fromHashableTypeRep = unHashableTypeRep++-- | Helper to get hashable type rep from type+hashableTypeRep :: forall proxy a . Typeable a => proxy a -> HashableTypeRep+hashableTypeRep = HashableTypeRep . typeRep++instance NFData HashableTypeRep where+ rnf (HashableTypeRep tr) = rnfTypeRep tr