gore-and-ash-sync-1.2.0.1: Gore&Ash module for high level network synchronization

Copyright(c) Anton Gushcha, 2015-2016
LicenseBSD3
Maintainerncrashed@gmail.com
Stabilityexperimental
PortabilityPOSIX
Safe HaskellNone
LanguageHaskell2010

Game.GoreAndAsh.Sync

Contents

Description

The core module contains high-level networking API for Gore&Ash. It allows to define separate types of messages for each actor and perform automatic synchronzation controlled by synchronization EDSL.

The module depends on following core modules:

So SyncT should be placed after LoggingT, ActorT and NetworkT in monad stack.

The module is NOT pure within first phase (see ModuleStack docs), 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 [LoggingT, NetworkT, ActorT, SyncT ... 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, MonadThrow, MonadCatch, LoggingMonad, NetworkMonad, ActorMonad, SyncMonad ... other modules monads ... )

-- | Current GHC (7.10.3) isn't able to derive this
instance SyncMonad AppMonad where 
  getSyncIdM = AppMonad . getSyncIdM
  getSyncTypeRepM = AppMonad . getSyncTypeRepM
  registerSyncIdM = AppMonad . registerSyncIdM
  addSyncTypeRepM a b = AppMonad $ addSyncTypeRepM a b
  syncScheduleMessageM peer ch i mt msg  = AppMonad $ syncScheduleMessageM peer ch i mt msg
  syncSetLoggingM = AppMonad . syncSetLoggingM
  syncSetRoleM = AppMonad . syncSetRoleM
  syncGetRoleM = AppMonad syncGetRoleM
  syncRequestIdM a b = AppMonad $ syncRequestIdM a b 

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

Important note, the system tries to use channel id 1 for service messages, but fallbacks to default channel if there is only one channel allocated in network module. Check initalization of network module, client and server allocated channels count must match.

Synopsis

Low-level API

data SyncState s Source #

Inner state of sync module

s
- State of next module, the states are chained via nesting.

Instances

Generic (SyncState s) Source # 

Associated Types

type Rep (SyncState s) :: * -> * #

Methods

from :: SyncState s -> Rep (SyncState s) x #

to :: Rep (SyncState s) x -> SyncState s #

NFData s => NFData (SyncState s) Source # 

Methods

rnf :: SyncState s -> () #

Monad m => MonadState (SyncState s) (SyncT s m) 

Methods

get :: SyncT s m (SyncState s)

put :: SyncState s -> SyncT s m ()

state :: (SyncState s -> (a, SyncState s)) -> SyncT s m a

type Rep (SyncState s) Source # 
type Rep (SyncState s) = D1 (MetaData "SyncState" "Game.GoreAndAsh.Sync.State" "gore-and-ash-sync-1.2.0.1-C0M5s2yQvsxJQyH5pbk8Jg" False) (C1 (MetaCons "SyncState" PrefixI True) ((:*:) ((:*:) (S1 (MetaSel (Just Symbol "syncNextState") NoSourceUnpackedness SourceStrict DecidedStrict) (Rec0 s)) ((:*:) (S1 (MetaSel (Just Symbol "syncIdMap") NoSourceUnpackedness SourceStrict DecidedStrict) (Rec0 (HashMap HashableTypeRep Word64))) (S1 (MetaSel (Just Symbol "syncIdMapRev") NoSourceUnpackedness SourceStrict DecidedStrict) (Rec0 (HashMap Word64 HashableTypeRep))))) ((:*:) ((:*:) (S1 (MetaSel (Just Symbol "syncNextId") NoSourceUnpackedness SourceStrict DecidedUnpack) (Rec0 Word64)) (S1 (MetaSel (Just Symbol "syncScheduledMessages") NoSourceUnpackedness SourceStrict DecidedStrict) (Rec0 (HashMap Peer (Seq (String, ChannelID, Word64 -> Message)))))) ((:*:) (S1 (MetaSel (Just Symbol "syncLogging") NoSourceUnpackedness SourceStrict DecidedStrict) (Rec0 Bool)) (S1 (MetaSel (Just Symbol "syncRole") NoSourceUnpackedness SourceStrict DecidedStrict) (Rec0 SyncRole))))))

data SyncT s m a Source #

Monad transformer of sync 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 [LoggingT, NetworkT, ActorT, SyncT, ... other modules ... ] IO

