#[cerulion_node] struct and a tick() method. This guide takes you from cerulion node create to a node that reads inputs, writes outputs, and is ready to stage into a graph.
For the full attribute and policy grammar, see the node macro reference and trigger policies reference.
Run these commands from inside a workspace (a directory with a
[workspace]
Cargo.toml and a graphs/ folder). Create one with cerulion workspace create <name>.Create the node type
cerulion node create <node_type> scaffolds nodes/<node_type>/ with a Cargo.toml (cdylib) and a src/lib.rs macro template. The type name becomes the folder name and, PascalCased, the struct name. It must be non-empty, alphanumeric or underscore, and must not start with a digit.
Add ports while creating the node:
Pick a trigger policy
The trigger policy decides when the node fires. Pass it with--policy SPEC:

--policy depends on the node’s inputs:
- 0 inputs (no
-i, no-T): a source-only node must declare a non-data policy.cerulion node createerrors otherwise. - 1+ inputs via
-ionly: no node-level policy is written; the runtime fires on any input arrival and emits a graph-build warning. -Tset: the policy becomesdata_triggerfor that trigger input.
Create a periodic source node
A camera with no inputs needs a period:
Create a data-triggered consumer
A detector that fires whenever an image arrives:
Write the tick()
Opennodes/<node_type>/src/lib.rs. The template pairs two macros:
#[cerulion_node(...)]on the struct declares the node and its trigger policy, and generates the glue that letscerulion graph runload it.#[cerulion_node_impl]on the adjacentimplblock letstick()use plain field access for ports — reads and writes go directly to shared memory, zero-copy. It takes no arguments, and the struct must appear before the impl block.
Fixed vs variable fields
How you write an output field depends on whether it is fixed-size or variable-length:- Fixed primitive fields (for example
x,y,z,height,width) are written directly:self.linear_velocity.x = 0.3;. The write goes straight to shared memory. - Variable-length fields (
string,T[]) are also written with plain assignment, but they are read through an accessor —self.scan.ranges()— because their length is only known at runtime.
#[output] is the only form you need, for every schema:
Earlier versions required you to list variable fields in the attribute
(
#[output(data, encoding)]) and to wrap nested ones in complex(...). Both
forms are gone — write #[output] and assign the fields.Build the node
cerulion node build <node_type> compiles the crate into a cdylib. Add --release for an optimized build.
A successful build prints
Built '<node_type>'. On failure the CLI prints
Error: with the cargo output, and no cdylib is produced.Inspect and adjust
Use these commands to review and edit node types after creation.List node types
List node types
cerulion node list prints a table of types with input/output counts and a short policy label.Show one node's details
Show one node's details
cerulion node info <node_type> prints the type, policy, and each port with its schema. Metadata is parsed from src/lib.rs; there is no sidecar file.Add ports later
Add ports later
cerulion node modify <node_type> mutates src/lib.rs in place, preserving the tick body and comments. It takes the same -i, -T, -o, and --policy flags (at most one of each per call).-T also sets the data_trigger policy. To make a node externally triggered, use --policy external.Delete a node type
Delete a node type
cerulion node delete <node_type> removes the node crate and its workspace member entry.Next steps
Wire and run a graph
Stage these node types into a graph and run it.
Trigger policies
The full
--policy grammar and defaulting matrix.Backpressure and deadlines
Queue policies, rate caps, and reacting to late data in code.