Skip to main content
A node is a Rust crate with a #[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:
Each of -o, -i, and -T takes two values (a schema and a name), and you may pass at most one of each per create call. Add more ports later with node modify. Schemas accept both sensor_msgs/Image and sensor_msgs::Image; both canonicalize to the slash form.

Pick a trigger policy

The trigger policy decides when the node fires. Pass it with --policy SPEC:
The Cerulion lion pointing at a giant stopwatch β€” trigger policies decide exactly when a node fires
How the policy defaults when you omit --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 create errors otherwise.
  • 1+ inputs via -i only: no node-level policy is written; the runtime fires on any input arrival and emits a graph-build warning.
  • -T set: the policy becomes data_trigger for that trigger input.
A source-only node (zero inputs) has nothing to fire it, so you must give it an explicit non-data policy with --policy period_ms=N or --policy external. Combining a non-data --policy with -T is a conflict and errors.

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()

Open nodes/<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 lets cerulion graph run load it.
  • #[cerulion_node_impl] on the adjacent impl block lets tick() 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.
Import everything from the prelude, and import the message types you use:

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[], nested types) must be listed in the #[output(...)] attribute; you then write them with plain assignment too.
List simple variable fields by name in #[output(...)]:
For nested-typed variable fields, use the complex(...) form, for example #[output(data, complex(header))]. See the node macro reference for the full #[output] grammar.
Zero-copy write for large payloads: plain = assignment copies your buffer once. For camera drivers or codecs that can write directly into a destination buffer, use fill_from instead β€” your producer receives the destination buffer itself, skipping the copy:
FillFrom and SliceSource are re-exported from cerulion_core::prelude. See the node macro reference for details.

Build the node

cerulion node build <node_type> compiles the crate into a cdylib. Add --release for an optimized build.
node build shells out to cargo, so cargo must be on your PATH.
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.
cerulion node list prints a table of types with input/output counts and a short policy label.
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.
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).
Adding a trigger input with -T also sets the data_trigger policy. To make a node externally triggered, use --policy external.
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.