-- | Current GHC (7.10.3) isn't able to derive this
instance SyncMonad AppMonad where
  getSyncIdM = AppMonad . getSyncIdM
  getSyncTypeRepM = AppMonad . getSyncTypeRepM
  registerSyncIdM = AppMonad . registerSyncIdM
  addSyncTypeRepM a b = AppMonad $ addSyncTypeRepM a b
  syncScheduleMessageM peer ch i mt msg  = AppMonad $ syncScheduleMessageM peer ch i mt msg
  syncSetLoggingM = AppMonad . syncSetLoggingM
  syncSetRoleM = AppMonad . syncSetRoleM
  syncGetRoleM = AppMonad syncGetRoleM
  syncRequestIdM a b = AppMonad $ syncRequestIdM a b

newtype AppMonad a = AppMonad (AppStack a)
  deriving (Functor, Applicative, Monad, MonadFix, MonadIO, LoggingMonad, MonadThrow, MonadCatch, NetworkMonad, ActorMonad)

The module is NOT pure within first phase (see ModuleStack docs), therefore currently only IO end monad can handler the module.

Instances

MonadTrans (SyncT s) Source # 

Methods

lift :: Monad m => m a -> SyncT s m a #

Monad m => MonadState (SyncState s) (SyncT s m) Source # 

Methods

get :: SyncT s m (SyncState s)

put :: SyncState s -> SyncT s m ()

state :: (SyncState s -> (a, SyncState s)) -> SyncT s m a

Monad m => Monad (SyncT s m) Source # 

Methods

(>>=) :: SyncT s m a -> (a -> SyncT s m b) -> SyncT s m b #

(>>) :: SyncT s m a -> SyncT s m b -> SyncT s m b #

return :: a -> SyncT s m a #

fail :: String -> SyncT s m a #

Functor m => Functor (SyncT s m) Source # 

Methods

fmap :: (a -> b) -> SyncT s m a -> SyncT s m b #

(<$) :: a -> SyncT s m b -> SyncT s m a #

MonadFix m => MonadFix (SyncT s m) Source # 

Methods

mfix :: (a -> SyncT s m a) -> SyncT s m a #

Monad m => Applicative (SyncT s m) Source # 

Methods

pure :: a -> SyncT s m a #

(<*>) :: SyncT s m (a -> b) -> SyncT s m a -> SyncT s m b #

(*>) :: SyncT s m a -> SyncT s m b -> SyncT s m b #

(<*) :: SyncT s m a -> SyncT s m b -> SyncT s m a #

MonadIO m => MonadIO (SyncT s m) Source # 

Methods

liftIO :: IO a -> SyncT s m a #

MonadMask m => MonadMask (SyncT s m) Source # 

Methods

mask :: ((forall a. SyncT s m a -> SyncT s m a) -> SyncT s m b) -> SyncT s m b

uninterruptibleMask :: ((forall a. SyncT s m a -> SyncT s m a) -> SyncT s m b) -> SyncT s m b

MonadThrow m => MonadThrow (SyncT s m) Source # 

Methods

throwM :: Exception e => e -> SyncT s m a

MonadCatch m => MonadCatch (SyncT s m) Source # 

Methods

catch :: Exception e => SyncT s m a -> (e -> SyncT s m a) -> SyncT s m a

MonadIO m => SyncMonad (SyncT s m) Source # 

Methods

getSyncIdM :: HashableTypeRep -> SyncT s m (Maybe Word64) Source #

getSyncTypeRepM :: Word64 -> SyncT s m (Maybe HashableTypeRep) Source #

registerSyncIdM :: HashableTypeRep -> SyncT s m Word64 Source #

addSyncTypeRepM :: HashableTypeRep -> Word64 -> SyncT s m () Source #

syncScheduleMessageM :: (NetworkMonad (SyncT s m), LoggingMonad (SyncT s m), NetworkMessage i, Serialize (NetworkMessageType i)) => Peer -> ChannelID -> i -> MessageType -> NetworkMessageType i -> SyncT s m () Source #

syncSetLoggingM :: Bool -> SyncT s m () Source #

syncSetRoleM :: SyncRole -> SyncT s m () Source #

syncGetRoleM :: SyncT s m SyncRole Source #

