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 (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 point budget asked of every feed (defaultnil):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_timeout- milliseconds a virtual source's call may run (default1_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, 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
@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, and every cell prepared again (spec/15 §5,
§8.2): :ok, or {:error, errors}.
@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.