gore-and-ash-sync-1.1.0.0: src/Game/GoreAndAsh/Sync/Remote/Actor.hs
{-|
Module : Game.GoreAndAsh.Sync.Remote.Actor
Description : Automatic synchronization of actor
Copyright : (c) Anton Gushcha, 2015-2016
License : BSD3
Maintainer : ncrashed@gmail.com
Stability : experimental
Portability : POSIX
-}
module Game.GoreAndAsh.Sync.Remote.Actor(
RemoteActor(..)
, RemActorId(..)
, RemActorNetMessage(..)
, clientSync
, serverSync
, registerRemoteActor
) where
import Control.Monad.Fix
import Control.Wire
import Control.Wire.Unsafe.Event (event, Event(..))
import Data.Proxy
import Data.Serialize
import Data.Word
import GHC.Generics
import Prelude hiding ((.), id)
import qualified Data.ByteString as BS
import qualified Data.Sequence as S
import Game.GoreAndAsh
import Game.GoreAndAsh.Actor
import Game.GoreAndAsh.Network
import Game.GoreAndAsh.Logging
import Game.GoreAndAsh.Sync.Message
import Game.GoreAndAsh.Sync.Remote.Sync
import Game.GoreAndAsh.Sync.API
-- | Id of synchronization actor build over another actor
newtype RemActorId i = RemActorId { unRemActorId :: i }
deriving (Show, Eq, Ord, Generic)
-- | Need to be called once for each remote actor (remote collections actually do this)
registerRemoteActor :: forall proxy i a m . (ActorMonad m, ActorMessage i, RemoteActor i a)
=> proxy i -- ^ Proxy of type of base actor id
-> GameMonadT m () -- ^ Register basic id
registerRemoteActor _ = registerActorTypeRepM (Proxy :: Proxy (RemActorId i))
-- | Stub for local remote actor API
data RemActorMessage i
instance RemoteActor i a => ActorMessage (RemActorId i) where
type ActorMessageType (RemActorId i) = RemActorMessage i
toCounter = toCounter . unRemActorId
fromCounter = RemActorId . fromCounter
-- | Network protocol of synchronization actor
data RemActorNetMessage i =
RemActorSyncRequest -- ^ Request full synchronization
| RemActorSyncValue !Word64 !BS.ByteString -- ^ Carries value of indexed value
deriving (Generic, Show)
instance Serialize (RemActorNetMessage i)
-- | Filter helper to pass through only RemActorSyncRequest
isRemActorSyncRequest :: RemActorNetMessage i -> Bool
isRemActorSyncRequest m = case m of
RemActorSyncRequest -> True
_ -> False
-- | Filter helper to pass through only RemActorSyncValue
isRemActorSyncValue :: Word64 -> RemActorNetMessage i -> Bool
isRemActorSyncValue ia m = case m of
RemActorSyncValue ib _ -> ia == ib
_ -> False
-- | Helper to construct sync message
mkSyncMessage :: Dict (Serialize a) -> Word64 -> a -> RemActorNetMessage i
mkSyncMessage d w a = RemActorSyncValue w (encodish d a)
-- | Helper to parse sync message
fromSyncMessage :: Dict (Serialize a) -> RemActorNetMessage i -> Maybe a
fromSyncMessage d m = case m of
RemActorSyncRequest -> Nothing
RemActorSyncValue _ bs -> case decodish d bs of
Left _ -> Nothing
Right a -> Just a
-- | Helper to deserialize only last message
fromSyncMessageLast :: Dict (Serialize a) -> S.Seq (RemActorNetMessage i) -> Maybe a
fromSyncMessageLast d s = case S.viewr s of
S.EmptyR -> Nothing
_ S.:> m -> fromSyncMessage d m
instance RemoteActor i a => NetworkMessage (RemActorId i) where
type NetworkMessageType (RemActorId i) = RemActorNetMessage i
-- | Helper to run @peerSendIndexedM@ with given dictionary
peerSendRemoteActorMsg :: (ActorMonad m, NetworkMonad m, LoggingMonad m, SyncMonad m)
=> Dict (RemoteActor i a) -- ^ Dictionary from Sync AST
-> Peer -> ChannelID -> RemActorId i -> MessageType -> RemActorNetMessage i
-> GameMonadT m ()
peerSendRemoteActorMsg Dict = peerSendIndexedM
-- | Helper to run @peerIndexedMessages@ with given dictionary
peerListenRemoteActor :: (ActorMonad m, NetworkMonad m, LoggingMonad m, SyncMonad m)
=> Dict (RemoteActor i a) -- ^ Dictionary from Sync AST
-> Peer -> ChannelID -> RemActorId i
-> GameWire m () (Event (S.Seq (RemActorNetMessage i)))
peerListenRemoteActor Dict = peerIndexedMessages
-- | Sends all data to remote client
serverFullSync :: (SyncMonad m, ActorMonad m, NetworkMonad m, LoggingMonad m)
=> Sync m i s a -- ^ Strategy of syncing
-> Peer -- ^ Which client to send
-> i -- ^ Id of actor
-> GameWire m s a
serverFullSync ms peer i = case ms of
SyncPure a -> pure a -- Client already knows the constant value
SyncNone sa -> arr sa
SyncClient Dict _ w sa -> liftGameMonad1 $ \s -> do
let val = sa s
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage . mkSyncMessage Dict w $! val
return val
SyncServer Dict w sa -> liftGameMonad1 $ \s -> do
let val = sa s
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage . mkSyncMessage Dict w $! val
return val
SyncCond _ _ ma -> serverFullSync ma peer i
SyncReject _ _ _ ma -> serverFullSync ma peer i
SyncApp mf ma -> proc s -> do
f <- serverFullSync mf peer i -< s
a <- serverFullSync ma peer i -< s
returnA -< f a
-- | Sends all data to remote server
clientFullSync :: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m)
=> Sync m i s a -- ^ Sync strategy
-> Peer -- ^ Server connection
-> i -- ^ Actor id
-> GameWire m s a -- ^ Synchronizing of client state
clientFullSync ms peer i = case ms of
SyncPure a -> pure a -- Client already knows the constant value
SyncNone sa -> arr sa
SyncClient Dict _ w sa -> liftGameMonad1 $ \s -> do
let val = sa s
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage . mkSyncMessage Dict w $! val
return val
SyncServer _ _ sa -> arr sa -- The value is server side
SyncCond _ _ ma -> clientFullSync ma peer i
SyncReject _ _ _ ma -> clientFullSync ma peer i
SyncApp mf ma -> proc s -> do
f <- clientFullSync mf peer i -< s
a <- clientFullSync ma peer i -< s
returnA -< f a
-- | Perform partial client side synchronization
clientPartialSync :: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m)
=> Sync m i s a -- ^ Sync strategy
-> Peer -- ^ Server connection
-> i -- ^ Actor id
-> GameWire m s a -- ^ Synchronizing of client state
clientPartialSync ms peer i = case ms of
SyncPure a -> pure a
SyncNone sa -> arr sa
SyncClient Dict _ w sa -> proc s -> do
emsgs <- filterMsgs (isRemActorSyncValue w) . peerListenRemoteActor Dict peer (ChannelID 0) (RemActorId i) -< ()
emsg <- filterJustE . mapE (fromSyncMessageLast Dict) -< emsgs
case emsg of
NoEvent -> do
let val = event (sa s) id emsg
liftGameMonad1 (
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage
. mkSyncMessage Dict w) -< val
returnA -< val
Event val -> returnA -< val
SyncServer Dict w sa -> proc s -> do
emsgs <- filterMsgs (isRemActorSyncValue w) . peerListenRemoteActor Dict peer (ChannelID 0) (RemActorId i) -< ()
emsg <- filterJustE . mapE (fromSyncMessageLast Dict) -< emsgs
returnA -< event (sa s) id emsg
SyncCond w sa ma -> proc s -> do
e <- w -< s
case e of
NoEvent -> returnA -< sa s
Event _ -> clientPartialSync ma peer i -< s
SyncReject Dict w wid ma -> proc s -> do
a <- clientPartialSync ma peer i -< s
e <- w -< (s, a)
case e of
NoEvent -> returnA -< a
Event a' -> do
syncPeer -< a'
returnA -< a'
where
syncPeer = liftGameMonad1 $ do
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage
. mkSyncMessage Dict wid
SyncApp mf ma -> proc s -> do
f <- clientPartialSync mf peer i -< s
a <- clientPartialSync ma peer i -< s
returnA -< f a
-- | Perform partial server side synchronization
serverPartialSync :: (MonadFix m, ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m)
=> Sync m i s a -- ^ Sync strategy
-> i -- ^ Actor id
-> GameWire m s a -- ^ Transformer of state
serverPartialSync ms i = case ms of
SyncPure a -> pure a
SyncNone sa -> arr sa
SyncClient Dict сpeer w sa -> onPeers $ \peers -> proc s -> do
ea <- listenPeer -< s
let a = sa s
a' = event (sa s) id ea
sequenceA (liftGameMonad1 . syncPeer <$> S.filter (/= сpeer) peers) -< a'
serverChanged -< (a, a')
returnA -< a'
where
-- Listen field owner about updates
listenPeer = proc _ -> do
emsgs <- filterMsgs (isRemActorSyncValue w) . peerListenRemoteActor Dict сpeer (ChannelID 0) (RemActorId i) -< ()
filterJustE . mapE (fromSyncMessageLast Dict) -< emsgs
-- Sync given peer field state
syncPeer peer = do
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage
. mkSyncMessage Dict w
-- Detect when server changes the value and resync owner
serverChanged = mkSFN $ \(_, a') -> ((), go a')
where
go v = mkGen $ \_ (a, a') -> if v == a
then return (Right (), go a')
else do
syncPeer сpeer a
return (Right (), go a')
SyncServer Dict w sa -> onPeers $ \peers -> proc s -> do
let val = sa s
sequenceA (syncPeer <$> peers) -< val
returnA -< val
where
syncPeer peer = liftGameMonad1 $ do
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage
. mkSyncMessage Dict w
SyncCond w sa ma -> proc s -> do
e <- w -< s
case e of
NoEvent -> returnA -< sa s
Event _ -> serverPartialSync ma i -< s
SyncReject Dict w wid ma -> proc s -> do
a <- serverPartialSync ma i -< s
e <- w -< (s, a)
case e of
NoEvent -> returnA -< a
Event a' -> do
onPeers (sequenceA . fmap syncPeer) -< a'
returnA -< a'
where
syncPeer peer = liftGameMonad1 $ do
peerSendRemoteActorMsg Dict peer (ChannelID 0) (RemActorId i) ReliableMessage
. mkSyncMessage Dict wid
SyncApp mf ma -> proc s -> do
f <- serverPartialSync mf i -< s
a <- serverPartialSync ma i -< s
returnA -< f a
-- | Perform client side synchronization
clientSync :: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, RemoteActor i a)
=> Sync m i s a -- ^ Sync strategy
-> Peer -- ^ Server connection
-> i -- ^ Actor id
-> GameWire m s a -- ^ Synchronizing of client state
clientSync ms peer i = proc s -> do
peerSendIndexed peer (ChannelID 0) (RemActorId i) ReliableMessage . now -< RemActorSyncRequest
syncOnRequest -< s
clientPartialSync ms peer i -< s
where
syncOnRequest = proc s -> do
emsgs <- filterMsgs isRemActorSyncRequest . peerIndexedMessages peer (ChannelID 0) (RemActorId i) -< ()
case emsgs of
NoEvent -> returnA -< ()
Event _ -> pure () . clientFullSync ms peer i -< s
-- | Perform server side synchronization
serverSync :: (MonadFix m, ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, RemoteActor i a)
=> Sync m i s a -- ^ Sync strategy
-> i -- ^ Actor id
-> GameWire m s a -- ^ Synchronizing of server state
serverSync ms i = proc s -> do
syncOnRequest -< s
serverPartialSync ms i -< s
where
syncOnRequest = onPeers $ \peers -> sequenceA $ syncOnRequestPeer <$> peers
syncOnRequestPeer peer = proc s -> do
emsgs <- filterMsgs isRemActorSyncRequest . peerIndexedMessages peer (ChannelID 0) (RemActorId i) -< ()
case emsgs of
NoEvent -> returnA -< ()
Event _ -> pure () . serverFullSync ms peer i -< s