syncRequestIdM :: (ActorMonad (SyncT s m), NetworkMonad (SyncT s m), LoggingMonad (SyncT s m), NetworkMessage i) => Peer -> proxy i -> SyncT s m () Source #

type ModuleState (SyncT s m) Source # 
type ModuleState (SyncT s m) = SyncState s

data SyncRole Source #

Defines behavior in synchronization for actor ids

Constructors

SyncMaster

Registers types of actors

SyncSlave

Always ask for ids from other nodes

Instances

Enum SyncRole Source # 
Eq SyncRole Source # 
Show SyncRole Source # 
Generic SyncRole Source # 

Associated Types

type Rep SyncRole :: * -> * #

Methods

from :: SyncRole -> Rep SyncRole x #

to :: Rep SyncRole x -> SyncRole #

NFData SyncRole Source # 

Methods

rnf :: SyncRole -> () #

type Rep SyncRole Source # 
type Rep SyncRole = D1 (MetaData "SyncRole" "Game.GoreAndAsh.Sync.State" "gore-and-ash-sync-1.2.0.1-C0M5s2yQvsxJQyH5pbk8Jg" False) ((:+:) (C1 (MetaCons "SyncMaster" PrefixI False) U1) (C1 (MetaCons "SyncSlave" PrefixI False) U1))

class MonadIO m => SyncMonad m where Source #

Low level API for module Need at least one network channel to operate. If you open more than one channel, the module would use chanel id 1 as service channel, therefore count of channels on client and server should match (server won't response on channel 1 if it doesn't have it).

Methods

getSyncIdM :: HashableTypeRep -> m (Maybe Word64) Source #

Find actor id by it stable type representation

getSyncTypeRepM :: Word64 -> m (Maybe HashableTypeRep) Source #

Find actor type representation by it id

registerSyncIdM :: LoggingMonad m => HashableTypeRep -> m Word64 Source #

Generate and register new id for given actor type representation

addSyncTypeRepM :: LoggingMonad m => HashableTypeRep -> Word64 -> m () Source #

Register new type rep with given id, doesn't overide existing records

syncScheduleMessageM :: (NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i)) => Peer -> ChannelID -> i -> MessageType -> NetworkMessageType i -> m () Source #

Send message as soon as network id of actor is resolved

syncSetLoggingM :: Bool -> m () Source #

Switch on/off detailed logging of the module

syncSetRoleM :: SyncRole -> m () Source #

Setups behavior model in synchronizing of actor ids Note: clients should be slaves and server master

syncGetRoleM :: m SyncRole Source #

Returns current behavior model in synchronizing of actor ids Note: clients should be slaves and server master

syncRequestIdM :: forall proxy i. (ActorMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i) => Peer -> proxy i -> m () Source #

Send request for given peer for id of given actor

Instances

(MonadIO (mt m), SyncMonad m, ActorMonad m, NetworkMonad m, LoggingMonad m, MonadTrans mt) => SyncMonad (mt m) Source # 

Methods

getSyncIdM :: HashableTypeRep -> mt m (Maybe Word64) Source #

getSyncTypeRepM :: Word64 -> mt m (Maybe HashableTypeRep) Source #

registerSyncIdM :: HashableTypeRep -> mt m Word64 Source #

addSyncTypeRepM :: HashableTypeRep -> Word64 -> mt m () Source #

syncScheduleMessageM :: (NetworkMonad (mt m), LoggingMonad (mt m), NetworkMessage i, Serialize (NetworkMessageType i)) => Peer -> ChannelID -> i -> MessageType -> NetworkMessageType i -> mt m () Source #

syncSetLoggingM :: Bool -> mt m () Source #

syncSetRoleM :: SyncRole -> mt m () Source #

syncGetRoleM :: mt m SyncRole Source #

syncRequestIdM :: (ActorMonad (mt m), NetworkMonad (mt m), LoggingMonad (mt m), NetworkMessage i) => Peer -> proxy i -> mt m () Source #

MonadIO m => SyncMonad (SyncT s m) Source # 

Methods

getSyncIdM :: HashableTypeRep -> SyncT s m (Maybe Word64) Source #

getSyncTypeRepM :: Word64 -> SyncT s m (Maybe HashableTypeRep) Source #

registerSyncIdM :: HashableTypeRep -> SyncT s m Word64 Source #

