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.
A source whose function takes a context (spec/08 §10.5.6) is given the variables at its
scope: the engine is shown every scope's own variables (Visualize.Board.own_vars/1)
when the board starts and on every vars/2 and vars/3, so a viewer's change to a
variable re-runs the context-taking sources that see it, and only those (D-229).
Budgets
Each bound slot's budget is derived from what its cell draws at its size
(Visualize.Board.Budget, spec/15 §8.2, D-226): its marks' plot widths and types, or
the design's budget, capped by :budget. Each feed is fetched once, at the largest
budget of the slots it fills, and each slot whose budget is smaller is handed the
delivery reduced to its own (Visualize.Feed.reduce/4, reduced_by: :visualize); a
slot bound to a virtual source is handed the source's output reduced to its own the
same way (D-230), the source's inputs asked the ceiling. A resize, or new variables,
recompute the budgets; a feed that honors :budget and whose budget changed is fetched
again.
The ceiling is the one hard bound (D-227): a slot whose delivery, at its own budget,
still holds more rows than :budget — a :budget feed that over-delivers, or an output
Visualize.Feed.reduce/4 cannot reduce — is handed to its cell without them, and the
cell is {:over_budget, rows, ceiling}, never drawn.
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 (defaultVisualize.Theme.default/0):system- the unit system every cell's measures are shown in,:metric,:us,:ukor: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, besideconfig :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 aresize/2(default1200):tick- the data rate, milliseconds between turns;nilturns only ontick/1(default1000):budget- the ceiling on every slot's budget: each bound slot is asked the budget its cell's plot width and marks derive, or its design declares, capped by this, and no slot is handed more rows than this;nilcaps nothing (defaultnil, spec/15 §8.2, D-226, D-227):max_heap- each cell's heap limit in words (default25_000_000):clock- the board's now, a function of no argument answering a UTCDateTime(defaultDateTime.utc_now/0):limits- the limits the components' designs are read under:boards- the boards the cells' drill targets may name, asVisualize.Board.validate/3takes them (default%{}):debounce- milliseconds a brushed window waits for the next, and a virtual source's pulse waits for more changes (default250);0applies each at once:virtual_sources- the modules of the host's virtual-source functions (use Visualize.VirtualSources), besideconfig :visualize, virtual_sources:(default[], spec/15 §14.6):virtual_source_reductions- the reductions a virtual source's call may spend (default100_000_000, spec/08 §10.5.4):virtual_source_timeout- milliseconds before a virtual source's call that has not answered is hung: the liveness bound, not a speed limit (default30_000):virtual_source_heap- words a virtual source's call may hold (default10_000_000):id- the board's id, each cell's chart root<id>-<cell>-chart(defaultvis-board-and a hash of the board's meta and cell names, spec/15 §8.2, D-188):name- a registered name
Summary
Functions
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, every cell prepared again, the budgets
recomputed and the virtual sources shown every scope's variables (spec/15 §5, §8.2,
§14.2): :ok, or {:error, errors}, nothing changed. A pulse that makes due at once
(debounce: 0) runs, and the cells are handed their data, before it answers.
One cell's own variables merged over its own, as vars/2 merges the board's (spec/15
§5, §8.2, D-229): :ok, or {:error, errors} — {:undeclared, :var, name} for a
variable the cell's component does not declare, {[:cells, cell], :unknown_key} for a
cell the board does not have.
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
@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
@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.
Returns a specification to start this module under a supervisor.
See Supervisor.
@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.
@spec export(GenServer.server(), :svg | :png | :pages | :html) :: String.t() | [String.t()] | {:ok, binary(), list()} | {:error, term()}
As export/3 with [] options.
@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.
@spec info(GenServer.server()) :: info()
The board's present state (spec/15 §8.2).
@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.
@spec reset_window(GenServer.server()) :: :ok
The window the board was started with, set as window/2 sets it (spec/15 §13.1).
@spec resize(GenServer.server(), number()) :: :ok
The container width set and every cell's box recomputed (spec/15 §1.6, §8.2); :ok.
@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.
@spec snapshot(GenServer.server()) :: Visualize.Board.Snapshot.t()
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.
@spec start(Supervisor.supervisor(), keyword()) :: DynamicSupervisor.on_start_child()
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.
@spec start_link(keyword()) :: GenServer.on_start()
A board started linked to the caller, for a host's own supervisor; as start/2 answers.
@spec stop(GenServer.server()) :: :ok
The board and its cells stopped; :ok.
@spec subscribe(GenServer.server()) :: {:ok, info()}
As subscribe/2 for the caller.
@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}.
@spec tick(GenServer.server()) :: :ok
One turn of the loop now (spec/15 §8.2); :ok once it is done.
@spec unsubscribe(GenServer.server(), pid()) :: :ok
A subscriber removed; :ok.
@spec vars(GenServer.server(), map()) :: :ok | {:error, [Visualize.Board.error(), ...]}
Board variables merged over the board's, every cell prepared again, the budgets
recomputed and the virtual sources shown every scope's variables (spec/15 §5, §8.2,
§14.2): :ok, or {:error, errors}, nothing changed. A pulse that makes due at once
(debounce: 0) runs, and the cells are handed their data, before it answers.
@spec vars(GenServer.server(), atom() | String.t(), map()) :: :ok | {:error, [Visualize.Board.error(), ...]}
One cell's own variables merged over its own, as vars/2 merges the board's (spec/15
§5, §8.2, D-229): :ok, or {:error, errors} — {:undeclared, :var, name} for a
variable the cell's component does not declare, {[:cells, cell], :unknown_key} for a
cell the board does not have.
@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.