# `RasterExRatatui.Session`
[🔗](https://github.com/mcass19/raster_ex_ratatui/blob/v0.4.0/lib/raster_ex_ratatui/session.ex#L1)

An ExRatatui app running on a raster, driven from the caller's own process.

A session is what `RasterExRatatui.Surface` wraps: the `ExRatatui.CellSession`, the app server, the writer that delivers its renders, and the `RasterExRatatui.Raster` that turns them into pixels. It has no process of its own. A consumer that already owns a process in charge of a panel (a screen manager, a device loop) starts a session in it, folds the session's messages with `handle/2`, and writes the pixels it gets back however the panel wants.

    defmodule Badge.Screen do
      use GenServer

      alias RasterExRatatui.{Raster, Session}

      def init(app) do
        Process.flag(:trap_exit, true)
        raster = Raster.new(size: {400, 300}, format: RasterExRatatui.PixelFormat.Mono)
        {:ok, session} = Session.start(raster, app: app, keep_frame: true)
        {:ok, session}
      end

      def handle_info(msg, session) do
        case Session.handle(session, msg) do
          {:render, _patches, session} ->
            Badge.Display.show(Session.frame(session))
            {:noreply, session}

          {:exit, reason, session} ->
            Badge.Display.show(crash_frame(reason))
            {:noreply, session}

          :unknown ->
            {:noreply, session}
        end
      end

      def terminate(_reason, session), do: Session.stop(session)
    end

## Lifecycle

1. `start/2` creates the cell session for the raster's grid, starts the app server **linked to the caller**, and returns the session. The caller must trap exits: the app's exit then arrives as an `{:EXIT, pid, reason}` message that `handle/2` turns into `{:exit, reason, session}` instead of taking the caller down.
2. Every render of the app arrives in the caller's mailbox as `{RasterExRatatui.Session, ref, diff}`. `handle/2` takes that message, every other render of the same session already waiting behind it, folds them into the raster as one, and returns the patches. Renders are never queued up: a slow panel shows fewer, later frames, never every frame later and later.
3. `await/2` waits for the first render (or the app's exit) with a timeout, for consumers that want the first frame before they show anything.
4. `stop/1` stops the app server, waiting up to `shutdown_timeout:` for its `terminate/2` before killing it, and closes the cell session.

When the app exits, its server closes the cell session on the way out; a consumer that wants the app back starts a new session on the same raster (`start(raster(session), opts)`) and the new app's first render repaints everything.

## Options

`start/2` takes:

  * `:app` (required) — the `ExRatatui.App` module to run
  * `:app_opts` — keyword list passed to the app's `mount/1` (or reducer `init/1`), default `[]`. The session adds `surface:` to it, a map describing the panel: `:size` (the physical panel in pixels), `:cell_size` (the effective cell in pixels), `:grid_size` (cells), `:format`, `:scale`, and `:rotate` (see `RasterExRatatui.Raster`'s rotation section)
  * `:keep_frame` — keep a full frame of the panel up to date (default `false`). `frame/1` then returns it at no cost, and `render/2` applies each patch to it, for panels that only take whole frames
  * `:shutdown_timeout` — milliseconds `stop/1` waits for the app server before killing it (default `4_000`)

The pieces stay reachable: `raster/1` is the current raster (`RasterExRatatui.Raster.frame/1` renders what the panel shows), `server/1` the app server's pid, and `drain/2` and `render/2` are the two halves of `handle/2`, for a consumer that gathers renders and rasterises them on its own schedule.

# `payload`

```elixir
@type payload() :: RasterExRatatui.Raster.payload()
```

A payload the session folds: a cell diff from the app, or a snapshot.

# `t`

```elixir
@type t() :: %RasterExRatatui.Session{
  app: module(),
  cell_session: ExRatatui.CellSession.t(),
  frame: binary() | nil,
  raster: RasterExRatatui.Raster.t(),
  ref: reference(),
  server: pid() | nil,
  shutdown_timeout: non_neg_integer()
}
```

# `await`

```elixir
@spec await(t(), timeout()) ::
  {:render, [RasterExRatatui.Patch.t()], t()}
  | {:exit, term(), t()}
  | {:timeout, t()}
```

Waits up to `timeout` milliseconds for the next render of the session's app, or its exit, and handles it.

Returns what `handle/2` would, or `{:timeout, session}`. Other messages stay in the mailbox. Typical right after `start/2`, for the first frame.

# `drain`

```elixir
@spec drain(t(), term()) ::
  {:diffs, [ExRatatui.CellSession.Diff.t()], t()}
  | {:exit, term(), t()}
  | :unknown
```

The first half of `handle/2`: classifies a message without rasterising anything.

Returns `{:diffs, diffs, session}` for a render, with every render of the session still waiting in the mailbox gathered behind it, oldest first (renders from before a `resize/2` are dropped, since the next one repaints the new grid in full); `{:exit, reason, session}` for the app server's exit; `:unknown` otherwise. `render/2` takes the diffs whenever the consumer is ready to draw.

# `frame`

```elixir
@spec frame(t()) :: binary()
```

The whole panel as one row-major buffer of packed pixels: the kept frame with `keep_frame: true`, rendered from the raster otherwise.

# `grid_size`

```elixir
@spec grid_size(t()) :: RasterExRatatui.Raster.size()
```

The grid the app renders to, `{cols, rows}`.

# `handle`

```elixir
@spec handle(t(), term()) ::
  {:render, [RasterExRatatui.Patch.t()], t()} | {:exit, term(), t()} | :unknown
```

Handles a message from the caller's mailbox.

Returns:

  * `{:render, patches, session}` for a render of this session's app: the `RasterExRatatui.Patch` list that repaints what changed (empty when nothing did), after folding every render of the session still waiting in the mailbox into the same call. With `keep_frame: true`, `frame/1` is up to date as well
  * `{:exit, reason, session}` when the app server exited; the session no longer has a server, and its cell session is closed
  * `:unknown` for any other message

## Examples

    def handle_info(msg, %{session: session} = state) do
      case RasterExRatatui.Session.handle(session, msg) do
        {:render, patches, session} -> {:noreply, %{state | session: write(patches, session)}}
        {:exit, reason, session} -> {:stop, reason, %{state | session: session}}
        :unknown -> {:noreply, state}
      end
    end

# `raster`

```elixir
@spec raster(t()) :: RasterExRatatui.Raster.t()
```

The session's current `RasterExRatatui.Raster`.

# `render`

```elixir
@spec render(t(), [payload()]) :: {[RasterExRatatui.Patch.t()], t()}
```

The second half of `handle/2`: folds `payloads` (diffs or snapshots, in order) into the raster and returns the patches that repaint what changed.

With `keep_frame: true`, the patches are also applied to the kept frame.

# `resize`

```elixir
@spec resize(t(), RasterExRatatui.Raster.size()) :: t()
```

Changes the panel size, in pixels. The raster and the cell session are rebuilt at the new grid, the app receives an `ExRatatui.Event.Resize`, and its next render repaints the whole panel.

# `send_event`

```elixir
@spec send_event(t(), ExRatatui.Event.t()) :: :ok
```

Forwards an `ExRatatui.Event` (a `%Key{}`, a `%Mouse{}`, …) to the app, like input from a terminal. Dropped once the app has exited.

# `server`

```elixir
@spec server(t()) :: pid() | nil
```

The app server's pid, or `nil` once it has exited.

# `start`

```elixir
@spec start(RasterExRatatui.Raster.t(), keyword()) :: {:ok, t()} | {:error, term()}
```

Starts an app on `raster` from the calling process (see the moduledoc for the options).

Returns `{:ok, session}`, or `{:error, reason}` when the app fails to mount, in which case nothing is left running. Raises `ArgumentError` without an `:app`.

The app server is linked to the caller, which must trap exits before calling this.

# `stop`

```elixir
@spec stop(t()) :: :ok
```

Stops the app server, waiting up to the session's `shutdown_timeout:` for its `terminate/2` before killing it, and closes the cell session. Leaves no `{:EXIT, …}` from the app in the caller's mailbox, even when the app died on its own just before. Safe after `{:exit, _, _}`, when only the cell session is left to close.

---

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