addSyncTypeRepM :: HashableTypeRep -> Word64 -> SyncT s m () Source #

syncScheduleMessageM :: (NetworkMonad (SyncT s m), LoggingMonad (SyncT s m), NetworkMessage i, Serialize (NetworkMessageType i)) => Peer -> ChannelID -> i -> MessageType -> NetworkMessageType i -> SyncT s m () Source #

syncSetLoggingM :: Bool -> SyncT s m () Source #

syncSetRoleM :: SyncRole -> SyncT s m () Source #

syncGetRoleM :: SyncT s m SyncRole Source #

syncRequestIdM :: (ActorMonad (SyncT s m), NetworkMonad (SyncT s m), LoggingMonad (SyncT s m), NetworkMessage i) => Peer -> proxy i -> SyncT s m () Source #

Typed message API

Example of usage of typed message API:

data Player = Player {
  playerId :: !PlayerId
, playerPos :: !(V2 Double)
, playerSize :: !Double
} deriving (Generic)

instance NFData Player 

newtype PlayerId = PlayerId { unPlayerId :: Int } deriving (Eq, Show, Generic) 
instance NFData PlayerId 
instance Hashable PlayerId 
instance Serialize PlayerId

-- | Local message type
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

-- | Remote message type
data PlayerNetMessage = 
    NetMsgPlayerFire !(V2 Double)
  deriving (Generic, Show)

instance NFData PlayerNetMessage
instance Serialize PlayerNetMessage

instance NetworkMessage PlayerId where 
  type NetworkMessageType PlayerId = PlayerNetMessage

playerActorServer :: :: (PlayerId -> Player) -> AppActor PlayerId Game Player 
playerActorServer initialPlayer = makeActor $ i -> stateWire (initialPlayer i) $ mainController i
  where
  mainController i = proc (g, p) -> do
    p2 <- peerProcessIndexedM peer (ChannelID 0) i netProcess -< p
    forceNF . playerShot -< p2
    where
    -- | Shortcut for peer
    peer = playerPeer $ initialPlayer i

    -- | Handle when player is shot
    playerShot :: AppWire Player Player
    playerShot = proc p -> do 
      emsg <- actorMessages i isPlayerShotMessage -< ()
      let newPlayer = p {
          playerPos = 0
        }
      returnA -< event p (const newPlayer) emsg

    -- | Process player specific net messages
    netProcess :: Player -> PlayerNetMessage -> GameMonadT AppMonad Player 
    netProcess p msg = case msg of 
      NetMsgPlayerFire v -> do 
        let d = normalize v 
            v2 a = V2 a a
            pos = playerPos p + d * v2 (playerSize p * 1.5)
            vel = d * v2 bulletSpeed
        putMsgLnM $ "Fire bullet at " <> pack (show pos) <> " with velocity " <> pack (show vel)
        actors <- calculatePlayersOnLine pos vel
        forM_ actors . actorSendM actor . PlayerShotMessage . playerId $ p
        return p 

playerActorClient :: Peer -> PlayerId -> AppActor PlayerId Camera Player 
playerActorClient peer i = makeFixedActor i $ stateWire initialPlayer $ proc (c, p) -> do 
  processFire -< (c, p)
  liftGameMonad4 renderSquare -< (playerSize p, playerPos p, playerColor p, c)
  forceNF -< p
  where
    initialPlayer = Player {
        playerId = i 
      , playerPos = 0
      , playerColor = V3 1 0 0
      , playerRot = 0
      , playerSpeed = 0.5
      , playerSize = 1
      }

    processFire :: AppWire (Camera, Player) ()
    processFire = proc (c, p) -> do 
      e <- mouseClick ButtonLeft -< ()
      let wpos = cameraToWorld c $ e
      let edir = (v -> normalize $ v - playerPos p) $ wpos 
      let emsg = NetMsgPlayerFire $ edir
      peerSendIndexed peer (ChannelID 0) i ReliableMessage -< emsg
      returnA -< ()

class ActorMessage i => NetworkMessage i Source #

Extension for actor message, messages that are sent to remote host

Associated Types

type NetworkMessageType i :: * Source #

Corresponding message payload for i identifier, usually ADT

Getting messages

peerIndexedMessages Source #

Arguments

:: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i)) 
=> Peer

Which peer we are listening

-> ChannelID

Which channel we are listening

-> i

ID of actor

