{-# LANGUAGE TemplateHaskell #-}
module NetEngine
( NetEngineOutput(..), NetEngineInput(..)
, netEngine
, gNEOMove, gNEOPacket, gNEOSetIterTimer
, gNEOPeerConnected, gNEOGameIteration
) where
import Control.Applicative
import Control.Category
import Control.FilterCategory
import Codec.Compression.Zlib (decompress, compress)
import Control.Monad ((>=>))
import Data.ADT.Getters
import Data.ByteString.Lazy.Char8 (ByteString, pack, unpack)
import Data.Function (on)
import Data.List (isPrefixOf, partition)
import Data.Map (Map, delete, fromList, insert, lookup, toList)
import Data.Monoid (Monoid(..))
import FRP.Peakachu.Program
import Network.Socket (SockAddr)
import Prelude hiding ((.), id, lookup)
data NetEngineState moveType idType = NetEngineState
{ neLocalQueue :: [(Integer, moveType)]
, neQueue :: Map (Integer, idType) moveType
, nePeers :: [idType]
, nePeerAddrs :: [SockAddr]
, neWaitingForPeers :: Bool
, neGameIteration :: Integer
, neOutputMove :: moveType
, neLatencyIters :: Integer
, neMyPeerId :: idType
}
data NetEngineOutput moveType idType
= NEOMove moveType
| NEOPeerConnected idType
| NEOPacket String SockAddr
| NEOSetIterTimer
| NEOGameIteration Integer
deriving Show
$(mkADTGetters ''NetEngineOutput)
data NetEngineInput moveType
= NEIMove Integer moveType
| NEIPacket String SockAddr
| NEIMatching [SockAddr]
| NEIIterTimer
| NEITransmitTimer
$(mkADTGetters ''NetEngineInput)
data NetEngineMid moveType idType
= AInput (NetEngineInput moveType)
| AState (NetEngineState moveType idType)
$(mkADTGetters ''NetEngineMid)
data HelloType = LetsPlay | WereOn
deriving (Read, Show, Eq)
data NetEngPacket m i
= Moves (Map (Integer, i) m)
| Hello i HelloType
deriving (Read, Show)
magic :: String
magic = "dtkffod!"
atP :: FilterCategory cat => (a -> Maybe b) -> cat a b
atP = mapMaybeC
outPacket
:: (Show a, Show i)
=> NetEngPacket a i -> SockAddr -> NetEngineOutput a i
outPacket = NEOPacket . (magic ++) . withPack compress . show
withPrev :: MergeProgram a (a, a)
withPrev = (,) <$> delayP (1::Int) <*> id
atChgOf :: Eq b
=> (a -> b) -> MergeProgram a a
atChgOf onfunc =
arrC snd . filterC (uncurry (on (/=) onfunc)) . withPrev
netEngine
:: ( Monoid moveType, Ord peerIdType
, Read moveType, Read peerIdType
, Show moveType, Show peerIdType
)
=> peerIdType
-> MergeProgram
(NetEngineInput moveType)
(NetEngineOutput moveType peerIdType)
netEngine myPeerId =
mconcat
[ NEOSetIterTimer <$ singleValueP
, mconcat
[ mconcat
[ NEOMove . neOutputMove <$> id
, NEOSetIterTimer <$ id
, NEOGameIteration . neGameIteration <$> id
] . atChgOf neGameIteration . atP gAState
, mconcat
[ outPacket (Hello myPeerId WereOn)
<$> flattenC
. arrC nePeerAddrs
, arrC NEOPeerConnected
. filterC (/= myPeerId)
. flattenC
. arrC nePeers
]
. atChgOf nePeerAddrs
. atP gAState
, flattenC
. ( sendMoves
<$> (lstP gAState <* atP (gAInput >=> gNEITransmitTimer))
)
]
. mconcat
[ AState <$> scanlP netEngineStep (startState myPeerId)
, arrC AInput
]
, outPacket (Hello myPeerId LetsPlay) <$> flattenC . atP gNEIMatching
-- for warnings
, unwarn gNEIIterTimer
, unwarn gNEIMove
, unwarn gNEIPacket
]
where
unwarn x = undefined <$> filterC (const False) . atP x
sendMoves state =
outPacket (Moves (neQueue state)) <$> nePeerAddrs state
startState
:: (Monoid moveType, Ord peerIdType)
=> peerIdType -> NetEngineState moveType peerIdType
startState myPeerId =
NetEngineState
{ neLocalQueue = []
, neQueue =
fromList
[ ((i, myPeerId), mempty)
| i <- [0 .. latencyIters-1]
]
, nePeers = [myPeerId]
, nePeerAddrs = mempty
, neWaitingForPeers = False
, neGameIteration = 0
, neOutputMove = mempty
, neLatencyIters = latencyIters
, neMyPeerId = myPeerId
}
where
latencyIters = 5
netEngineStep
:: (Monoid a, Ord i, Read a, Read i)
=> NetEngineState a i -> NetEngineInput a -> NetEngineState a i
netEngineStep state (NEIMove iter move) =
state { neLocalQueue = (iter, move) : neLocalQueue state }
netEngineStep state NEIIterTimer = netEngineNextIter state
netEngineStep state (NEIPacket contents sender)
| isPrefixOf magic contents =
processPacket state sender
. read . withPack decompress
. drop (length magic) $ contents
| otherwise = state
netEngineStep state _ = state
withPack :: (ByteString -> ByteString) -> String -> String
withPack = (unpack .) . (. pack)
processPacket
:: (Monoid a, Ord i)
=> NetEngineState a i -> SockAddr -> NetEngPacket a i
-> NetEngineState a i
processPacket state sender (Hello peerId _)
| length (nePeers state) > 1 = state
| otherwise =
(startState myPeerId)
{ nePeers = [myPeerId, peerId]
, nePeerAddrs = [sender]
}
where
myPeerId = neMyPeerId state
processPacket state _ (Moves moves)
| neWaitingForPeers state = netEngineNextIter updState
| otherwise = updState
where
updState =
state
{ neQueue =
mappend (neQueue state) . fromList
. filter ((>= neGameIteration state) . fst . fst)
. toList $ moves
}
netEngineNextIter ::
(Monoid a, Ord i) => NetEngineState a i -> NetEngineState a i
netEngineNextIter ne =
case peerMoves of
Nothing -> ne { neWaitingForPeers = True }
Just move ->
ne
{ neLocalQueue = futureMoves
, neQueue =
insert moveKey (mconcat (snd <$> nextMoves)) $
foldr delete (neQueue ne) delKeys
, neGameIteration = iter + 1
, neOutputMove = move
, neWaitingForPeers = False
}
where
(nextMoves, futureMoves) =
partition ((<= nextMoveIter) . fst)
. neLocalQueue $ ne
nextMoveIter = iter + neLatencyIters ne
moveKey = (nextMoveIter, neMyPeerId ne)
iter = neGameIteration ne
moveKeys = (,) iter <$> nePeers ne
delKeys = (,) (iter - neLatencyIters ne) <$> nePeers ne
peerMoves =
mconcat <$>
sequence ((`lookup` neQueue ne) <$> moveKeys)