packages feed

reliable-io-0.0.1: examples/Soak.hs

{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE TupleSections #-}
module Main (main) where

--------------------------------------------------------------------------------

import Control.Exception (try, AsyncException(..))
import Control.Monad (forM_, when, unless)
import Data.Unique (hashUnique, newUnique)
import Data.Word (Word8, Word16)
import Foreign.Marshal.Array (peekArray, pokeArray, allocaArray)
import Foreign.Ptr (Ptr, nullPtr)
import System.Environment (getArgs)
import System.IO (hSetBuffering, stdin, BufferMode(..))

import Reliable.IO (Endpoint)
import qualified Reliable.IO as Reliable

--------------------------------------------------------------------------------

gMaxPacketBytes :: Num a => a
gMaxPacketBytes = 16 * 1024

randomInt :: Int -> Int -> IO Int
randomInt low high = do
  randInt <- hashUnique <$> newUnique
  return $ low + (randInt `mod` (high - low + 1))

seqNoToPktSz :: Word16 -> Int
seqNoToPktSz seqNo =
  ((fromIntegral seqNo * 1023) `mod` (gMaxPacketBytes - 2)) + 2

initializeSoak :: Double -> IO (Endpoint, Endpoint)
initializeSoak startTime = do
    putStrLn "initializing"
    Reliable.initialize
    Reliable.logLevel Reliable.LogLevel'Debug

    let clientConfig =
          Reliable.setPacketFragmentationLimit 500 $
          Reliable.setName "client" $
          Reliable.defaultConfig

        serverConfig =
          Reliable.setPacketFragmentationLimit 500 $
          Reliable.setName "server" $
          Reliable.defaultConfig

        recvFn :: Word16 -> Ptr Word8 -> Int -> IO Bool
        recvFn _ ptr sz = do
          when (ptr == nullPtr) $ fail "Received null pointer"
          when (sz <= 0) $ fail "Received empty packet"
          when (sz <= 2) $ fail "Received empty header"
          when (sz > gMaxPacketBytes) $ fail "Packet too large."
          
          (s0 : s1 : arr) <- peekArray sz ptr
          let seqNo :: Word16
              seqNo = (fromIntegral s0) + (256 * fromIntegral s1)

          unless (sz == seqNoToPktSz seqNo) $
            fail "Sequence number doesn't match packet size"

          forM_ (zip [(2 :: Int)..] arr) $ \(i, dat) ->
            unless (fromIntegral dat == (i + fromIntegral seqNo) `mod` 256) $
            fail "Malformed packet"

          return True

    rec clientEndpoint <- Reliable.createEndpoint clientConfig startTime
                            (xmitFn True) recvFn
        serverEndpoint <- Reliable.createEndpoint serverConfig startTime
                            (xmitFn False) recvFn

        let xmitFn :: Bool -> Word16 -> Ptr Word8 -> Int -> IO ()
            xmitFn toServer _ ptr sz = do
              rand <- randomInt 0 100
              let ep = if toServer then serverEndpoint else clientEndpoint
              unless (rand < 5) $ Reliable.receivePacket ep ptr sz

    return (clientEndpoint, serverEndpoint)

shutdownSoak :: Endpoint -> Endpoint -> IO ()
shutdownSoak client server = do
    putStrLn "shutdown"

    Reliable.destroyEndpoint client
    Reliable.destroyEndpoint server
    Reliable.terminate

iterateSoak :: Endpoint -> Endpoint -> Double -> IO ()
iterateSoak client server t =
  let generatePacketData :: Word16 -> [Word8]
      generatePacketData seqNo =
        (fromIntegral $ seqNo `mod` 256) :
        (fromIntegral $ (seqNo `div` 256) `mod` 256) :
        map (fromIntegral . (+ fromIntegral seqNo))
            [2..(seqNoToPktSz seqNo - 1)]

      sendNextPacket ep ptr = do
        seqNo <- Reliable.nextPacketSequence ep
        let pkt = generatePacketData seqNo
        pokeArray ptr pkt
        Reliable.sendPacket ep ptr (length pkt)

   in allocaArray gMaxPacketBytes $ \clientPktPtr ->
      allocaArray gMaxPacketBytes $ \serverPktPtr -> do
        sendNextPacket client clientPktPtr
        sendNextPacket server serverPktPtr

        Reliable.update client t
        Reliable.update server t

        Reliable.clearAcks client
        Reliable.clearAcks server
        
main :: IO ()
main = do
    hSetBuffering stdin LineBuffering
    args <- getArgs
    let startTime :: Double
        startTime = 100.0

        numIters :: Maybe Int
        numIters = case length args of
            1 -> Just $ read (head args)
            _ -> Nothing

        times = case numIters of
            Just x -> take x $ map (+startTime) [0.0,0.1..]
            Nothing -> map (+startTime) [0.0,0.1..]

    putStrLn "[soak]"
    putStrLn $ "num_iterations = " <> show numIters

    (client, server) <- initializeSoak startTime
    soakResult <- try $ forM_ times $ iterateSoak client server

    case soakResult of
        (Left UserInterrupt) -> return ()
        (Left e) -> fail (show e)
        (Right ()) -> return ()

    shutdownSoak client server