-> GameWire m a (Event (Seq (NetworkMessageType i)))

Messages that are addressed to the actor

Fires when network messages for specific actor has arrived Note: mid-level API is not safe to use with low-level at same time as first bytes of formed message are used for actor id. So, you need to have a special forbidden id for you custom messages.

peerProcessIndexed Source #

Arguments

:: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i)) 
=> Peer

Which peer we are listening

-> ChannelID

Which channel we are listening

-> i

ID of actor

-> (a -> NetworkMessageType i -> a)

Handler of message

-> GameWire m a a

Updates a with given handler for messages

Same as peerIndexedMessages, but transforms input state with given handler

peerProcessIndexedM Source #

Arguments

:: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i)) 
=> Peer

Which peer we are listening

-> ChannelID

Which channel we are listening

-> i

ID of actor

-> (a -> NetworkMessageType i -> GameMonadT m a)

Handler of message

-> GameWire m a a

Updates a with given handler for messages

Same as peerIndexedMessages, but transforms input state with given handler, monadic version

Sending messages

peerSendIndexedM Source #

Arguments

:: (SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i)) 
=> Peer

Which peer we sending to

-> ChannelID

Which channel we are sending within

-> i

ID of actor

-> MessageType

Strategy of the message (reliable, unordered etc.)

-> NetworkMessageType i

Message to send

-> m () 

Encodes a message for specific actor type and send it to remote host Note: mid-level API is not safe to use with low-level at same time as first bytes of formed message are used for actor id. So, you need to have a special forbidden id for you custom messages.

peerSendIndexed Source #

Arguments

:: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i)) 
=> Peer

Which peer we sending to

-> ChannelID

Which channel we are sending within

-> i

ID of actor

-> MessageType

Strategy of the message (reliable, unordered etc.)

-> GameWire m (Event (NetworkMessageType i)) (Event ()) 

Encodes a message for specific actor type and send it to remote host, arrow version Note: mid-level API is not safe to use with low-level at same time as first bytes of formed message are used for actor id. So, you need to have a special forbidden id for you custom messages.

peerSendIndexedDyn Source #

Arguments

:: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i)) 
=> ChannelID

Which channel we are sending within

-> MessageType

Strategy of the message (reliable, unordered etc.)

-> GameWire m (Event (Peer, i, NetworkMessageType i)) (Event ()) 

Encodes a message for specific actor type and send it to remote host, arrow version. Takes peer, id and message as arrow input.

peerSendIndexedMany Source #

Arguments

:: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i), Foldable t) 
=> Peer

Which peer we sending to

-> ChannelID

Which channel we are sending within

-> i

ID of actor

-> MessageType

Strategy of the message (reliable, unordered etc.)

-> GameWire m (Event (t (NetworkMessageType i))) (Event ()) 

Encodes a message for specific actor type and send it to remote host, arrow version Note: mid-level API is not safe to use with low-level at same time as first bytes of formed message are used for actor id. So, you need to have a special forbidden id for you custom messages.

peerSendIndexedManyDyn Source #

Arguments

:: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i), Foldable t) 
=> ChannelID

Which channel we are sending within

-> MessageType

Strategy of the message (reliable, unordered etc.)

-> GameWire m (Event (t (Peer, i, NetworkMessageType i))) (Event ()) 

Encodes a message for specific actor type and send it to remote host, arrow version. Takes peer, id and message as arrow input.

Helpers

filterMsgs Source #

Arguments

:: Monad m 
=> (a -> Bool)

Predicate to test message

-> GameWire m (Event (Seq a)) (Event (Seq a)) 

Helper to filter output of peerIndexedMessages

Automatic synchronization

The synchronization API is built around Sync applicative functor. It allows to combine complex synchronization strategies from reasonable small amount of basic blocks (see noSync, clientSide, serverSide, condSync, syncReject).

Synchornization description is considered as complete, when you get 'Sync m i a a' type (FullSync type synonym). After that you can use clientSync and serverSync at your actors to sync them.

Example of usage of sync API:

data Player = Player {
  playerId :: !PlayerId
, playerPos :: !(V2 Double)
, playerSize :: !Double
} deriving (Generic)

instance NFData Player 

newtype PlayerId = PlayerId { unPlayerId :: Int } deriving (Eq, Show, Generic) 
instance NFData PlayerId 
instance Hashable PlayerId 
instance Serialize PlayerId

