alpaca-netcode-0.1.0.0: src/Alpaca/NetCode.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_HADDOCK not-home #-}
-- | This module should be all you need to get started writing multiplayer
-- games. See "Alpaca.NetCode.Advanced" for more advanced usage.
module Alpaca.NetCode
( runServer,
runClient,
Client,
clientPlayerId,
clientSample,
clientSetInput,
clientStop,
-- ** Types
Tick (..),
PlayerId (..),
HostName,
ServiceName,
) where
import Alpaca.NetCode.Advanced
import qualified Data.Map as M
import Flat (
Flat,
)
-- | 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 =>
-- | The server's host name or IP address e.g. @"localhost"@.
HostName ->
-- | The server's port number e.g. @"8111"@.
ServiceName ->
-- | 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 the tick rate.
Int ->
-- | Initial input for new players. Must be the same across all clients and
-- the server.
--
-- Note that the client and server do input "prediction" by assuming @input@s
-- do not change. It is important to design your @input@ type accordingly. For
-- example, Do NOT store a @Bool@ indicating that a button has been clicked.
-- Instead, store a @Bool@ indicating if that button is currently held down.
-- Then, store enough information in the @world@ state to identify a click.
input ->
-- | Initial world state. Must be the same across all clients.
world ->
-- | A deterministic stepping function (for a single tick). In practice you
-- can choose to use whatever monad stack within as long as you (un)wrap into
-- a pure function e.g. you can use `ST` as long as you wrap it in `runST`.
-- Must be the same across all clients and the server. Takes:
--
-- * a map from PlayerId to current input. You can use the key set as the set
-- of all connected players.
-- * current game tick.
-- * previous tick's world state.
--
-- It is important that this is deterministic else clients' states will
-- diverge. Beware of floating point non-determinism!
( M.Map PlayerId input ->
Tick ->
world ->
world
) ->
IO (Client world input)
runClient
serverHostName
serverPort
tickRate
input0
world0
stepOneTick
= runClientWith
serverHostName
serverPort
Nothing
(defaultClientConfig tickRate)
input0
world0
stepOneTick
-- | 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) =>
-- | The server's port number e.g. @"8111"@.
ServiceName ->
-- | 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 the tick rate.
Int ->
-- | Initial input for new players. Must be the same across all clients and
-- the server.
input ->
IO ()
runServer
serverPort
tickRate
input0
= runServerWith
serverPort
Nothing
(defaultServerConfig tickRate)
input0