Visualize.Board.Server (Visualize v0.7.0)

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, whose binding names a feed feeds: does not hold and no virtual source of the board is, or whose virtual sources cannot be defined, is not started: {:error, {:board, errors}}, {:error, {:feeds, names}} or {:error, {:virtual_sources, errors}}, before any process runs.

Virtual sources

The board's virtual sources and its cells' (spec/15 §14, D-194) run here, where the feeds run: a Visualize.VirtualSource.Engine held beside the monitor. Each turn shows it the feeds' deliveries; a change makes a pulse due debounce: later, and the pulse recomputes the sources that read what changed, in rank order, each once. A cell's slot bound to a virtual source's name is handed its last good output as it is handed a feed's.

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)
  • :generators - the generator modules (spec/14 §3.13) every cell's component is read with and every cell's frames call, beside config :visualize, generators: (default [])
  • :assigns - the host's assigns every generator's context carries: the viewer's, so a host whose generators read the viewer runs a board per viewer (default %{})
  • :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, and a virtual source's pulse waits for more changes (default 250); 0 applies each at once
  • :virtual_sources - the modules of the host's virtual-source functions (use Visualize.VirtualSources), beside config :visualize, virtual_sources: (default [], spec/15 §14.6)
  • :virtual_source_timeout - milliseconds a virtual source's call may run (default 1_000)
  • :virtual_source_heap - words a virtual source's call may hold (default 10_000_000)
  • :id - the board's id, each cell's chart root <id>-<cell>-chart (default vis-board- and a hash of the board's meta and cell names, spec/15 §8.2, D-188)
  • :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.

A click a cell's TooltipHook pushed, as Visualize.Event.decode/1 read it, resolved by the cell's process against the drawing it holds (spec/15 §9.2, D-170): {:ok, %Visualize.Event{}} with the cell, the declared name, the row as its mark drew it, typed, and its item — or :error for a cell that is not running, a click on a drawing it no longer holds, a key it has no row for, a name the mark does not declare, or the cell's own pick. Asked as pick/4 asks, never holding the board.

As export/3 with [] options.

The running board exported (spec/15 §12, D-181): its snapshot/1 drawn by Visualize.Board.Export — :svg (svg/2), :png (png/2), :pages (pages/2) or :html (html/2) — with the board's own :generators, :assigns and :system, so the export draws what the board draws for its viewer. opts are that function's, and an option given here wins over the board's. The snapshot and the options are one call to the board process.

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

The row behind a cell's keyboard point, {series, index} (spec/10 §17.2), its identity (spec/15 §13.2), its item and its frame: {:ok, %{row: row, identity: identity | nil, item: item | nil, frame: name}}, the identity {field, value}, or a map for a composite one the slot's schema declares (D-167), the item as a Visualize.Event's (D-170), 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}, or a map from field to value for a composite identity (D-167), each field read through the board's links, or nil to clear it. A map of one field is held as its pair. 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: Visualize.Board.identity() | 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.

event(board, cell, click)

@spec event(GenServer.server(), atom(), Visualize.Event.click()) ::
  {:ok, Visualize.Event.t()} | :error

A click a cell's TooltipHook pushed, as Visualize.Event.decode/1 read it, resolved by the cell's process against the drawing it holds (spec/15 §9.2, D-170): {:ok, %Visualize.Event{}} with the cell, the declared name, the row as its mark drew it, typed, and its item — or :error for a cell that is not running, a click on a drawing it no longer holds, a key it has no row for, a name the mark does not declare, or the cell's own pick. Asked as pick/4 asks, never holding the board.

export(board, format)

@spec export(GenServer.server(), :svg | :png | :pages | :html) ::
  String.t() | [String.t()] | {:ok, binary(), list()} | {:error, term()}

As export/3 with [] options.

export(board, format, opts)

@spec export(GenServer.server(), :svg | :png | :pages | :html, keyword()) ::
  String.t() | [String.t()] | {:ok, binary(), list()} | {:error, term()}

The running board exported (spec/15 §12, D-181): its snapshot/1 drawn by Visualize.Board.Export — :svg (svg/2), :png (png/2), :pages (pages/2) or :html (html/2) — with the board's own :generators, :assigns and :system, so the export draws what the board draws for its viewer. opts are that function's, and an option given here wins over the board's. The snapshot and the options are one call to the board process.

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: Visualize.Board.identity() | nil,
     item: Visualize.Event.item() | nil,
     frame: atom()
   }}
  | :error

The row behind a cell's keyboard point, {series, index} (spec/10 §17.2), its identity (spec/15 §13.2), its item and its frame: {:ok, %{row: row, identity: identity | nil, item: item | nil, frame: name}}, the identity {field, value}, or a map for a composite one the slot's schema declares (D-167), the item as a Visualize.Event's (D-170), 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(), Visualize.Board.identity() | nil) :: :ok

The board's selection (spec/15 §13.2): {field, value}, or a map from field to value for a composite identity (D-167), each field read through the board's links, or nil to clear it. A map of one field is held as its pair. 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.