packages feed

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