Resolves a surface's flat component dictionary into a tree of
A2UICore.Resolution.Nodes.
Child references become nodes, collection templates become one node
per item (each in its item's data scope), components that have not
arrived yet become :pending placeholders that are replaced when
their definition does, and a component referenced by one of its own
descendants is cut with a :cyclic placeholder.
The resolver is plain data. It does not observe anything: after the
surface changed, hand the new surface to update/2 and read what
happened from the notifications it returns.
{resolver, notifications} = NodeResolver.new(surface)
surface = Surface.put_components(surface, components)
{resolver, notifications} = NodeResolver.update(resolver, surface)Identity
A position is identified by the edge that leads to it (the parent
position, the property, and the component and data path it points at).
While the edge keeps leading to the same kind of node, the node keeps
its ref; only its props change. A node is replaced — and the old
one destroyed — when the component behind the edge appears, goes away
or changes type, when a sibling ordinal shifts, or when its parent is
destroyed.
Notifications
{:node_created, ref}— a new position is mounted;{:node_updated, ref}— a node that was already mounted now has different props (reported once perupdate/2, whatever number of properties changed);{:node_destroyed, ref}— a mounted node is gone; reported once;{:error | :warning, payload}— a diagnostic (an unknown component type, a cyclic reference, a binding that does not resolve), reported when the condition appears and again only after it went away.
write/4 and invoke/5 are the interaction half: they say which path
a write reaches and which events and calls an action produces, from
the surface as it is now.
Agent functions
From v1.0 a function call that the surface's catalogs cannot run
locally is the agent's (see
Agent functions).
The resolver keeps what it knows of those calls in agent_calls.
When a property needs a call it has not seen, update/2 marks the
call pending and reports {:agent_call, %{key: key, call: call}}: the
renderer sends call to the agent and later hands the outcome to
settle_agent_call/3, then calls update/2 again. Until then the
property is nil and its node is busy. Settled calls are kept for
the properties and actions that use them again; past 256 calls, the
settled ones no property uses are forgotten.
Summary
Functions
Number of mounted nodes, placeholders included.
Destroys every node and stops the resolver; further updates do nothing. Idempotent.
Invokes the action in property of node ref, evaluating its dynamic
parts against surface as it is now.
Resolves surface from scratch.
The mounted node with ref, or nil.
The mounted nodes by position: "root", "root/child",
"root/children/0", "root/tabs/1/child". Positions describe where a
node sits in the tree; they are not identities.
The root node, or nil while the root component is missing.
Records the outcome of the agent function call key (from an
{:agent_call, %{key: key}} notification): {:ok, value} or
{:error, payload}. Call update/2 afterwards to resolve the
properties that use it. A call the resolver no longer knows is
ignored.
Marks the agent function call key as sent, so that properties that
use it wait for its outcome instead of reporting it again; for the
calls an action's interactions ask for (invoke/5).
Reconciles the tree with surface's current components and data.
What a write to property of node ref changes: {:ok, path, value}
when the property is a binding to a data path, an error otherwise.
Apply it with A2UICore.State.Surface.set_data/3 and update/2.
Types
@type notification() :: {:node_created, pos_integer()} | {:node_updated, pos_integer()} | {:node_destroyed, pos_integer()} | {:error | :warning, map()} | {:agent_call, map()}
@type t() :: %A2UICore.Resolution.NodeResolver{ agent_calls: %{ optional(term()) => A2UICore.Resolution.DataContext.agent_call_state() }, disposed?: boolean(), edges: %{optional(String.t()) => pos_integer()}, next_ref: pos_integer(), nodes: %{optional(pos_integer()) => A2UICore.Resolution.Node.t()}, reported: MapSet.t(), root: pos_integer() | nil, surface_id: String.t() | nil }
Functions
@spec count(t()) :: non_neg_integer()
Number of mounted nodes, placeholders included.
@spec dispose(t()) :: {t(), [notification()]}
Destroys every node and stops the resolver; further updates do nothing. Idempotent.
@spec invoke(t(), A2UICore.State.Surface.t(), pos_integer(), String.t(), DateTime.t()) :: {:ok, [interaction()]} | {:error, A2UICore.Error.t()}
Invokes the action in property of node ref, evaluating its dynamic
parts against surface as it is now.
An event action yields {:action_event, event} (see
A2UICore.State.Surface.dispatch_action/4); a function-call action
runs the catalog function and yields no event. Resolution problems
come back as {:error, payload} / {:warning, payload} entries, and
what the functions ask for as {:write, ...} / {:messages, ...}
entries for the renderer to apply (see
A2UICore.Resolution.DataContext).
@spec new(A2UICore.State.Surface.t()) :: {t(), [notification()]}
Resolves surface from scratch.
@spec node(t(), pos_integer()) :: A2UICore.Resolution.Node.t() | nil
The mounted node with ref, or nil.
@spec positions(t()) :: %{optional(String.t()) => A2UICore.Resolution.Node.t()}
The mounted nodes by position: "root", "root/child",
"root/children/0", "root/tabs/1/child". Positions describe where a
node sits in the tree; they are not identities.
@spec root(t()) :: A2UICore.Resolution.Node.t() | nil
The root node, or nil while the root component is missing.
Records the outcome of the agent function call key (from an
{:agent_call, %{key: key}} notification): {:ok, value} or
{:error, payload}. Call update/2 afterwards to resolve the
properties that use it. A call the resolver no longer knows is
ignored.
Marks the agent function call key as sent, so that properties that
use it wait for its outcome instead of reporting it again; for the
calls an action's interactions ask for (invoke/5).
@spec update(t(), A2UICore.State.Surface.t()) :: {t(), [notification()]}
Reconciles the tree with surface's current components and data.
Returns the new resolver and the notifications, in the order things were found (creations and updates, then destructions).
@spec write(t(), pos_integer(), String.t(), term()) :: {:ok, String.t(), term()} | {:error, A2UICore.Error.t()}
What a write to property of node ref changes: {:ok, path, value}
when the property is a binding to a data path, an error otherwise.
Apply it with A2UICore.State.Surface.set_data/3 and update/2.