packages feed

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)