Skip to content

structural

structural #

Nodes that control the organization and execution order of other procedures.

Structural procedures control the organization and execution order of other procedures: sequential execution, parallel execution, optional steps, and the append-an-element loop.

The loop carries its own semantics: [loop_templates][] answers "which nodes does the device clone per iteration?" for a whole graph, so the serializer only has to emit the answer.

OptionalProcedure #

Bases: Procedure[Void]

Mark a procedure as optional, allowing it to be skipped or fail gracefully.

The wrapped procedure can fail without causing the overall workflow to fail, and its result is discarded. Useful for non-critical actions; build one with cg.do_optional(step) or the cg.optional() block.

Attributes:

Name Type Description
procedure Procedure[CGType]

The procedure to run optionally; its result is discarded.

Source code in capturegraph-lib/capturegraph/procedures/nodes/process/structural.py
@make_procedure
class OptionalProcedure(Procedure[Void]):
    """Mark a procedure as optional, allowing it to be skipped or fail gracefully.

    The wrapped procedure can fail without causing the overall workflow to fail,
    and its result is discarded. Useful for non-critical actions; build one with
    ``cg.do_optional(step)`` or the ``cg.optional()`` block.

    Attributes:
        procedure: The procedure to run optionally; its result is discarded.
    """

    procedure: Procedure[CGType]

PreloadProcedure #

Bases: Procedure[T]

Run procedure preloaded with default's result, and return its result.

The runtime evaluates default (typically a constant) and preloads it into procedure — the same preload a cache uses to re-fill a node from stored data — so the wrapped node starts complete and never blocks the capture. Wrapping a user-input node turns its question into an option: a control pre-answered with a sensible default the user may still adjust (RAW on a photo, a frame-rate preference on a video). node.preload(default) builds this.

Attributes:

Name Type Description
procedure Procedure[CGType]

The procedure to preload; its (possibly user-adjusted) result is returned.

default Procedure[CGType]

The default to preload, matching procedure's type by structure.

Source code in capturegraph-lib/capturegraph/procedures/nodes/process/structural.py
@make_procedure
class PreloadProcedure[T: CGType](Procedure[T]):
    """Run ``procedure`` preloaded with ``default``'s result, and return its result.

    The runtime evaluates ``default`` (typically a constant) and preloads it into
    ``procedure`` — the same preload a cache uses to re-fill a node from stored
    data — so the wrapped node starts complete and never blocks the capture.
    Wrapping a user-input node turns its question into an *option*: a control
    pre-answered with a sensible default the user may still adjust (RAW on a
    photo, a frame-rate preference on a video). ``node.preload(default)``
    builds this.

    Attributes:
        procedure: The procedure to preload; its (possibly user-adjusted)
            result is returned.
        default: The default to preload, matching ``procedure``'s type by
            structure.
    """

    procedure: Procedure[CGType]
    default: Procedure[CGType]

    def _resolve_type(self) -> type[CGType]:
        """The wrapped procedure's type, checked against the default's."""
        check_type(
            self.default.return_type,
            self.procedure.return_type,
            "PreloadProcedure default",
        )
        return self.procedure.return_type

ProcedureSequence #

Bases: Procedure[Void]

Execute a list of procedures in sequential order.

Each procedure runs to completion before the next. A step may return a value (e.g. a CacheProcedure returns the value it stored); the sequence runs it for effect and discards the result, returning Void.

Attributes:

Name Type Description
procedures Substeps

List of procedures to execute in order.

Source code in capturegraph-lib/capturegraph/procedures/nodes/process/structural.py
@make_procedure
class ProcedureSequence(Procedure[Void]):
    """Execute a list of procedures in sequential order.

    Each procedure runs to completion before the next. A step may return a value
    (e.g. a ``CacheProcedure`` returns the value it stored); the sequence runs it
    for effect and discards the result, returning ``Void``.

    Attributes:
        procedures: List of procedures to execute in order.
    """

    procedures: Substeps

ProcedureSet #

Bases: Procedure[Void]

Execute a list of procedures concurrently (in parallel).

All procedures in the set start together and can complete in any order; their results are discarded and the set returns Void.

Attributes:

Name Type Description
procedures Substeps

List of procedures to execute concurrently.

Source code in capturegraph-lib/capturegraph/procedures/nodes/process/structural.py
@make_procedure
class ProcedureSet(Procedure[Void]):
    """Execute a list of procedures concurrently (in parallel).

    All procedures in the set start together and can complete in any order;
    their results are discarded and the set returns ``Void``.

    Attributes:
        procedures: List of procedures to execute concurrently.
    """

    procedures: Substeps

ProcedureWhile #

Bases: Procedure[Void]

Repeatedly run a body, appending one array element per iteration.

Do-while semantics: body runs at least once, then condition is evaluated; while it is true (and max_iterations is not reached) the body runs again, each iteration appending a fresh element to array_path (via a PathAppend node inside the body). The with cg.while_repeat(condition, array_path): block authors this node, yielding the appended element to cache into; construct it directly only for a hand-built graph.

On the device, the body and condition are duplicated per iteration (template instantiation), so each iteration gets independent state and its own array element. Loop-invariant inputs (array_path, max_iterations) are referenced without duplication. Which nodes those are is [loop_templates][], reported on the wire as the template_nodes setting.

