gore-and-ash-sync-1.1.0.0: src/Game/GoreAndAsh/Sync/Remote/Collection.hs
{-|
Module : Game.GoreAndAsh.Sync.Remote.Collection
Description : Automatic synchronization of collection of actors
Copyright : (c) Anton Gushcha, 2015-2016
License : BSD3
Maintainer : ncrashed@gmail.com
Stability : experimental
Portability : POSIX
-}
module Game.GoreAndAsh.Sync.Remote.Collection(
RemActorCollId(..)
, remoteActorCollectionServer
, remoteActorCollectionClient
) where
import Control.DeepSeq
import Control.Monad (liftM)
import Control.Monad.Fix
import Control.Wire
import Control.Wire.Unsafe.Event
import Data.Filterable
import Data.Hashable
import Data.Proxy
import Data.Serialize
import GHC.Generics
import Prelude hiding ((.), id)
import qualified Data.Foldable as F
import qualified Data.Sequence as S
import Game.GoreAndAsh
import Game.GoreAndAsh.Actor
import Game.GoreAndAsh.Actor.Collection
import Game.GoreAndAsh.Actor.Indexed
import Game.GoreAndAsh.Logging
import Game.GoreAndAsh.Network
import Game.GoreAndAsh.Sync.API
import Game.GoreAndAsh.Sync.Message
import Game.GoreAndAsh.Sync.Remote.Actor
-- | Unique id space for remote collections actors
newtype RemActorCollId = RemActorCollId { unRemActorCollId :: Int }
deriving (Show, Eq, Ord, Generic)
instance NFData RemActorCollId
instance Hashable RemActorCollId
-- | Stub for local collection API
data RemActorCollMessage
instance ActorMessage RemActorCollId where
type ActorMessageType RemActorCollId = RemActorCollMessage
toCounter = unRemActorCollId
fromCounter = RemActorCollId
-- | Comminucation protocol between client and server side collections
data RemActorCollNetMessage =
-- | Sent to client when server collection adds new actor
RemActorCollNetSpawn !Int
-- | Sent to client when server collection removes specific actor
| RemActorCollNetDespawn !Int
-- | Sent when remote collection is set up to get actors that were
-- created before the collection creation
| RemActorCollRequestOthers
deriving (Generic)
instance NFData RemActorCollNetMessage
instance Serialize RemActorCollNetMessage
instance NetworkMessage RemActorCollId where
type NetworkMessageType RemActorCollId = RemActorCollNetMessage
-- | Helper to filter out specific message type
isRemActorCollNetSpawn :: RemActorCollNetMessage -> Bool
isRemActorCollNetSpawn m = case m of
RemActorCollNetSpawn _ -> True
_ -> False
-- | Helper to filter out specific message type
isRemActorCollNetDespawn :: RemActorCollNetMessage -> Bool
isRemActorCollNetDespawn m = case m of
RemActorCollNetDespawn _ -> True
_ -> False
-- | Helper to filter out specific message type
isRemActorCollRequestOthers :: RemActorCollNetMessage -> Bool
isRemActorCollRequestOthers m = case m of
RemActorCollRequestOthers -> True
_ -> False
-- | 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
-- | Server side collection of network actors that are automatically
-- synchronized to remote clients (creation and removing of actors).
--
-- * Second wire input is event with new actors to add to the collection
--
-- * Third wire input is event with id of actor to delete from the collection
--
-- Note: the collection doesn't do synchronization of actor internal state, you should
-- call 'clientSync' by yourself.
remoteActorCollectionServer :: forall m a b c c2 i . (MonadFix m, SyncMonad m, LoggingMonad m, ActorMonad m, NetworkMonad m, Eq i, RemoteActor i b, DynCollection c, FilterConstraint c (GameWireIndexed m i a b), FilterConstraint c (Either () (b, i)), F.Foldable c2, Functor c2)
=> c (GameActor m i a b) -- ^ Initial set of actors
-> GameActor m RemActorCollId (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b)
remoteActorCollectionServer initialActors = do
registerRemoteActor (Proxy :: Proxy i)
makeActor $ \cid -> proc (a, addEvent, remEvent) -> do
liftGameMonadOnce (registerActorTypeRepM (Proxy :: Proxy i)) -< ()
(bs, is) <- dynCollection' cid -< (a, addEvent, remEvent)
respondRequestOthers cid -< is
returnA -< bs
where
-- | If any client asks for other actors, send ids
respondRequestOthers :: RemActorCollId -> GameWire m (c i) ()
respondRequestOthers cid = onPeers listenPeers
where
listenPeers :: S.Seq Peer -> GameWire m (c i) ()
listenPeers peers = proc is -> do
sequenceA (listenPeer <$> peers) -< is
returnA -< ()
listenPeer :: Peer -> GameWire m (c i) ()
listenPeer peer = proc is -> do
e <- filterMsgs isRemActorCollRequestOthers . peerIndexedMessages peer (ChannelID 0) cid -< ()
let msgs = RemActorCollNetSpawn . toCounter <$> is
peerSendIndexedMany peer (ChannelID 0) cid ReliableMessage -< const msgs <$> e
returnA -< ()
-- | Modified dynamic collection implementation
dynCollection' cid = mkGen $ \ds input -> do
arrs <- sequence initialActors
go arrs ds input
where
-- | Send ids to clients
sendSpawnMsgs is = do
peers <- networkPeersM
F.forM_ peers $ \peer -> F.forM_ is $ \i ->
peerSendIndexedM peer (ChannelID 0) cid ReliableMessage $ RemActorCollNetSpawn i
-- | Send ids to clients
sendDespawnMsgs is = do
peers <- networkPeersM
F.forM_ peers $ \peer -> F.forM_ is $ \i ->
peerSendIndexedM peer (ChannelID 0) cid ReliableMessage $ RemActorCollNetDespawn i
-- | Each iteration do
go :: c (GameWireIndexed m i a b)
-> GameTime
-> (a, Event (c (GameActor m i a b)), Event (c2 i))
-> GameMonadT m (Either () (c b, c i), GameWire m (a, Event (c (GameActor m i a b)), Event (c2 i)) (c b, c i))
go currentWires ds (a, addEvent, removeEvent) = do
-- Adding new wires
newAddedWires <- onEvent currentWires addEvent $ \newActors -> do
addWires <- sequence newActors
sendSpawnMsgs $ toCounter . indexedId <$> addWires
return $ currentWires `concatDynColl` addWires
-- Removing wires
newRemovedWires <- onEvent newAddedWires removeEvent $ \ids -> do
sendDespawnMsgs $ toCounter <$> 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)
-- Attach ids
let is = indexedId <$> newRemovedWires :: c i
let bs' = (\(eb, i) -> (, i) <$> eb) <$> bs `zipDynColl` is :: c (Either () (b, i))
let bs'' = unzipDynColl $ rightsDynColl bs' :: (c b, c i)
return $ length newWires `seq` length is `seq` (Right bs'', mkGen $ go newWires)
-- | Client side collection of network actors that are automatically
-- synchronized to remote clients (creation and removing of actors).
--
-- Note: the collection doesn't do synchronization of actor internal state, you should
-- call 'serverSync' by yourself.
remoteActorCollectionClient :: forall m i a b . (SyncMonad m, LoggingMonad m, ActorMonad m, NetworkMonad m, Eq i, RemoteActor i b)
=> RemActorCollId -- ^ Corresponding server collection id
-> Peer -- ^ Server peer
-> (i -> GameActor m i a b) -- ^ How to construct client side actors
-> GameActor m RemActorCollId a (S.Seq b)
remoteActorCollectionClient cid peer mkActor = do
registerRemoteActor (Proxy :: Proxy i)
makeFixedActor cid $ proc a -> do
peerSendIndexed peer (ChannelID 0) cid ReliableMessage . now -< RemActorCollRequestOthers
emsgs <- peerIndexedMessages peer (ChannelID 0) cid -< ()
addEvent <- mapE (fmap $ \(RemActorCollNetSpawn i) -> fromCounter i) . filterMsgs isRemActorCollNetSpawn -< emsgs
remEvent <- mapE (fmap $ \(RemActorCollNetDespawn i) -> fromCounter i) . filterMsgs isRemActorCollNetDespawn -< emsgs
dynCollection emptyDynColl -< (a, fmap mkActor <$> addEvent, remEvent)