# `Cauldron2D.World`
[🔗](https://github.com/jaman/cauldron/blob/v0.3.3/cauldron_2d/lib/cauldron_2d/world.ex#L1)

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
  * `:follow` — another world, on this machine or another, to follow instead of
    running one of its own, or `{:link, address, port}`: the world behind a
    `Cauldron2D.Link` listener there, which the world connects to itself and plays
    with rollback, closing the connection when it stops. Default none
  * `:rollback` — with `:follow`, play the host's game with rollback
    (`Cauldron2D.World.Rollback`) instead of following it step by step: a keyword
    list of `:delay`, `:window` and `:lead`, or `true` for their defaults. Default none
  * `:join_props` — with `:follow`, props added to every join of this world's players
    sent to the host. Default none

## Following

A world started with `follow: host` runs the host's game on its own copy of the host's
state (`Cauldron2D.World.Lockstep`): it has no clock, and after each of the host's
ticks applies the joins, leaves and input the host applied and runs the steps the host
ran, then sends its own players their frames. Its players' joins, leaves and input go
to the host, which takes them into its next tick as its own players', so only those
cross between the machines, never views. `pause/1` and `resume/1` do nothing on a
follower and `paused?/1` is `false`: only the host's own players pause the game. A
follower stops, with reason `:normal`, when the host does. Starting one fails with
`{:error, {:host, reason}}` when the host cannot be reached within `:timeout`
milliseconds (default `10_000`).

## Rollback

A world started with `follow: host, rollback: opts` runs the host's game on its own
clock instead, from the host's state when it starts. Both worlds apply their own
players' input as soon as it comes, a few frames ahead, and run the other's input
again when it arrives late (`Cauldron2D.World.Rollback`). The host decides every join
and leave. A host plays with rollback against one world at a time, and a world playing
with rollback has no followers. When the host stops, the other world stops with reason
`:normal`; when the other world stops, the host goes on alone. The other world adds
`round_trip`, its round trip to the host in milliseconds when it started, to the props
of every join of its players it sends the host.

# `player_id`

```elixir
@type player_id() :: term()
```

# `stats`

```elixir
@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.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `frames`

```elixir
@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`

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

The game module the world runs.

# `input`

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

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

# `join`

```elixir
@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`

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

Remove player `id`.

# `pause`

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

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

# `paused?`

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

Whether the world is paused.

# `players`

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

The ids of the players present.

# `replay`

```elixir
@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`

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

Step again, from now.

# `snapshot`

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

The game's current state.

# `start`

```elixir
@spec start(keyword()) :: GenServer.on_start()
```

Start a world not linked to the caller, as `start_link/1` does otherwise.

# `start_link`

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

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

# `stats`

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

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