-- | Local message type
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

-- | Remote message type
data PlayerNetMessage = 
    NetMsgPlayerFire !(V2 Double)
  deriving (Generic, Show)

instance NFData PlayerNetMessage
instance Serialize PlayerNetMessage

instance NetworkMessage PlayerId where 
  type NetworkMessageType PlayerId = PlayerNetMessage

playerActorServer :: :: (PlayerId -> Player) -> AppActor PlayerId Game Player 
playerActorServer initialPlayer = makeActor $ i -> stateWire (initialPlayer i) $ mainController i
  where
  mainController i = proc (g, p) -> do
    p2 <- peerProcessIndexedM peer (ChannelID 0) i netProcess -< p
    forceNF . serverSync playerSync i . playerShot -< p2
    where
    -- | Shortcut for peer
    peer = playerPeer $ initialPlayer i

    -- | Handle when player is shot
    playerShot :: AppWire Player Player
    playerShot = proc p -> do 
      emsg <- actorMessages i isPlayerShotMessage -< ()
      let newPlayer = p {
          playerPos = 0
        }
      returnA -< event p (const newPlayer) emsg

    -- | Process player specific net messages
    netProcess :: Player -> PlayerNetMessage -> GameMonadT AppMonad Player 
    netProcess p msg = case msg of 
      NetMsgPlayerFire v -> do 
        let d = normalize v 
            v2 a = V2 a a
            pos = playerPos p + d * v2 (playerSize p * 1.5)
            vel = d * v2 bulletSpeed
        putMsgLnM $ "Fire bullet at " <> pack (show pos) <> " with velocity " <> pack (show vel)
        actors <- calculatePlayersOnLine pos vel
        forM_ actors . actorSendM actor . PlayerShotMessage . playerId $ p
        return p 

    playerSync :: FullSync AppMonad PlayerId Player 
    playerSync = Player 
      <$> pure i 
      <*> clientSide peer 0 playerPos
      <*> clientSide peer 1 playerSize

playerActorClient :: Peer -> PlayerId -> AppActor PlayerId Camera Player 
playerActorClient peer i = makeFixedActor i $ stateWire initialPlayer $ proc (c, p) -> do 
  processFire -< (c, p)
  liftGameMonad4 renderSquare -< (playerSize p, playerPos p, playerColor p, c)
  forceNF . clientSync playerSync peer i -< p
  where
    initialPlayer = Player {
        playerId = i 
      , playerPos = 0
      , playerColor = V3 1 0 0
      , playerRot = 0
      , playerSpeed = 0.5
      , playerSize = 1
      }

    processFire :: AppWire (Camera, Player) ()
    processFire = proc (c, p) -> do 
      e <- mouseClick ButtonLeft -< ()
      let wpos = cameraToWorld c $ e
      let edir = (v -> normalize $ v - playerPos p) $ wpos 
      let emsg = NetMsgPlayerFire $ edir
      peerSendIndexed peer (ChannelID 0) i ReliableMessage -< emsg
      returnA -< ()

    playerSync :: FullSync AppMonad PlayerId Player 
    playerSync = Player 
      <$> pure i 
      <*> clientSide peer 0 playerPos
      <*> clientSide peer 1 playerSize

class NetworkMessage i => RemoteActor i a | i -> a, a -> i Source #

API to support automatic synchronization of actors between client and server

Associated Types

type RemoteActorState i :: * Source #

State of remote actor (should be equal a)

type RemoteActorId a :: * Source #

Id of remote actor (should be equal i)

clientSync Source #

Arguments

