gore-and-ash-network-1.1.0.0: src/Game/GoreAndAsh/Network/API.hs
{-|
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)