-- Hoogle documentation, generated by Haddock
-- See Hoogle, http://www.haskell.org/hoogle/
-- | Rollback/replay NetCode for realtime, deterministic, multiplayer games.
--
-- A rollback/replay client-server system for realtime multiplayer games.
-- The API only requires you to express your game as a pure,
-- deterministic function.
--
-- See Alpaca.NetCode to get started.
--
--
Advantages
--
--
-- - Simple code. Your game logic contains no NetCode.
-- - Low bandwidth. Only inputs are shared.
-- - Zero latency. Player's own inputs affect their game
-- immediatly.
-- - UDP based. Unordered and using redundancy to mitigate packet
-- loss.
-- - Lightweight server. The server does not run the game logic, it
-- only relays and tracks user inputs.
-- - Cheating. Only inputs are shared which eliminates a whole class
-- state manipulation cheats.
--
--
-- Disadvantages
--
--
-- - Increased CPU usage. Rollback/replay means that clients must run
-- the game step function multiple times per frame.
-- - Not suitable for large numbers of players. Tens of players is
-- likey reasonable.
--
--
-- Disclaimer
--
-- This is an initial release with minimal functionality and still very
-- experimental. Use at your own risk.
@package alpaca-netcode
@version 0.1.0.0
-- | Rollback and replay based game networking
module Alpaca.NetCode.Advanced
-- | Run a server for a single game. This will block until the game ends,
-- specifically when all players have disconnected.
runServerWith :: forall input. (Eq input, Flat input) => ServiceName -> Maybe SimNetConditions -> ServerConfig -> input -> IO ()
-- | Run a server for a single game. This will block until the game ends,
-- specifically when all players have disconnected.
runServerWith' :: forall input clientAddress. (Eq input, Flat input, Show clientAddress, Ord clientAddress) => (NetMsg input -> clientAddress -> IO ()) -> IO (NetMsg input, clientAddress) -> Maybe SimNetConditions -> ServerConfig -> input -> IO ()
-- | Configuration options specific to the server.
data ServerConfig
ServerConfig :: Int -> Float -> ServerConfig
-- | Tick rate (ticks per second). Typically 30 or 60.
-- Must be the same across all clients and the server. Packet rate and
-- hence network bandwidth will scale linearly with this the tick rate.
[scTickRate] :: ServerConfig -> Int
-- | Seconds of not receiving packets from a client before disconnecting
-- that client.
[scClientTimeout] :: ServerConfig -> Float
-- | Sensible defaults for ServerConfig based on the tick rate.
defaultServerConfig :: Int -> ServerConfig
-- | Start a client. This blocks until the initial handshake with the
-- server is finished.
runClientWith :: forall world input. Flat input => HostName -> ServiceName -> Maybe SimNetConditions -> ClientConfig -> input -> world -> (Map PlayerId input -> Tick -> world -> world) -> IO (Client world input)
-- | Start a client. This blocks until the initial handshake with the
-- server is finished.
runClientWith' :: forall world input. Flat input => (NetMsg input -> IO ()) -> IO (NetMsg input) -> Maybe SimNetConditions -> ClientConfig -> input -> world -> (Map PlayerId input -> Tick -> world -> world) -> IO (Client world input)
-- | Configuration options specific to clients.
data ClientConfig
ClientConfig :: Int -> Float -> Int -> Int -> Int -> ClientConfig
-- | Tick rate (ticks per second). Typically 30 or 60.
-- Must be the same across all clients and the server. Packet rate and
-- hence network bandwidth will scale linearly with this the tick rate.
[ccTickRate] :: ClientConfig -> Int
-- | Add this constant amount of latency (in seconds) to this client's
-- inputs. A good value is 0.03 or something between 0
-- and 0.1. May differ between clients.
--
-- Too high of a value and the player will get annoyed at the extra input
-- latency. On the other hand, a higher value means less miss-predictions
-- of other clients. In the extreme case, set to something higher than
-- ping, there will be no miss predictions: all clients will receive
-- inputs before rendering the corresponding tick.
[ccFixedInputLatency] :: ClientConfig -> Float
-- | Maximum number of ticks to predict when sampling.
-- defaultClientConfig uses ccTickRate / 2. If the client
-- is this many ticks behind the current tick, it will simply stop at an
-- earlier tick. You may want to scale this value along with the tick
-- rate. May differ between clients.
[ccMaxPredictionTicks] :: ClientConfig -> Int
-- | If the client's latest known authoritative world is this many ticks
-- behind the current tick, no prediction will be done at all when
-- sampling. defaultClientConfig uses ccTickRate * 3.
-- Useful because want to save CPU cycles for catching up with the
-- server. You may want to scale this value along with the tick rate. May
-- differ between clients.
[ccResyncThresholdTicks] :: ClientConfig -> Int
-- | When submitting inputs to the server, we also send a copy of
-- ccSubmitInputDuplication many recently submitted inputs in
-- order to mittigate the effect for dropped packets.
-- defaultClientConfig uses 15.
[ccSubmitInputDuplication] :: ClientConfig -> Int
-- | Sensible defaults for ClientConfig based on the tick rate.
defaultClientConfig :: Int -> ClientConfig
-- | A Client. You'll generally obtain this via runClient.
data Client world input
-- | The client's PlayerId
clientPlayerId :: Client world input -> PlayerId
-- | Sample the current world state.
--
-- . First, This will estimate the current tick based on ping and clock
-- synchronization with the server. Then, this extrapolates past the
-- latest know authoritative world state by assuming no user inputs have
-- changed (unless otherwise known e.g. our own player's inputs are
-- known). If the client has been stopped, this will return the last
-- predicted world.
clientSample :: Client world input -> IO world
-- | Sample the world state. First, This will estimate the current tick
-- based on ping and clock synchronization with the server. Then, the
-- world state will be rollback and inputs replayed as necessary. This
-- returns:
--
--
-- - New authoritative world states in chronological order since the
-- last sample time. These world states are the True world states at each
-- tick. This list will be empty if no new authoritative world states
-- have been derived since that last call to this sample function. Though
-- it's often simpler to just use the predicted world state, you can use
-- these authoritative world states to render output when you're not
-- willing to miss-predict but are willing to have greater latency. If
-- the client has been stopped, this will be an empty list.
-- - The predicted current world state. This extrapolates past the
-- latest know authoritative world state by assuming no user inputs have
-- changed (unless otherwise known e.g. our own player's inputs are
-- known). If the client has been stopped, this will return the last
-- predicted world.
--
clientSample' :: Client world input -> IO ([world], world)
-- | Set the client's current input.
clientSetInput :: Client world input -> input -> IO ()
-- | Stop the client.
clientStop :: Client world input -> IO ()
-- | Settings for simulating network conditions. Packets in both the send
-- and receive directions are randomly dropped or delayed by `simPing/2`
-- plus some random duration between `-simJitter` and simJitter.
data SimNetConditions
SimNetConditions :: Float -> Float -> Float -> SimNetConditions
-- | Extra ping (seconds)
[simPing] :: SimNetConditions -> Float
-- | Extra jitter (seconds). Should be less than simPing.
[simJitter] :: SimNetConditions -> Float
-- | Package loss (0 = no packet loss, 1 = 100% packet loss).
[simPackageLoss] :: SimNetConditions -> Float
-- | The game is broken into discrete ticks starting from 0.
newtype Tick
Tick :: Int64 -> Tick
newtype PlayerId
PlayerId :: Word8 -> PlayerId
[unPlayerId] :: PlayerId -> Word8
data NetMsg input
-- | Either a host name e.g., "haskell.org" or a numeric host
-- address string consisting of a dotted decimal IPv4 address or an IPv6
-- address e.g., "192.168.0.1".
type HostName = String
-- | Either a service name e.g., "http" or a numeric port number.
type ServiceName = String
-- | This module should be all you need to get started writing multiplayer
-- games. See Alpaca.NetCode.Advanced for more advanced usage.
module Alpaca.NetCode
-- | Run a server for a single game. This will block until the game ends,
-- specifically when all players have disconnected.
runServer :: forall input. (Eq input, Flat input) => ServiceName -> Int -> input -> IO ()
-- | Start a client. This blocks until the initial handshake with the
-- server is finished. You must call clientSetInput on the
-- returned client to submit new inputs.
--
-- Think of world as shared state between all clients. Alpaca
-- NetCode takes care of synchronizing and predicting the world
-- state across all clients. Additionally, clock synchronization is done
-- with the server and the "current" tick is decided for you when
-- sampling with clientSample.
--
-- Typical usage looks like this:
--
--
-- main :: IO ()
-- main = do
-- myClient <- runClient "localhost" "8111" 30 myInput0 myWorld0 worldStep
-- let myPlayerId = clientPlayerId myClient
--
-- mainGameLoop $ do
-- myInput <- pollInput -- Poll inputs from some other library
-- clientSetInput myClient -- Push inputs to Alpaca NetCode
-- world <- clientSample -- Sample the current (predicted) world
-- renderWorld myPlayerId world -- Render the world
--
-- -- You're free to do IO and maintain state local to the client.
--
-- return (gameIsOver world) -- Return True to keep looping
--
-- clientStop myClient
--
-- -- Given
-- data World = World { .. }
-- data Input = Input { .. } deriving (Generic, Eq, Flat)
-- myWorld0 :: World
-- gameIsOver :: World -> Bool
-- myInput0 :: Input
-- worldStep :: Map PlayerId Input -> Tick -> World -> World
-- renderWorld :: PlayerId -> World -> IO ()
-- pollInput :: IO Input
-- mainGameLoop :: IO Bool -> IO ()
--
runClient :: forall world input. Flat input => HostName -> ServiceName -> Int -> input -> world -> (Map PlayerId input -> Tick -> world -> world) -> IO (Client world input)
-- | A Client. You'll generally obtain this via runClient.
data Client world input
-- | The client's PlayerId
clientPlayerId :: Client world input -> PlayerId
-- | Sample the current world state.
--
-- . First, This will estimate the current tick based on ping and clock
-- synchronization with the server. Then, this extrapolates past the
-- latest know authoritative world state by assuming no user inputs have
-- changed (unless otherwise known e.g. our own player's inputs are
-- known). If the client has been stopped, this will return the last
-- predicted world.
clientSample :: Client world input -> IO world
-- | Set the client's current input.
clientSetInput :: Client world input -> input -> IO ()
-- | Stop the client.
clientStop :: Client world input -> IO ()
-- | The game is broken into discrete ticks starting from 0.
newtype Tick
Tick :: Int64 -> Tick
newtype PlayerId
PlayerId :: Word8 -> PlayerId
[unPlayerId] :: PlayerId -> Word8
-- | Either a host name e.g., "haskell.org" or a numeric host
-- address string consisting of a dotted decimal IPv4 address or an IPv6
-- address e.g., "192.168.0.1".
type HostName = String
-- | Either a service name e.g., "http" or a numeric port number.
type ServiceName = String