{-| Module : Game.GoreAndAsh.Network.API Description : Monadic and arrow API for network core module Copyright : (c) Anton Gushcha, 2015-2016 License : BSD3 Maintainer : ncrashed@gmail.com Stability : experimental Portability : POSIX The module contains monadic and arrow API of network core module. -} module Game.GoreAndAsh.Network.API( NetworkMonad(..) -- * Peer handling , peersConnected , peersDisconnected , peerDisconnected , currentPeers , onPeers -- * Messaging support , peerMessages , peerSend , peerSendMany ) where import Control.DeepSeq hiding (force) import Control.Exception.Base (IOException) import Control.Monad.Catch import Control.Monad.State.Strict import Control.Wire hiding (when) import Control.Wire.Unsafe.Event import Data.Maybe (fromMaybe) import Data.Text import Foreign import Game.GoreAndAsh import Game.GoreAndAsh.Logging import Game.GoreAndAsh.Network.Message import Game.GoreAndAsh.Network.Module import Game.GoreAndAsh.Network.State import Network.ENet.Host import Network.ENet.Packet as P import Network.ENet.Peer import Network.Socket (SockAddr) import Prelude hiding ((.), id) import qualified Data.ByteString as BS import qualified Data.Foldable as F import qualified Data.HashMap.Strict as H import qualified Data.Sequence as S -- | Low-level monadic API of the core module class (MonadIO m, MonadCatch m) => NetworkMonad m where -- | Start listening for messages, should be called once networkBind :: LoggingMonad m => Maybe SockAddr -- ^ Address to listen, Nothing is client -> Word -- ^ Maximum count of connections -> Word -- ^ Number of channels to open -> Word32 -- ^ Incoming max bandwidth -> Word32 -- ^ Outcoming max bandwidth -> m () -- | Returns peers that were connected during last frame peersConnectedM :: m (S.Seq Peer) -- | Returns peers that were disconnected during last frame peersDisconnectedM :: m (S.Seq Peer) -- | Initiate connection to the remote host networkConnect :: LoggingMonad m => SockAddr -- ^ Address of host -> Word -- ^ Count of channels to open -> Word32 -- ^ Additional data (0 default) -> m (Maybe ()) -- | Returns received packets for given peer and channel peerMessagesM :: Peer -> ChannelID -> m (S.Seq BS.ByteString) -- | Sends a packet to given peer on given channel peerSendM :: LoggingMonad m => Peer -> ChannelID -> Message -> m () -- | Returns list of currently connected peers (servers on client side, clients on server side) networkPeersM :: m (S.Seq Peer) -- | Sets flag for detailed logging (for debug) networkSetDetailedLoggingM :: Bool -> m () -- | Return count of allocated network channels networkChannels :: m Word instance {-# OVERLAPPING #-} (MonadIO m, MonadCatch m) => NetworkMonad (NetworkT s m) where networkBind addr conCount chanCount inBandth outBandth = do nstate <- NetworkT get phost <- liftIO $ create addr (fromIntegral conCount) (fromIntegral chanCount) inBandth outBandth if phost == nullPtr then case addr of Nothing -> putMsgLnM "Network: failed to initalize client side" Just a -> putMsgLnM $ "Network: failed to connect to " <> pack (show a) else do when (networkDetailedLogging nstate) $ putMsgLnM $ case addr of Nothing -> "Network: client network system initalized" Just a -> "Network: binded to " <> pack (show a) NetworkT $ put $ nstate { networkHost = Just phost , networkMaximumChannels = chanCount } peersConnectedM = do NetworkState{..} <- NetworkT get return networkConnectedPeers peersDisconnectedM = do NetworkState{..} <- NetworkT get return networkDisconnectedPeers networkConnect addr chanCount datum = do nstate <- NetworkT get case networkHost nstate of Nothing -> do putMsgLnM $ "Network: cannot connect to " <> pack (show addr) <> ", system isn't initalized" return $ Just () Just host -> do peer <- liftIO $ connect host addr (fromIntegral chanCount) datum if peer == nullPtr then do putMsgLnM $ "Network: failed to connect to " <> pack (show addr) return Nothing else do NetworkT . put $! nstate { networkMaximumChannels = chanCount } return $ Just () peerMessagesM peer ch = do msgs <- networkMessages <$> NetworkT get return . fromMaybe S.empty $! H.lookup (peer, ch) msgs peerSendM peer ch msg = do nstate <- NetworkT get when (networkDetailedLogging nstate) $ putMsgLnM $ "Network: sending packet via channel " <> pack (show ch) <> ", payload: " <> pack (show msg) -- IOError let sendAction = liftIO $ send peer ch =<< P.poke (messageToPacket msg) catch sendAction $ \(e :: IOException) -> do putMsgLnM $ "Network: failed to send packet '" <> pack (show e) <> "'" networkPeersM = do NetworkState{..} <- NetworkT get return $! networkPeers S.>< networkConnectedPeers networkSetDetailedLoggingM f = do s <- NetworkT get put $ s { networkDetailedLogging = f } networkChannels = do s <- NetworkT get return $ networkMaximumChannels s instance {-# OVERLAPPABLE #-} (MonadIO (mt m), MonadCatch (mt m), LoggingMonad m, NetworkMonad m, MonadTrans mt) => NetworkMonad (mt m) where networkBind a mc mch ib ob = lift $ networkBind a mc mch ib ob peersConnectedM = lift peersConnectedM peersDisconnectedM = lift peersDisconnectedM networkConnect a b c = lift $ networkConnect a b c peerMessagesM a b = lift $ peerMessagesM a b peerSendM a b c = lift $ peerSendM a b c networkPeersM = lift networkPeersM networkSetDetailedLoggingM = lift . networkSetDetailedLoggingM networkChannels = lift networkChannels -- | Fires when one or several clients were connected peersConnected :: (LoggingMonad m, NetworkMonad m) => GameWire m a (Event (S.Seq Peer)) peersConnected = mkGen_ $ \_ -> do ps <- peersConnectedM return $! if S.null ps then Right NoEvent else ps `deepseq` Right (Event ps) -- | Fires when one of connected peers is disconnected for some reason peersDisconnected :: (LoggingMonad m, NetworkMonad m) => GameWire m a (Event (S.Seq Peer)) peersDisconnected = mkGen_ $ \_ -> do ps <- peersDisconnectedM return $! if S.null ps then Right NoEvent else ps `deepseq` Right (Event ps) -- | Fires when statically known peer is disconnected peerDisconnected :: (LoggingMonad m, NetworkMonad m) => Peer -> GameWire m a (Event ()) peerDisconnected p = mkGen_ $ \_ -> do ps <- peersDisconnectedM return $! case F.find (p ==) ps of Nothing -> Right NoEvent Just _ -> Right $! Event () -- | Returns list of current peers (clients on server, servers on client) currentPeers :: (LoggingMonad m, NetworkMonad m) => GameWire m a (S.Seq Peer) currentPeers = liftGameMonad networkPeersM -- | Returns sequence of packets that were recieved during last frame from given peer and channel id peerMessages :: (LoggingMonad m, NetworkMonad m) => Peer -> ChannelID -> GameWire m a (Event (S.Seq BS.ByteString)) peerMessages p ch = mkGen_ $ \_ -> do msgs <- peerMessagesM p ch return $! if S.null msgs then Right NoEvent else msgs `deepseq` Right (Event msgs) -- | Send message to given peer with given channel id peerSend :: (LoggingMonad m, NetworkMonad m) => Peer -> ChannelID -> GameWire m (Event Message) (Event ()) peerSend peer chid = liftGameMonadEvent1 $ peerSendM peer chid -- | Send several messages to given peer with given channel id peerSendMany :: (LoggingMonad m, NetworkMonad m, F.Foldable t) => Peer -> ChannelID -> GameWire m (Event (t Message)) (Event ()) peerSendMany peer chid = liftGameMonadEvent1 $ mapM_ (peerSendM peer chid) -- | Sometimes you want to listen all peers and use statefull computations at the same time. -- -- The helper maintance internal collection of current peers and switches over it each time -- it changes. onPeers :: forall m a b . (MonadFix m, LoggingMonad m, NetworkMonad m) => (S.Seq Peer -> GameWire m a b) -- ^ Wire that uses current peer collection -> GameWire m a b onPeers w = switch $ proc _ -> do -- Trick to immediate switch to current set of peers epeers <- now . currentPeers -< () returnA -< (error "onPeers: impossible", go <$> epeers) where go initalPeers = proc a -> do conEvent <- peersConnected -< () disEvent <- peersDisconnected -< () -- Local state loop to catch up peers rec curPeers' <- forceNF . delay initalPeers -< curPeers let addEvent = (\ps -> curPeers' S.>< ps) <$> conEvent let addedPeers = event curPeers' id addEvent -- To not loose added peers when some removed let remEvent = (F.foldl' (\ps p -> S.filter (/= p) ps) addedPeers) <$> disEvent let ew = fmap listenPeers $ addEvent `mergeR` remEvent (curPeers, b) <- rSwitch (listenPeers initalPeers) -< (a, ew) returnA -< b where listenPeers :: S.Seq Peer -> GameWire m a (S.Seq Peer, b) listenPeers peers = proc a -> do b <- w peers -< a returnA -< (peers, b)