{-# OPTIONS_GHC -fno-warn-orphans #-} {-| Module : Game.GoreAndAsh.Sync.Message Description : Arrow utilities for messaging Copyright : (c) Anton Gushcha, 2015-2016 License : BSD3 Maintainer : ncrashed@gmail.com Stability : experimental Portability : POSIX -} module Game.GoreAndAsh.Sync.Message( NetworkMessage(..) -- * Getting messages , peerIndexedMessages , peerProcessIndexed , peerProcessIndexedM -- * Sending messages , peerSendIndexedM , peerSendIndexed , peerSendIndexedDyn , peerSendIndexedMany , peerSendIndexedManyDyn -- * Helpers , filterMsgs ) where import Control.Wire import Control.Wire.Unsafe.Event import Data.Maybe import Data.Serialize import Data.Typeable import Data.Word import Prelude hiding ((.), id) import qualified Control.Monad as M import qualified Data.ByteString as BS import qualified Data.Foldable as F import qualified Data.Sequence as S import Game.GoreAndAsh import Game.GoreAndAsh.Actor import Game.GoreAndAsh.Logging import Game.GoreAndAsh.Network import Game.GoreAndAsh.Sync.API import Game.GoreAndAsh.Sync.State -- | 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. peerIndexedMessages :: forall m i a . (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 (S.Seq (NetworkMessageType i))) -- ^ Messages that are addressed to the actor peerIndexedMessages p chid i = inhibit2NoEvent $ proc _ -> do netid <- mkNetId -< () emsgs <- peerMessages p chid -< () filterE (not . S.null) . mapE (\(netid, msgs) -> catMaybesSeq $ (parse netid) <$> msgs) -< (netid, ) <$> emsgs where inhibit2NoEvent w = w <|> never -- | If actor is new, register actor and cache the id mkNetId :: GameWire m () Word64 mkNetId = go False where go sended = mkGen $ \_ _ -> do let !tr = actorFingerprint (Proxy :: Proxy i) mnid <- getSyncIdM tr case mnid of Nothing -> do r <- syncGetRoleM case r of SyncMaster -> do !nid <- registerSyncIdM tr return (Right nid, pure nid) SyncSlave -> do M.unless sended $ syncRequestIdM p (Proxy :: Proxy i) return (Left (), go True) Just !nid -> return (Right nid, pure nid) -- | Parses packet, decodes only user messages parse :: Word64 -> BS.ByteString -> Maybe (NetworkMessageType i) parse !netid !bs = case decode bs of Left _ -> Nothing Right (fp :: Word64, bs2 :: BS.ByteString) -> if fp == 0 then Nothing else case decode bs2 of Left _ -> Nothing Right (w64 :: Word64, mbs :: BS.ByteString) -> if not (fp == netid && fromIntegral w64 == toCounter i) then Nothing else case decode mbs of Left _ -> Nothing Right !m -> Just $! m -- | catMaybes for sequences catMaybesSeq :: S.Seq (Maybe a) -> S.Seq a catMaybesSeq = fmap fromJust . S.filter isJust -- | 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. peerSendIndexedM :: forall m i . (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 () peerSendIndexedM p chid i mt msg = do mnid <- getSyncIdM $ actorFingerprint (Proxy :: Proxy i) case mnid of Nothing -> syncScheduleMessageM p chid i mt msg -- schedule message send when we resolve sync id Just nid -> do let w64 = fromIntegral (toCounter i) :: Word64 msg' = Message mt $! encode (nid, encode (w64, encode msg)) peerSendM p chid msg' -- | 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. peerSendIndexed :: (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 ()) peerSendIndexed p chid i mt = liftGameMonadEvent1 $ peerSendIndexedM p chid i mt -- | Encodes a message for specific actor type and send it to remote host, arrow version. -- Takes peer, id and message as arrow input. peerSendIndexedDyn :: (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 ()) peerSendIndexedDyn chid mt = liftGameMonadEvent1 $ \(p, i, msg) -> peerSendIndexedM p chid i mt msg -- | 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. peerSendIndexedMany :: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i), F.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 ()) peerSendIndexedMany p chid i mt = liftGameMonadEvent1 . F.mapM_ $ peerSendIndexedM p chid i mt -- | Encodes a message for specific actor type and send it to remote host, arrow version. -- Takes peer, id and message as arrow input. peerSendIndexedManyDyn :: (ActorMonad m, SyncMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i), F.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 ()) peerSendIndexedManyDyn chid mt = liftGameMonadEvent1 . F.mapM_ $ \(p, i, msg) -> peerSendIndexedM p chid i mt msg -- | Same as @peerIndexedMessages@, but transforms input state with given handler peerProcessIndexed :: (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 peerProcessIndexed p chid i f = proc a -> do emsgs <- peerIndexedMessages p chid i -< () returnA -< event a (F.foldl' f a) emsgs -- | Same as @peerIndexedMessages@, but transforms input state with given handler, monadic version peerProcessIndexedM :: (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 peerProcessIndexedM p chid i f = proc a -> do emsgs <- peerIndexedMessages p chid i -< () liftGameMonad2 (\emsgs a -> case emsgs of NoEvent -> return a Event msgs -> F.foldlM f a msgs) -< (emsgs, a) -- | Helper to filter output of @peerIndexedMessages@ filterMsgs :: (Monad m) => (a -> Bool) -- ^ Predicate to test message -> GameWire m (Event (S.Seq a)) (Event (S.Seq a)) filterMsgs p = filterE (not . S.null) . mapE (S.filter p)