Attributes:

Name Type Description
body Procedure[Void]

The loop body to run each iteration (typically a ProcedureSequence).

condition Input[Bool]

Boolean procedure evaluated after each iteration; loop continues while true.

array_path Procedure[Path]

The Path to the Array each iteration appends into.

max_iterations Input[Number]

Maximum iteration count (a Number value or a procedure producing one), evaluated once.

Source code in capturegraph-lib/capturegraph/procedures/nodes/process/structural.py
@make_procedure
class ProcedureWhile(Procedure[Void]):
    """Repeatedly run a body, appending one array element per iteration.

    Do-while semantics: ``body`` runs at least once, then ``condition`` is
    evaluated; while it is true (and ``max_iterations`` is not reached) the body
    runs again, each iteration appending a fresh element to ``array_path`` (via a
    ``PathAppend`` node inside the body). The ``with cg.while_repeat(condition,
    array_path):`` block authors this node, yielding the appended element to cache
    into; construct it directly only for a hand-built graph.

    On the device, the body and condition are duplicated per iteration (template
    instantiation), so each iteration gets independent state and its own array
    element. Loop-invariant inputs (``array_path``, ``max_iterations``) are
    referenced without duplication. Which nodes those are is [loop_templates][],
    reported on the wire as the ``template_nodes`` setting.

    Attributes:
        body: The loop body to run each iteration (typically a ProcedureSequence).
        condition: Boolean procedure evaluated after each iteration; loop
            continues while true.
        array_path: The ``Path`` to the ``Array`` each iteration appends into.
        max_iterations: Maximum iteration count (a ``Number`` value or a procedure
            producing one), evaluated once.
    """

    body: Procedure[Void]
    condition: Input[Bool]
    array_path: Procedure[Path]
    max_iterations: Input[Number]

RequiredProcedure #

Bases: Procedure[Void]

Run a procedure for effect and discard its result.

Wraps any procedure into a Void step: it runs procedure to completion, drops whatever it returns, and is the required counterpart of [OptionalProcedure][]. cg.do(step) builds this, and |= / &= wrap each cache in one so a stored value is never left dangling.

Attributes:

Name Type Description
procedure Procedure[CGType]

The procedure to run; its result is discarded.

Source code in capturegraph-lib/capturegraph/procedures/nodes/process/structural.py
@make_procedure
class RequiredProcedure(Procedure[Void]):
    """Run a procedure for effect and discard its result.

    Wraps any procedure into a ``Void`` step: it runs ``procedure`` to completion,
    drops whatever it returns, and is the required counterpart of
    [OptionalProcedure][]. ``cg.do(step)`` builds this, and ``|=`` / ``&=``
    wrap each cache in one so a stored value is never left dangling.

    Attributes:
        procedure: The procedure to run; its result is discarded.
    """

    procedure: Procedure[CGType]

loop_templates(graph) #

For each ProcedureWhile in graph, the nodes it clones per iteration.

A node loops when the loop's body or condition reaches it — stopping at the loop-invariant array_path and max_iterations — and nothing outside that reach depends on it. Which nodes qualify is a property of the whole graph, not of the loop alone, so this takes the graph and runs once it is complete.

Parameters:

Name Type Description Default
graph Mapping[str, Procedure[CGType]]

Every node reachable from the serialization root, by uuid.

required

Returns:

Type Description
dict[str, list[str]]

The looping nodes' uuids, keyed by their ProcedureWhile's uuid.

Raises:

Type Description
ValueError

If a node outside a loop depends on one the loop clones (each iteration has its own copy, so the reference names no single node), or an inner loop clones a node its enclosing loop does not.

Source code in capturegraph-lib/capturegraph/procedures/nodes/process/structural.py
def loop_templates(
    graph: Mapping[str, Procedure[CGType]],
) -> dict[str, list[str]]:
    """For each ``ProcedureWhile`` in ``graph``, the nodes it clones per iteration.

    A node loops when the loop's ``body`` or ``condition`` reaches it — stopping at
    the loop-invariant ``array_path`` and ``max_iterations`` — and nothing outside
    that reach depends on it. Which nodes qualify is a property of the whole graph,
    not of the loop alone, so this takes the graph and runs once it is complete.

    Args:
        graph: Every node reachable from the serialization root, by uuid.

    Returns:
        The looping nodes' uuids, keyed by their ``ProcedureWhile``'s uuid.

    Raises:
        ValueError: If a node outside a loop depends on one the loop clones (each
            iteration has its own copy, so the reference names no single node), or
            an inner loop clones a node its enclosing loop does not.
    """
    loops = {uuid: node for uuid, node in graph.items() if isinstance(node, ProcedureWhile)}
    if not loops:
        return {}

    dependents: dict[str, set[str]] = {}
    for uuid, node in graph.items():
        for dependency in _dependencies(node):
            dependents.setdefault(dependency.uuid, set()).add(uuid)

    templates = {uuid: _looping_nodes(loop, dependents) for uuid, loop in loops.items()}
    _check_no_leaks(graph, templates)
    _check_nesting(graph, templates)
    return {uuid: sorted(template) for uuid, template in templates.items()}