nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
Public Member Functions | Static Public Member Functions | List of all members
rclcpp::Executor Class Reference

#include <executor.hpp>

Public Member Functions

Result add_on_shutdown_callback (ShutdownCallback callback, void *context=nullptr, OnShutdownCallbackHandle *out=nullptr)
 
Result add_pre_shutdown_callback (ShutdownCallback callback, void *context=nullptr, PreShutdownCallbackHandle *out=nullptr)
 
Result cancel ()
 
Result count_publishers (const char *topic_name, size_t *out_count)
 
Result count_subscribers (const char *topic_name, size_t *out_count)
 
Result create_node (::rclcpp::Node &out, const char *name, const char *ns=nullptr)
 
 Executor ()
 Default constructor — creates an uninitialized executor.
 
 Executor (Executor &&other)
 
Result get_client_names_and_types_by_node (const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx)
 phase-381 W4 — what services one named node CALLS, with the types.
 
Result get_node_names (nros_cpp_node_visit_fn visit, void *ctx)
 
Result get_publisher_names_and_types_by_node (const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx)
 phase-381 W4 — what one named node PUBLISHES, with the types.
 
Result get_publishers_info_by_topic (const char *topic_name, nros_cpp_endpoint_info_visit_fn visit, void *ctx)
 
Result get_service_names_and_types (nros_cpp_names_and_types_visit_fn visit, void *ctx)
 
Result get_service_names_and_types_by_node (const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx)
 phase-381 W4 — what services one named node SERVES, with the types.
 
Result get_subscription_names_and_types_by_node (const char *node_name, const char *node_namespace, nros_cpp_names_and_types_visit_fn visit, void *ctx)
 
Result get_subscriptions_info_by_topic (const char *topic_name, nros_cpp_endpoint_info_visit_fn visit, void *ctx)
 phase-381 W4 — the subscriptions on topic_name, one visit each.
 
Result get_topic_names_and_types (nros_cpp_names_and_types_visit_fn visit, void *ctx)
 
void * handle ()
 
bool is_spinning () const
 
NodeBuilder node_builder (const char *name)
 
bool ok () const
 Check if the executor is initialized.
 
Executor & operator= (Executor &&other)
 
Result ping (int32_t timeout_ms)
 
bool remove_on_shutdown_callback (OnShutdownCallbackHandle handle)
 
bool remove_pre_shutdown_callback (PreShutdownCallbackHandle handle)
 
Result shutdown ()
 Shut down the executor and close the middleware connection.
 
Result spin (int32_t poll_ms=10)
 
Result spin_for (uint32_t duration_ms, int32_t poll_ms=10)
 
template<typename T = void>
Result spin_once ()
 
Result spin_once (int32_t timeout_ms)
 
 ~Executor ()
 Destructor — shuts down if still active.
 

Static Public Member Functions

static Result create (Executor &out, const char *locator, uint8_t domain_id, const char *session_name)
 
static Result create (Executor &out, const char *locator=nullptr, uint8_t domain_id=0)
 
static Result create_with_rmw (Executor &out, const char *rmw, const char *locator=nullptr, uint8_t domain_id=0, const char *session_name="nros_cpp")
 

Detailed Description

Explicit executor for managing ROS 2 entities and spinning.

Mirrors rclcpp::executors::SingleThreadedExecutor. Provides an explicit alternative to the global rclcpp::init()/rclcpp::spin_once() free functions.

The executor uses inline opaque storage — no heap allocation required.

Usage:

RCLCPP_TRY(executor.create_node(node, "my_node"));
// Create publishers, subscriptions, etc. on node...
while (executor.ok()) {
executor.spin_once(10);
}
executor.shutdown();
Definition client.hpp:129
Definition executor.hpp:116
Result create_node(::rclcpp::Node &out, const char *name, const char *ns=nullptr)
Definition node.hpp:3084
bool ok() const
Check if the executor is initialized.
Definition executor.hpp:492
static Result create(Executor &out, const char *locator=nullptr, uint8_t domain_id=0)
Definition executor.hpp:130
Result shutdown()
Shut down the executor and close the middleware connection.
Definition executor.hpp:573
Result spin_once(int32_t timeout_ms)
Definition executor.hpp:228
Definition node.hpp:703
#define RCLCPP_TRY(expr)
Definition result.hpp:258