:: (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

Perform client side synchronization

serverSync Source #

Arguments

:: (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

Perform server side synchronization

Synchronization primitives

data Sync m i s a Source #

Special monad that keeps info about synchronization logic between client and server for type a. Remote collection uses the description to generate special code to automatic synchronization of shared actor state.

m
means underlying game monad, that will be used during synchronization
i
means actor unique id type
s
means actor state that is beeing syncing. As soon as you crafted 'Sync i s s' it means you defined full description how to sync actor state.
a
is actual value type that the Sync value is describing synchronization for. As soon as you crafted 'Sync i s s' it means you defined full description how to sync actor state.

Instances

Functor (Sync m i s) Source # 

Methods

fmap :: (a -> b) -> Sync m i s a -> Sync m i s b #

(<$) :: a -> Sync m i s b -> Sync m i s a #

Applicative (Sync m i s) Source # 

Methods

pure :: a -> Sync m i s a #

(<*>) :: Sync m i s (a -> b) -> Sync m i s a -> Sync m i s b #

(*>) :: Sync m i s a -> Sync m i s b -> Sync m i s b #

(<*) :: Sync m i s a -> Sync m i s b -> Sync m i s a #

type FullSync m i s = Sync m i s s Source #

Type synonim for those Sync DSL programs that defines full synchronization of actor state

noSync Source #

Arguments

:: (s -> a)

Getter of the field

-> Sync m i s a 

Perphoms no synchronization, the sync primitive returns local value of field

clientSide Source #

Arguments

:: (Eq a, Serialize a, RemoteActor i s) 
=> Peer

Which peer controls the field, sync messages from other peers are not processed

-> Word64

Field id, other side actor should define clientSide with matching id

-> (s -> a)

Field getter

-> Sync m i s a 

Declares that state field is client side, i.e. it is produced in client actor and then sent to server. For peers that are not equal to specified (owner of the field) the sync behavior acts as serverSide.

If server side changes the value manually, client is forced to new server side value.

serverSide Source #

Arguments

:: (Serialize a, RemoteActor i s) 
=> Word64

Field id, other side actor should define serverSide with matching id

-> (s -> a)

Field getter

-> Sync m i s a 

Declares that state field is server side, i.e. it is produced in server actor and then sent to all clients.

Clients cannot change the value manually.

condSync Source #

Arguments

:: Monad m 
=> GameWire m s (Event b)

Wire that produces events when sync should be done

-> (s -> a)

Field getter

-> Sync m i s a

Sub action that should be done when sync event is produced

-> Sync m i s a 

Makes synchronization appear only when given wire produces an event.

Note: intended to use with serverSide

syncReject Source #

Arguments

:: (Serialize a, RemoteActor i s) 
=> GameWire m (s, a) (Event a)

Fires event when the synced value is invalid, event carries new value that should be placed and sended to remote peer

-> Word64

Id of field to resync at remote host when failed

-> Sync m i s a

Sub action that produces synced values for first argument

-> Sync m i s a 

There are sometimes net errors or malicios data change in remote actor, the action provides you ability to reject incorrect values and resync remote actor to fallback value.

Note: intended to use with serverSide

Helpers for conditional synchronization

fieldChanges Source #

Arguments

:: Eq a 
=> (s -> a)

Field getter

-> GameWire m s (Event a) 

Produces event when given field is changed

fieldChangesWithin Source #

Arguments

:: (Num a, Ord a) 
=> (s -> a)

Field getter

-> a

Delta, variation greater than the value is treated as change

-> GameWire m s (Event a) 

Produces event when given field is changed

Remote collection

newtype RemActorCollId Source #

Unique id space for remote collections actors

Constructors

RemActorCollId 

Instances

Eq RemActorCollId Source # 
Ord RemActorCollId Source # 
Show RemActorCollId Source # 
Generic RemActorCollId Source # 

Associated Types

type Rep RemActorCollId :: * -> * #

NFData RemActorCollId Source # 

Methods

rnf :: RemActorCollId -> () #

Hashable RemActorCollId Source # 
ActorMessage RemActorCollId Source # 

Associated Types

type ActorMessageType RemActorCollId :: *

NetworkMessage RemActorCollId Source # 

Associated Types

type NetworkMessageType RemActorCollId :: * Source #

type Rep RemActorCollId Source # 
type Rep RemActorCollId = D1 (MetaData "RemActorCollId" "Game.GoreAndAsh.Sync.Remote.Collection" "gore-and-ash-sync-1.2.0.1-C0M5s2yQvsxJQyH5pbk8Jg" True) (C1 (MetaCons "RemActorCollId" PrefixI True) (S1 (MetaSel (Just Symbol "unRemActorCollId") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 Int)))
type ActorMessageType RemActorCollId Source # 
type ActorMessageType RemActorCollId
type NetworkMessageType RemActorCollId Source # 

remoteActorCollectionServer Source #

Arguments

:: (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)), 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) 

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.

remoteActorCollectionClient Source #

Arguments

:: (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 (Seq b) 

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.