gore-and-ash-network-1.3.2.0: src/Game/GoreAndAsh/Network/Module.hs
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-|
Module : Game.GoreAndAsh.Network.Module
Description : Monad transformer and core module instance
Copyright : (c) Anton Gushcha, 2015-2016
License : BSD3
Maintainer : ncrashed@gmail.com
Stability : experimental
Portability : POSIX
The module contains declaration of module monad transformer and instance of 'GameModule'.
-}
module Game.GoreAndAsh.Network.Module(
NetworkT(..)
) where
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Error.Class
import Control.Monad.Extra (whenJust)
import Control.Monad.Fix
import Control.Monad.State.Strict
import Control.Monad.Trans.Resource
import Data.Hashable
import Game.GoreAndAsh
import Game.GoreAndAsh.Network.State
import Network.ENet
import Network.ENet.Host
import Network.ENet.Packet (peek)
import Network.ENet.Peer
import qualified Data.Foldable as F
import qualified Data.HashMap.Strict as H
import qualified Data.Sequence as S
import qualified Network.ENet.Bindings as B
-- | Monad transformer of network 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, ... other modules ... ] IO
--
-- newtype AppMonad a = AppMonad (AppStack a)
-- deriving (Functor, Applicative, Monad, MonadFix, MonadIO, LoggingMonad, NetworkMonad)
-- @
--
-- The module is NOT pure within first phase (see 'ModuleStack' docs), therefore currently only 'IO' end monad can handler the module.
newtype NetworkT s m a = NetworkT { runNetworkT :: StateT (NetworkState s) m a }
deriving (Functor, Applicative, Monad, MonadState (NetworkState s), MonadFix, MonadTrans, MonadIO, MonadThrow, MonadCatch, MonadMask, MonadError e)
instance MonadBase IO m => MonadBase IO (NetworkT s m) where
liftBase = NetworkT . liftBase
instance MonadResource m => MonadResource (NetworkT s m) where
liftResourceT = NetworkT . liftResourceT
instance GameModule m s => GameModule (NetworkT s m) (NetworkState s) where
type ModuleState (NetworkT s m) = NetworkState s
runModule (NetworkT m) s = do
((a, s'), nextState) <- runModule (runStateT m s) (networkNextState s)
s'' <- processEvents <=< clearMessages <=< moveDisconnected <=< moveConnected $ s'
return (a, s'' {
networkNextState = nextState
})
where
processEvents s' = case networkHost s' of
Nothing -> return s'
Just h -> processNetEvents s' h
moveConnected s' = return $ s' {
networkPeers = networkPeers s' S.>< networkConnectedPeers s'
, networkConnectedPeers = S.empty
}
moveDisconnected s' = return $ s' {
networkPeers = remAllFromSeq (networkDisconnectedPeers s') (networkPeers s')
, networkDisconnectedPeers = S.empty
}
clearMessages s' = return $ s' {
networkMessages = H.empty
}
newModuleState = do
s <- newModuleState
return $ emptyNetworkState s
withModule _ = withENetDo
cleanupModule NetworkState{..} = do
forM_ networkPeers $ \p -> disconnectNow p 0
forM_ networkConnectedPeers $ \p -> disconnectNow p 0
whenJust networkHost destroy
-- | Deletes all elements from second sequence that are in first sequence O(n^2)
remAllFromSeq :: (Eq k, Hashable k) => S.Seq k -> S.Seq k -> S.Seq k
remAllFromSeq s m = F.foldl' (\acc a -> S.filter (/= a) acc) m s
-- | Poll all events from ENet
processNetEvents :: MonadIO m => NetworkState s -> Host -> m (NetworkState s)
processNetEvents nst hst = liftIO $ untilNothing nst (service hst 0) handleEvent
where
untilNothing acc f h = do
ma <- f
case ma of
Nothing -> return acc
Just a -> do
acc' <- h acc a
untilNothing acc' f h
handleEvent s@NetworkState{..} (B.Event et peer ch edata packetPtr) = case et of
B.None -> do
when networkDetailedLogging $ putStrLn "Network: Event none"
return s
B.Connect -> do
when networkDetailedLogging $ putStrLn "Network: Peer connected"
return $ s {
networkConnectedPeers = networkConnectedPeers S.|> peer
}
B.Disconnect -> do
when networkDetailedLogging $ putStrLn $ "Network: Peer disconnected, code " ++ show edata
return $ s {
networkDisconnectedPeers = networkDisconnectedPeers S.|> peer
}
B.Receive -> do
(Packet !fs !bs) <- peek packetPtr
when networkDetailedLogging $ putStrLn $ "Network: Received message at channel " ++ show ch ++ ": "
++ show fs ++ ", payload: " ++ show bs
return $ s {
networkMessages = case H.lookup (peer, ch) networkMessages of
Nothing -> H.insert (peer, ch) (S.singleton bs) networkMessages
Just msgs -> H.insert (peer, ch) (msgs S.|> bs) networkMessages
}