Constructor & Destructor Documentation

◆ Executor() [1/2]

rclcpp::Executor::Executor ( )
inline

Default constructor — creates an uninitialized executor.

◆ ~Executor()

rclcpp::Executor::~Executor ( )
inline

Destructor — shuts down if still active.

◆ Executor() [2/2]

rclcpp::Executor::Executor ( Executor &&  other)
inline

Member Function Documentation

◆ add_on_shutdown_callback()

Result rclcpp::Executor::add_on_shutdown_callback ( ShutdownCallback  callback,
void *  context = nullptr,
OnShutdownCallbackHandle *  out = nullptr 
)
inline

Register a callback to run AFTER this executor's entities are torn down — rclcpp's Context::add_on_shutdown_callback / rclcpp::on_shutdown.

The entities are gone by the time it runs, so anything that needs the wire belongs in [add_pre_shutdown_callback] instead. Use this for what survives the middleware: releasing a GPIO, stopping a peripheral clock, writing a log line.

See also
add_pre_shutdown_callback for the parameter and error contract.

◆ add_pre_shutdown_callback()

Result rclcpp::Executor::add_pre_shutdown_callback ( ShutdownCallback  callback,
void *  context = nullptr,
PreShutdownCallbackHandle *  out = nullptr 
)
inline

Register a callback to run BEFORE this executor's entities are torn down — rclcpp's Context::add_pre_shutdown_callback. Issue 0790.

This is the load-bearing half and the one with no workaround. The callback runs while every publisher, subscription, service and client still works, so a node can publish a final state, answer a last request, park an actuator or release a bus. After teardown it cannot: on a device there is no OS to reclaim a claimed SPI bus, an armed DMA channel or an actuator holding its last commanded position.

The callbacks run from [shutdown] (and from the destructor, which shuts down if still active). They are a CLEAN-STOP facility: a watchdog reset, a hard fault or an abort does not come through here, and nothing backed by a static table could promise otherwise.

Parameters
callbackFunction to invoke. Must not be null.
contextOpaque pointer handed back to callback. Must stay valid until the callback runs or is removed.
outReceives the handle remove_pre_shutdown_callback takes. Optional.
Returns
Success, ErrorCode::Full when the fixed table is exhausted (NROS_EXECUTOR_MAX_SHUTDOWN_CBS, default 2), or ErrorCode::NotInitialized.

◆ cancel()

Result rclcpp::Executor::cancel ( )
inline

Ask a running [spin] to stop, WITHOUT tearing down the session — rclcpp::Executor::cancel. phase-417 W4.c.

ADOPT-BOUNDED (RFC-0089): cancel sets a flag the spin loop observes at the NEXT POLL BOUNDARY, so it returns BEFORE spinning has actually stopped; [is_spinning] is the observable that tells you when it has. The boundary is one poll_ms wide (the spin() argument), so the delay is a duration you chose rather than an unknown. rclcpp's cancel() makes the same "returns immediately" promise; naming the width is the part we add.

This is the verb a signal handler wants. [shutdown] is the other one and still tears everything down: after cancel() the executor is still ok(), still owns its publishers, subscriptions, services and clients, and can be spun again with no rediscovery. Collapsing the two — which is what a C++ node had to do before this existed — costs a full discovery round on every mode change.

Safe to call from another thread or a signal handler.

◆ count_publishers()

Result rclcpp::Executor::count_publishers ( const char *  topic_name,
size_t *  out_count 
)
inline

phase-381 W4 — how many publishers are visible on topic_name.

topic_name is a ROS name ("/chatter"). The count reflects what has been DISCOVERED, so it can be low right after startup and is never a proof of absence.

◆ count_subscribers()

Result rclcpp::Executor::count_subscribers ( const char *  topic_name,
size_t *  out_count 
)
inline

phase-381 W4 — how many subscribers are visible on topic_name. See [count_publishers] for the caveats.

◆ create() [1/2]

