Cauldron2D.World (Cauldron2D v0.2.2)

Copy Markdown View Source

Runs a Cauldron2D.Game at a fixed tick and keeps its players supplied with views.

{:ok, world} = Cauldron2D.World.start_link(game: MyGame, game_opts: [seed: 1], hz: 50)

Players join from their own processes with Cauldron2D.Player.join/3; each tick the world applies the input they sent, steps the game once per due step, and sends every player whose frame is due {:cauldron_frame, %{tick: n, view: view, events: events}} — every tick, or as often as the player asked (join/4's :frames, frames/3), the view built only for those frames. A player whose process exits is removed. Every join and leave is told to the subscribers of Cauldron2D.World.Presence, and each tick's events to those of Cauldron2D.World.Events. stats/1 says how the world is keeping up.

The next tick is timed only once the last has run, for when the next step falls due, so the world's mailbox never holds more than one tick: input sent while a slow tick runs is applied before the next step, however far the world falls behind. Steps more than Cauldron2D.Loop's :max_steps behind are dropped, not owed.

A world started with tick: :on_input has no clock: it runs one step, with dt of 1 / hz, each time a player's input arrives, and sends views after it. That is the world for a turn-based game. pause/1 stops a clocked world stepping until resume/1; time paused is not owed afterwards, and input sent meanwhile is applied on resume.

Options

  • :game — the module implementing Cauldron2D.Game. Required
  • :game_opts — passed to its init/1. Default []
  • :state — a game state to start from instead of calling init/1, as snapshot/1 returned it. Default none
  • :hz — ticks per second, and the dt of a step. Default 50
  • :tick — :fixed, stepping on the clock, or :on_input. Default :fixed
  • :record — keep a Cauldron2D.Replay of joins, leaves and input. Default false
  • :name — a GenServer name. Default none

Summary

Types

How the world is keeping up: the players present, the ticks a second it is set to, the steps run so far, and over the last 256 ticks that ran a step — how many they were, the microseconds a tick took (stepping and sending every view) as a mean, a 95th percentile and the longest, and behind, how many of them had more than one step due.

Functions

Returns a specification to start this module under a supervisor.

Change how often player id is sent frames, as join/4's :frames takes it.

The game module the world runs.

Send player id's current input, applied before the next tick.

Add the calling process as player id. {:error, :taken} when another process plays as id; any other {:error, reason} is the game's refusal.

Remove player id.

Stop stepping until resume/1. Nothing for a world stepping on input.

Whether the world is paused.

The ids of the players present.

The replay recorded so far, or nil when the world was not started with record: true.

Step again, from now.

The game's current state.

Start a world; see the module documentation for the options.

How the world is keeping up; see stats/0.

Types

player_id()

@type player_id() :: term()

stats()

@type stats() :: %{
  players: non_neg_integer(),
  hz: number(),
  steps: non_neg_integer(),
  ticks: non_neg_integer(),
  tick_us: %{
    mean: non_neg_integer(),
    p95: non_neg_integer(),
    max: non_neg_integer()
  },
  behind: non_neg_integer()
}

How the world is keeping up: the players present, the ticks a second it is set to, the steps run so far, and over the last 256 ticks that ran a step — how many they were, the microseconds a tick took (stepping and sending every view) as a mean, a 95th percentile and the longest, and behind, how many of them had more than one step due.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

frames(world, id, rate)

@spec frames(GenServer.server(), player_id(), boolean() | number()) :: :ok

Change how often player id is sent frames, as join/4's :frames takes it.

game(world)

@spec game(GenServer.server()) :: module()

The game module the world runs.

input(world, id, input)

@spec input(GenServer.server(), player_id(), Cauldron2D.Input.state()) :: :ok

Send player id's current input, applied before the next tick.

join(world, id, props, opts \\ [])

@spec join(GenServer.server(), player_id(), map(), keyword()) ::
  :ok | {:error, term()}

Add the calling process as player id. {:error, :taken} when another process plays as id; any other {:error, reason} is the game's refusal.

:frames is how often the player is sent a frame: true (default) after every tick that steps, a number for that many frames a second at most, or false for none — a second seat played from a process that already receives frames as another player. A frame carries the events of every step since the player's last frame. :timeout is how many milliseconds the world is given to take the player in, default 10_000.

leave(world, id)

@spec leave(GenServer.server(), player_id()) :: :ok

Remove player id.

pause(world)

@spec pause(GenServer.server()) :: :ok

Stop stepping until resume/1. Nothing for a world stepping on input.

paused?(world)

@spec paused?(GenServer.server()) :: boolean()

Whether the world is paused.

players(world)

@spec players(GenServer.server()) :: [player_id()]

The ids of the players present.

replay(world)

@spec replay(GenServer.server()) :: Cauldron2D.Replay.t() | nil

The replay recorded so far, or nil when the world was not started with record: true.

resume(world)

@spec resume(GenServer.server()) :: :ok

Step again, from now.

snapshot(world)

@spec snapshot(GenServer.server()) :: term()

The game's current state.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Start a world; see the module documentation for the options.

stats(world)

@spec stats(GenServer.server()) :: stats()

How the world is keeping up; see stats/0.