Visualize.Board.Server (Visualize v0.4.1)

Copy Markdown View Source

A live board: one process that owns the board's shared state and its feeds, and one process per cell under it (spec/15 §8, D-146).

The board process owns the window (in UTC), the zone, the variables, the sync group and the board's single now, read once per turn from clock:. It holds the Visualize.Feed.Monitor over the host's feeds (spec/08 §11.4, D-140): each turn it resolves the window at now, ticks the monitor, and hands each cell its slots' deliveries, schemas and statuses. Pushes reach the monitor through this process's mailbox. Setting a window (window/2) re-asks every feed.

The board also holds what its cells share when a person acts on one of them (spec/15 §13, D-151): a brushed window (brush/2), applied once rapid brushes settle and undone by reset_window/1, and the one selection (select/2), its field read through the board's links and sent to every cell, which dims the data that do not hold it. pick/4 reads the row behind a cell's keyboard point from that cell's process.

Each cell is a Visualize.Board.Cell: heap-limited, :temporary under a DynamicSupervisor this process starts and is the parent of, and monitored here. A cell that dies is shown {:crashed, reason} to every subscriber at once and started again after a backoff — 100 ms, doubling to 10 s, reset after 30 s alive — so a design that dies on every frame never takes a supervisor's restart intensity, its siblings or the board with it.

Starting

children = [{Visualize.Board.Supervisor, name: MyApp.Boards}]

{:ok, board} =
  Visualize.Board.Server.start(MyApp.Boards,
    board: board, store: store, binding: binding, feeds: feeds)

{:ok, info} = Visualize.Board.Server.subscribe(board)

def handle_info({:visualize_board, _board, {:cell, name, view}}, socket), do: …

A board that does not validate given its store, or whose binding names a feed feeds: does not hold, is not started: {:error, {:board, errors}} or {:error, {:feeds, names}}, before any process runs.

Options

  • :board - the %Visualize.Board{} (required)
  • :store - what resolution reads, spec/15 §3.2 (required)
  • :binding - the connector's %Visualize.Board.Binding{} (default empty)
  • :feeds - the feeds by the names the binding uses (default %{})
  • :theme - the theme the cells and their states are drawn in (default Visualize.Theme.default/0)
  • :system - the unit system every cell's measures are shown in, :metric, :us, :uk or :as_posted (default); a viewer's preference, so a host serving several runs a board per system (spec/14 §4.4, D-156)
  • :width - the container width in pixels until a resize/2 (default 1200)
  • :tick - the data rate, milliseconds between turns; nil turns only on tick/1 (default 1000)
  • :budget - the point budget asked of every feed (default nil)
  • :max_heap - each cell's heap limit in words (default 25_000_000)
  • :clock - the board's now, a function of no argument answering a UTC DateTime (default DateTime.utc_now/0)
  • :limits - the limits the components' designs are read under
  • :boards - the boards the cells' drill targets may name, as Visualize.Board.validate/3 takes them (default %{})
  • :debounce - milliseconds a brushed window waits for the next (default 250); 0 applies each at once
  • :name - a registered name

Summary

Functions

A brushed window (spec/15 §13.1): validated at once — :ok, or {:error, errors} as window/2 answers — and set as window/2 sets it once debounce: milliseconds pass with no later brush, so rapid brushes re-ask every feed once, for the last.

Returns a specification to start this module under a supervisor.

The board's present state (spec/15 §8.2).

The row behind a cell's keyboard point, {series, index} (spec/10 §17.2), and its identity (spec/15 §13.2): {:ok, %{row: row, identity: {field, value} | nil}}, or :error for a cell that is not running or a point it does not have.

The window the board was started with, set as window/2 sets it (spec/15 §13.1).

The container width set and every cell's box recomputed (spec/15 §1.6, §8.2); :ok.

The board's selection (spec/15 §13.2): {field, value}, its field read through the board's links, or nil to clear it. Every cell is sent it, and every subscriber the board's info. :ok.

The board frozen as it stands (spec/15 §12.2): a Visualize.Board.Snapshot of the board, its now and its resolved window, the width, the theme, and every cell's prepared component, variables and the slots the board last handed it — or the reason a cell that has crashed died. It holds no pid and no feed: Visualize.Board.Export draws it, and it reopens anywhere after the board has stopped.

A board started under a DynamicSupervisor (spec/15 §8.1): {:ok, pid}, or {:error, {:board, errors}} or {:error, {:feeds, names}} for a board that cannot run.

A board started linked to the caller, for a host's own supervisor; as start/2 answers.

The board and its cells stopped; :ok.

As subscribe/2 for the caller.

pid subscribed to the board's messages (spec/15 §8.2): {:ok, info}, and every cell sends it its present view. Every message is {:visualize_board, board, message}, with message {:board, info} or {:cell, name, view}.

One turn of the loop now (spec/15 §8.2); :ok once it is done.

A subscriber removed; :ok.

Board variables merged over the board's, and every cell prepared again (spec/15 §5,

The board's window set — nil, %{last: seconds} or %{from: t, to: t} in UTC — and every feed re-asked (spec/15 §8.2): :ok, or {:error, errors} for a window the board refuses (spec/15 §3.1). The turn runs at once.

Types

info()

@type info() :: %{
  board: Visualize.Board.t(),
  window: {DateTime.t(), DateTime.t()} | nil,
  zone: String.t(),
  now: DateTime.t() | nil,
  width: number(),
  sync: String.t() | nil,
  selection: {atom(), term()} | nil,
  moved: boolean(),
  cells: %{
    required(atom()) => %{
      pid: pid() | nil,
      state: Visualize.Board.State.t(),
      restarts: non_neg_integer()
    }
  }
}

Functions

brush(board, window)

@spec brush(GenServer.server(), Visualize.Board.window()) ::
  :ok | {:error, [Visualize.Board.error(), ...]}

A brushed window (spec/15 §13.1): validated at once — :ok, or {:error, errors} as window/2 answers — and set as window/2 sets it once debounce: milliseconds pass with no later brush, so rapid brushes re-ask every feed once, for the last.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

info(board)

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

The board's present state (spec/15 §8.2).

pick(board, cell, series, index)

@spec pick(GenServer.server(), atom(), non_neg_integer(), non_neg_integer()) ::
  {:ok, %{row: map(), identity: {atom(), term()} | nil}} | :error

The row behind a cell's keyboard point, {series, index} (spec/10 §17.2), and its identity (spec/15 §13.2): {:ok, %{row: row, identity: {field, value} | nil}}, or :error for a cell that is not running or a point it does not have.

The board answers the cell's process and the caller asks it, so a pick never holds the board; a cell that dies meanwhile answers :error.

reset_window(board)

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

The window the board was started with, set as window/2 sets it (spec/15 §13.1).

resize(board, width)

@spec resize(GenServer.server(), number()) :: :ok

The container width set and every cell's box recomputed (spec/15 §1.6, §8.2); :ok.

select(board, identity)

@spec select(GenServer.server(), {atom(), term()} | nil) :: :ok

The board's selection (spec/15 §13.2): {field, value}, its field read through the board's links, or nil to clear it. Every cell is sent it, and every subscriber the board's info. :ok.

snapshot(board)

The board frozen as it stands (spec/15 §12.2): a Visualize.Board.Snapshot of the board, its now and its resolved window, the width, the theme, and every cell's prepared component, variables and the slots the board last handed it — or the reason a cell that has crashed died. It holds no pid and no feed: Visualize.Board.Export draws it, and it reopens anywhere after the board has stopped.

start(supervisor, opts)

A board started under a DynamicSupervisor (spec/15 §8.1): {:ok, pid}, or {:error, {:board, errors}} or {:error, {:feeds, names}} for a board that cannot run.

start_link(opts)

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

A board started linked to the caller, for a host's own supervisor; as start/2 answers.

stop(board)

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

The board and its cells stopped; :ok.

subscribe(board)

@spec subscribe(GenServer.server()) :: {:ok, info()}

As subscribe/2 for the caller.

subscribe(board, pid)

@spec subscribe(GenServer.server(), pid()) :: {:ok, info()}

pid subscribed to the board's messages (spec/15 §8.2): {:ok, info}, and every cell sends it its present view. Every message is {:visualize_board, board, message}, with message {:board, info} or {:cell, name, view}.

tick(board)

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

One turn of the loop now (spec/15 §8.2); :ok once it is done.

unsubscribe(board, pid)

@spec unsubscribe(GenServer.server(), pid()) :: :ok

A subscriber removed; :ok.

vars(board, vars)

@spec vars(GenServer.server(), map()) ::
  :ok | {:error, [Visualize.Board.error(), ...]}

Board variables merged over the board's, and every cell prepared again (spec/15 §5,

§8.2): :ok, or {:error, errors}.

window(board, window)

@spec window(GenServer.server(), Visualize.Board.window()) ::
  :ok | {:error, [Visualize.Board.error(), ...]}

The board's window set — nil, %{last: seconds} or %{from: t, to: t} in UTC — and every feed re-asked (spec/15 §8.2): :ok, or {:error, errors} for a window the board refuses (spec/15 §3.1). The turn runs at once.