static Result rclcpp::Executor::create ( Executor &  out,
const char *  locator,
uint8_t  domain_id,
const char *  session_name 
)
inlinestatic

Create and initialize an executor with an explicit session name.

session_name flows through to the XRCE-DDS RMW backend as the per-process key derivation seed. Two processes sharing one XRCE Agent MUST use distinct names; see rclcpp::init's named overload for the full discussion.

◆ create() [2/2]

static Result rclcpp::Executor::create ( Executor &  out,
const char *  locator = nullptr,
uint8_t  domain_id = 0 
)
inlinestatic

Create and initialize an executor.

Opens a middleware connection. This is the explicit alternative to rclcpp::init().

Parameters
outReceives the initialized executor.
locatorMiddleware locator (e.g., "tcp/127.0.0.1:7447"), or nullptr.
domain_idROS domain ID (0-232).
Returns
Result indicating success or failure.

◆ create_node()

Result rclcpp::Executor::create_node ( ::rclcpp::Node &  out,
const char *  name,
const char *  ns = nullptr 
)
inline

Create a node on this executor.

Parameters
outReceives the initialized node.
nameNode name (null-terminated).
nsNode namespace (null-terminated); nullptr or "" = unset (inherit THIS executor's namespace), "/" = the root. See rclcpp::Node::create (issue 1473).
Returns
Result indicating success or failure.

◆ create_with_rmw()

static Result rclcpp::Executor::create_with_rmw ( Executor &  out,
const char *  rmw,
const char *  locator = nullptr,
uint8_t  domain_id = 0,
const char *  session_name = "nros_cpp" 
)
inlinestatic

Issue 1050 defect (3) — [create](Executor::create) against a named RMW backend.

rmw is the baked rung of RFC-0045's precedence model A: a hosted $NROS_RMW still wins, and nullptr / "" means "name none", which resolves only when exactly one backend is registered. See rclcpp::init_with_rmw for why naming one is sometimes the only way to get the backend the image declared.

◆ get_client_names_and_types_by_node()

Result rclcpp::Executor::get_client_names_and_types_by_node ( const char *  node_name,
const char *  node_namespace,
nros_cpp_names_and_types_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — what services one named node CALLS, with the types.

◆ get_node_names()

Result rclcpp::Executor::get_node_names ( nros_cpp_node_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — every node on the graph, with its namespace.

visit(name, ns, enclave) is called once per node; enclave is nullptr where the backend tracks none, which is what lets one call answer both rmw_get_node_names forms. Return false to stop early.

Takes a plain function pointer plus ctx rather than a std::function: this header is compiled -nostdinc++ against Zephyr's minimal libcpp in embedded builds, where <functional> does not exist (issue 0112). Every string is BORROWED for the duration of the call.

Reports what has been DISCOVERED and never blocks. The first call after startup legitimately sees a partial graph — the backend keeps a standing query fed by the spin loop — so poll rather than calling once and concluding. An empty enumeration means "nobody seen yet", never "nobody exists", and ErrorCode::Unsupported (a backend with no graph) stays distinct from it.

◆ get_publisher_names_and_types_by_node()

Result rclcpp::Executor::get_publisher_names_and_types_by_node ( const char *  node_name,
const char *  node_namespace,
nros_cpp_names_and_types_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — what one named node PUBLISHES, with the types.

◆ get_publishers_info_by_topic()

Result rclcpp::Executor::get_publishers_info_by_topic ( const char *  topic_name,
nros_cpp_endpoint_info_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — the publishers on topic_name, one visit each.

The endpoint carries no QoS: the GRANTED profile is what would answer "why is nothing arriving", no backend can read one back yet, and reporting the remote's DECLARED profile would be a confident wrong answer.

◆ get_service_names_and_types()

Result rclcpp::Executor::get_service_names_and_types ( nros_cpp_names_and_types_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — every service on the graph, with its types. As [get_topic_names_and_types], over servers and clients.

◆ get_service_names_and_types_by_node()

Result rclcpp::Executor::get_service_names_and_types_by_node ( const char *  node_name,
const char *  node_namespace,
nros_cpp_names_and_types_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — what services one named node SERVES, with the types.

◆ get_subscription_names_and_types_by_node()

Result rclcpp::Executor::get_subscription_names_and_types_by_node ( const char *  node_name,
const char *  node_namespace,
nros_cpp_names_and_types_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — what one named node SUBSCRIBES to, with the types.

subscription, not subscriber: the C++ surface takes rclcpp's vocabulary (create_subscription, Subscription<T>, get_subscriptions_info_by_topic). rclcpp has no *_by_node form for subscriptions at all, so the WORD comes from its vocabulary rather than from a method it lacks. The C surface says subscriber because rcl does, and the vtable slot because upstream rmw does.

◆ get_subscriptions_info_by_topic()

Result rclcpp::Executor::get_subscriptions_info_by_topic ( const char *  topic_name,
nros_cpp_endpoint_info_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — the subscriptions on topic_name, one visit each.

◆ get_topic_names_and_types()

Result rclcpp::Executor::get_topic_names_and_types ( nros_cpp_names_and_types_visit_fn  visit,
void *  ctx 
)
inline

phase-381 W4 — every topic on the graph, with the types on it.

visit(name, types, types_count) is called once per distinct TOPIC: a topic carrying two types is one call with two entries, not two calls. types_count may legitimately be 0 on a partially discovered graph. Same discovery caveat as [get_node_names].

◆ handle()

void * rclcpp::Executor::handle ( )
inline

Get the raw executor storage (for advanced use).

Non-const: downstream FFI mutates executor state through this pointer (e.g. spin_once), so exposing it as const would be a lie. Callers that only need to observe the handle should do so through methods on Executor directly.

◆ is_spinning()

bool rclcpp::Executor::is_spinning ( ) const
inline

Is a spin loop running on this executor right now? — rclcpp::Executor::is_spinning. phase-417 W4.c.

A DIFFERENT question from [ok], which answers "is this executor INITIALISED" — an executor that has never spun is ok() and not spinning, and one that has been cancelled mid-poll is both. Before this existed, ok() was the closest thing on offer and it answered neither half of the cancel envelope.

This is the second half of [cancel]'s contract: poll it to learn when the cancel has been ACTED on.

const because it is a pure read, and a predicate a caller cannot ask of a const Executor& is a predicate with an arbitrary restriction. The FFI slot takes void* because every OTHER nros_cpp_executor_* entry point mutates through it (see [handle]); this one does not, and the const_cast is confined to that mismatch.

◆ node_builder()

NodeBuilder rclcpp::Executor::node_builder ( const char *  name)
inline

Phase 104.C.9 — chainable Node-creation builder.

Mirrors Rust's Executor::node_builder(name).rmw(...).locator(...) .domain_id(...).namespace(...).sched(...).build(). Use this when binding a Node to a specific RMW backend, locator, domain, or SchedContext. Definition follows the full NodeBuilder class in node.hpp.

◆ ok()

bool rclcpp::Executor::ok ( ) const
inline

Check if the executor is initialized.

◆ operator=()

Executor & rclcpp::Executor::operator= ( Executor &&  other)
inline

◆ ping()

Result rclcpp::Executor::ping ( int32_t  timeout_ms)
inline

Phase 124.F.3 — session-level connectivity probe.

Wire-level round-trip ("is the peer / agent / router reachable?") with timeout_ms budget. Returns Result::success() on reply, ErrorCode::Timeout on no reply, ErrorCode::Unsupported when the active backend can't probe. Mirrors micro-ROS's rmw_uros_ping_agent.

Useful for reconnect-on-link-loss patterns — call periodically and tear down / re-open the executor on timeout.

◆ remove_on_shutdown_callback()

bool rclcpp::Executor::remove_on_shutdown_callback ( OnShutdownCallbackHandle  handle)
inline

Remove a registered on-shutdown callback — rclcpp's Context::remove_on_shutdown_callback.

See also
remove_pre_shutdown_callback

◆ remove_pre_shutdown_callback()

bool rclcpp::Executor::remove_pre_shutdown_callback ( PreShutdownCallbackHandle  handle)
inline

Remove a registered pre-shutdown callback — rclcpp's Context::remove_pre_shutdown_callback.

bool for rclcpp's reason: "it was not there" is an ordinary answer (the callback may already have run), not an error.

Returns
true when handle named a live callback.

◆ shutdown()

Result rclcpp::Executor::shutdown ( )
inline

Shut down the executor and close the middleware connection.

◆ spin()

Result rclcpp::Executor::spin ( int32_t  poll_ms = 10)
inline

Spin until this executor is shut down (blocking) — rclcpp::Executor::spin.

Issue 0338 — this verb used to mean the OPPOSITE here: spin was the BOUNDED form and there was no way to say "spin forever" on an executor, while spin blocks until shutdown in rclcpp, in the C API (nros_executor_spin) and in Rust. A user porting rclcpp code wrote exec.spin() and it did not compile; reaching for spin(ms) instead silently returned early. The bounded form is now [spin_for].

Exit condition: [cancel] on THIS executor — typically from a signal handler or another thread, which is rclcpp's clean-shutdown idiom. Returns the first non-success spin_once result, or success after a clean cancel.

phase-417 W4.c — the loop MOVED. It used to be right here, spelled while (initialized_), which made [shutdown] the only way out; and shutdown() calls nros_cpp_fini, so a C++ node could not stop spinning without tearing down the middleware and paying a full discovery round to come back. Two things were wrong with that: the missing verb, and a polling loop living in the wrapper (RFC-0020 violation class 2, the same defect issue 0329 fixed for the BOUNDED form). Both are gone — nros_cpp_spin is the loop, Rust-side, and this is a forwarder.

A concurrent shutdown() is no longer the supported way to end a spin. It never really was: it frees the executor the loop is polling. Say cancel(), then shutdown() once spin() has returned.

Parameters
poll_msIndividual spin_once timeout (default: 10ms).

◆ spin_for()

Result rclcpp::Executor::spin_for ( uint32_t  duration_ms,
int32_t  poll_ms = 10 
)
inline

Spin for a bounded duration (blocking) — rclcpp's spin_some(max_duration).

Repeatedly calls spin_once() until duration_ms has elapsed.

Parameters
duration_msTotal time to spin, in milliseconds.
poll_msIndividual spin_once timeout (default: 10ms).
Returns
Result from the last spin_once call.

◆ spin_once() [1/2]

template<typename T = void>
Result rclcpp::Executor::spin_once ( )
inline

REFUSED — spin_once() with no budget. phase-417 stage 3.

A member template with a defaulted parameter, not = delete: these headers are parsed as C++14, where a deleted function carries no message (= delete("reason") is C++26). The static_assert fires on INSTANTIATION, so the name stays declarable and only USING it fails, with the migration attached.

◆ spin_once() [2/2]

Result rclcpp::Executor::spin_once ( int32_t  timeout_ms)
inline

Drive transport I/O and dispatch callbacks.

Processes pending subscriptions, timers, services, and guard conditions. Call this periodically in your main loop.

The budget is REQUIRED — phase-417 stage 3. Upstream's spin_once(std::chrono::nanoseconds timeout = -1) blocks indefinitely by default; this one cannot, so there is no default to give that would not be a budget the caller never chose. It used to be = 10, and a ported argument-free spin_once() therefore returned after 10 ms where upstream was still waiting — silently. The no-argument form is now a compile error carrying NROS_RCLCPP_REFUSE_UNBOUNDED_SPIN; a negative timeout_ms is refused at the call, for the same reason under the same message.

ADOPT-BOUNDED envelope (RFC-0089 W3.d), and it is the part no signature carries: this dispatches EVERY ready arena entry, where upstream's spin_once executes the next ONE. RFC-0002 §3 computes the ready bitmap once per cycle, so "one item" is not a shape this executor has. A spin_once(0) here is therefore upstream's spin_some. That naming question is open and lives on the cpp:Executor::spin_some ledger row.

Parameters
timeout_msMaximum time to block waiting for I/O. 0 polls; negative is REFUSED (see above).
Returns
Result indicating success or failure.

The documentation for this class was generated from the following files: