nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
timer.hpp
Go to the documentation of this file.
1// nros-cpp: Timer class
2// Freestanding C++ — no exceptions, no STL required
3
9
10#ifndef NROS_RCLCPP_TIMER_HPP
11#define NROS_RCLCPP_TIMER_HPP
12
13#include <cstdint>
14#include <cstddef>
15
16#include "rclcpp/result.hpp"
17#include "rclcpp/detail/hosted_block.hpp"
18// phase-417 G6 — `time_until_trigger()` returns `rclcpp::Duration`, so this
19// header NAMES it and must be includable first (the rule `qos.hpp` states for
20// the same dependency). `duration.hpp` includes nothing of ours.
21#include "rclcpp/duration.hpp"
22
23// phase-476 W2 — nothing in this header is hosted-only any more: the pointer
24// aliases name `TimerHandle` rather than `std::shared_ptr`, and the callable
25// lives in the executor arena rather than a `std::function` cell. So it
26// includes no capability probe.
27
28#include "rclcpp/detail/nros_cpp_ffi.h"
29// issue 1975 -- `get_clock()` returns the clock the timer runs on. AFTER
30// `nros_cpp_ffi.h`, the order `node.hpp` already includes the two in.
31#include "rclcpp/clock.hpp"
32// issue 1974 -- `TimerInfoCallbackType` is an `InplaceFn`.
33#include "rclcpp/inplace_fn.hpp"
34
35// phase-427 W7 — `Node` is DEFINED in `rclcpp::` (RFC-0089: that namespace is
36// the home). The friend declaration below is qualified, and a qualified friend
37// names an existing entity rather than introducing one, so the name has to be
38// declared first, in `rclcpp::`.
39namespace rclcpp {
40class Node;
41}
42
43namespace rclcpp {
44
45class Timer;
46
47namespace detail {
52inline ClockValuePtr timer_clock(void* executor, size_t handle_id) {
53 uint8_t clock_type = static_cast<uint8_t>(RCL_CLOCK_UNINITIALIZED);
54 if (executor == nullptr ||
55 nros_cpp_timer_get_clock_type(executor, handle_id, &clock_type) != NROS_CPP_RET_OK) {
56 clock_type = static_cast<uint8_t>(RCL_CLOCK_UNINITIALIZED);
57 }
58 return ClockValuePtr(static_cast<rcl_clock_type_t>(clock_type));
59}
60} // namespace detail
61
69class TimerOps {
70 public:
73#if defined(NROS_CPP_STD) // hosted-family: ported-publish
74 ::rclcpp::detail::StatementResult cancel() {
75#else
77#endif
78 return Result(nros_cpp_timer_cancel(executor_, handle_id_));
79 }
80
82#if defined(NROS_CPP_STD) // hosted-family: ported-publish
83 ::rclcpp::detail::StatementResult reset() {
84#else
86#endif
87 return Result(nros_cpp_timer_reset(executor_, handle_id_));
88 }
89
92 bool is_canceled() const {
93 return executor_ == nullptr || nros_cpp_timer_is_canceled(executor_, handle_id_);
94 }
95
98 bool is_ready() const {
99 return executor_ != nullptr && nros_cpp_timer_is_ready(executor_, handle_id_);
100 }
101
106 Duration time_until_trigger() const {
107 int64_t ns = 0;
108 if (executor_ == nullptr ||
109 nros_cpp_timer_time_until_next_call_ns(executor_, handle_id_, &ns) != NROS_CPP_RET_OK) {
110 return Duration();
111 }
112 return Duration::from_nanoseconds(ns);
113 }
114
118 detail::ClockValuePtr get_clock() const { return detail::timer_clock(executor_, handle_id_); }
119
120 protected:
121 constexpr TimerOps(void* executor, size_t handle_id)
122 : executor_(executor), handle_id_(handle_id) {}
123
126};
127
153class TimerHandle : private TimerOps {
154 public:
158 using element_type = ::rclcpp::Timer;
159
160 constexpr TimerHandle() : TimerOps(nullptr, 0) {}
163 constexpr TimerHandle(decltype(nullptr)) : TimerOps(nullptr, 0) {}
164 constexpr TimerHandle(void* executor, size_t handle_id) : TimerOps(executor, handle_id) {}
165
169 explicit constexpr operator bool() const { return executor_ != nullptr; }
170
172 TimerOps* operator->() { return this; }
174 const TimerOps* operator->() const { return this; }
175
179 void reset() {
180 if (executor_ != nullptr) {
181 (void)nros_cpp_timer_release(executor_, handle_id_);
182 }
183 executor_ = nullptr;
184 handle_id_ = 0;
185 }
186
188 constexpr size_t handle_id() const { return handle_id_; }
189
190 friend constexpr bool operator==(const TimerHandle& a, const TimerHandle& b) {
191 return a.executor_ == b.executor_ && a.handle_id_ == b.handle_id_;
192 }
193 friend constexpr bool operator!=(const TimerHandle& a, const TimerHandle& b) {
194 return !(a == b);
195 }
196 friend constexpr bool operator==(const TimerHandle& a, decltype(nullptr)) {
197 return a.executor_ == nullptr;
198 }
199 friend constexpr bool operator!=(const TimerHandle& a, decltype(nullptr)) {
200 return a.executor_ != nullptr;
201 }
202 friend constexpr bool operator==(decltype(nullptr), const TimerHandle& a) {
203 return a.executor_ == nullptr;
204 }
205 friend constexpr bool operator!=(decltype(nullptr), const TimerHandle& a) {
206 return a.executor_ != nullptr;
207 }
208};
209
225class Timer {
226 public:
242
246#if defined(NROS_CPP_STD) // hosted-family: ported-publish
247 ::rclcpp::detail::StatementResult cancel() {
248#else
249 Result cancel() {
250#endif
251 if (!initialized_) return Result(ErrorCode::NotInitialized);
252 return Result(nros_cpp_timer_cancel(executor_, handle_id_));
253 }
254
257#if defined(NROS_CPP_STD) // hosted-family: ported-publish
258 ::rclcpp::detail::StatementResult reset() {
259#else
260 Result reset() {
261#endif
262 if (!initialized_) return Result(ErrorCode::NotInitialized);
263 return Result(nros_cpp_timer_reset(executor_, handle_id_));
264 }
265
267 bool is_canceled() const {
268 if (!initialized_) return true;
269 return nros_cpp_timer_is_canceled(executor_, handle_id_);
270 }
271
284 bool is_ready() const {
285 if (!initialized_) return false;
286 return nros_cpp_timer_is_ready(executor_, handle_id_);
287 }
288
312 Duration time_until_trigger() const {
313 if (!initialized_) return Duration();
314 int64_t ns = 0;
315 if (nros_cpp_timer_time_until_next_call_ns(executor_, handle_id_, &ns) != NROS_CPP_RET_OK) {
316 return Duration();
317 }
318 return Duration::from_nanoseconds(ns);
319 }
320
336 detail::ClockValuePtr get_clock() const {
337 return detail::timer_clock(initialized_ ? executor_ : nullptr, handle_id_);
338 }
339
341 bool is_valid() const { return initialized_; }
342
345 if (initialized_) {
346 nros_cpp_timer_cancel(executor_, handle_id_);
347 initialized_ = false;
348 }
349 // The closure block (if any) is freed here; the runtime no longer
350 // holds a raw pointer into it because we cancelled above.
351 detail::destroy_hosted_block(closure_);
352 }
353
354 // Move semantics (non-copyable)
355 Timer(Timer&& other)
356 : executor_(other.executor_), handle_id_(other.handle_id_),
357 initialized_(other.initialized_), closure_(other.closure_) {
358 other.executor_ = nullptr;
359 other.initialized_ = false;
360 other.closure_ = nullptr;
361 }
362
364 if (this != &other) {
365 if (initialized_) {
366 nros_cpp_timer_cancel(executor_, handle_id_);
367 }
368 executor_ = other.executor_;
369 handle_id_ = other.handle_id_;
370 initialized_ = other.initialized_;
371 detail::destroy_hosted_block(closure_);
372 closure_ = other.closure_;
373 other.executor_ = nullptr;
374 other.initialized_ = false;
375 other.closure_ = nullptr;
376 }
377 return *this;
378 }
379
382 Timer() : executor_(nullptr), handle_id_(0), initialized_(false), closure_(nullptr) {}
383
395 void attach_closure_block(detail::HostedBlockBase* block) {
396 detail::destroy_hosted_block(closure_);
397 closure_ = block;
398 }
399
400 private:
401 Timer(const Timer&) = delete;
402 Timer& operator=(const Timer&) = delete;
403
404 friend class ::rclcpp::Node;
405
406 void* executor_;
407 size_t handle_id_;
408 bool initialized_;
409
422 void* closure_;
423};
424
425} // namespace rclcpp
426
427// ============================================================================
428// rclcpp::Timer — FLAT. No `TimerBase`, no hierarchy (phase-430 W7 ruling)
429// ============================================================================
430//
431// RFC-0089 §"Timer, studied against RTOS semantics" decided this and the tree
432// disagreed with it: phase-417 W1.a had landed `class TimerBase` with a virtual
433// destructor and `detail::WallTimer : TimerBase` under it — exactly the
434// hierarchy the RFC refused. phase-430 W7 called for a ruling either way. The
435// ruling is DELETE, and it is argued from the executor's dispatch:
436//
437// 1. THE VTABLE HAS NO CALLER. The only virtual member was `~TimerBase()`,
438// and there is no virtual call through a `TimerBase*` anywhere in the
439// tree — there cannot be. The executor's callback slot is
440// `nros_cpp_timer_callback_t`, a raw `void(*)(void*)`, and dispatch goes
441// through the STATIC `detail::WallTimer::trampoline`. The executor never
442// holds a C++ timer object, so it never needs a type-erased base. Even
443// the destructor's virtuality was dead: the cell is built with
444// `std::make_shared<detail::WallTimer>()`, so the control block already
445// records the CONCRETE deleter and destruction through a base pointer was
446// correct without it. A vtable no dispatch uses is cost with no caller,
447// which is what clause 1 of the governing principle refuses.
448//
449// 2. THE NAME PROMISES CHILDREN WE REFUSE TO HAVE. `WallTimer` and
450// `GenericTimer` are upstream's siblings under `TimerBase`, and both stay
451// absent by decision: the clock axis is a RUNTIME FIELD on the flat timer
452// plus a second VERB (`create_timer(clock, …)` beside `create_wall_timer`),
453// never a type parameter — phase-425 landed ROS time in exactly that
454// shape. A base whose one leaf is `detail::`-private advertises a taxonomy
455// the header itself refuses two paragraphs later.
456//
457// 3. THE FLAT SHAPE IS A STRICTLY BETTER KEEP-ALIVE. `create_wall_timer`
458// returned `std::shared_ptr<::rclcpp::Timer>` aliased onto a private cell,
459// as `create_subscription` did. Since phase-476 W2 both return a two-word
460// handle and there is no cell: the callable is in the executor arena.
461//
462// The migration cost is one mechanical rename the compiler demands:
463// `rclcpp::TimerBase::SharedPtr timer_;` becomes `rclcpp::Timer::SharedPtr
464// timer_;`. That is the compile-or-conform rule working as designed, and it is
465// what the RFC calls the honest outcome — we do not have the hierarchy, so we
466// do not take the name for it. Zero non-test call sites in this tree used it.
467//
468// `rclcpp::Timer` is an ours-only name in upstream's namespace (RFC-0089
469// §"Settled: `nros::` is phased out entirely"), so it carries a ledger row with
470// `disposition: extension` and the collision gate watches for `rclcpp::Timer`
471// appearing in the recorded upstream surface.
472//
473// ADOPT-BOUNDED, and both halves of the envelope come with the executor:
474//
475// * PERIOD RESOLUTION IS ONE MILLISECOND. `nros_cpp_timer_create` takes a
476// `uint64_t period_ms`, so `create_wall_timer(500us, …)` truncates to 0 and
477// fires every spin. Activations land on spin boundaries either way, so the
478// achievable cadence was already the spin period — the truncation only
479// makes the floor explicit.
480// * MISSED DEADLINES CATCH UP, where rcl's DROP. `TimerState::fire` keeps the
481// overshoot (`elapsed_ms -= period_ms`, `nros-node/src/timer.rs:298`), so
482// after a stall the callback runs once per `spin_once` until the backlog is
483// drained and the mean cadence is preserved. `rcl_timer_call` instead skips
484// whole missed periods and re-phases onto the grid, firing once. Closing
485// the gap means a missed-deadline POLICY on the executor's timer —
486// Rust-side work, not a loop re-added here (issue 1041).
487//
488// phase-417 — ONE DISPATCH PATH. `create_wall_timer` registers an EXECUTOR
489// timer via `rclcpp::Node::create_wall_timer` (`node.hpp`), so the period
490// arithmetic, the missed-deadline policy and the clock are the executor's,
491// Rust-side. Until this landed, `WallTimer` carried its own
492// `std::chrono::steady_clock` deadline and a `Node::pump()` fired it from
493// `rclcpp::spin` / `spin_some` ONLY. Scheduling in the wrapper is
494// RFC-0019/RFC-0020 violation class 2. Do not reintroduce a second dispatch
495// loop.
496
497namespace rclcpp {
498
535using TimerBase = ::rclcpp::Timer;
536
555
566using TimerInfoCallbackType = InplaceFn<void(const TimerInfo&)>;
567
568// phase-476 W2 deleted `detail::WallTimer`, the heap cell (a `std::function`
569// plus an `rclcpp::Timer`) that gave a capturing timer callback a stable address
570// and that the node kept alive through ~~`owned_entities`~~. The callable now lives
571// in the executor arena (`nros_cpp_timer_create_capturing`), destroyed when the
572// timer is released, and the returned handle is two words.
573
574} // namespace rclcpp
575
576#endif // NROS_RCLCPP_TIMER_HPP
Definition timer.hpp:225
Timer & operator=(Timer &&other)
Definition timer.hpp:363
Timer(Timer &&other)
Definition timer.hpp:355
~Timer()
Destructor — cancels the timer.
Definition timer.hpp:344
Timer()
Definition timer.hpp:382
bool is_canceled() const
Check if the timer is cancelled.
Definition timer.hpp:267
Result reset()
Definition timer.hpp:260
::rclcpp::TimerHandle SharedPtr
Definition timer.hpp:236
bool is_ready() const
Definition timer.hpp:284
Duration time_until_trigger() const
Definition timer.hpp:312
void attach_closure_block(detail::HostedBlockBase *block)
Definition timer.hpp:395
Result cancel()
Definition timer.hpp:249
bool is_valid() const
Check if the timer is initialized and valid.
Definition timer.hpp:341
::rclcpp::TimerHandle UniquePtr
Definition timer.hpp:241
detail::ClockValuePtr get_clock() const
Definition timer.hpp:336
::rclcpp::TimerHandle ConstSharedPtr
Timer::ConstSharedPtr — the same handle; see SharedPtr.
Definition timer.hpp:238
Definition node.hpp:845
Definition timer.hpp:153
friend constexpr bool operator!=(decltype(nullptr), const TimerHandle &a)
Definition timer.hpp:205
constexpr TimerHandle(decltype(nullptr))
Definition timer.hpp:163
friend constexpr bool operator==(decltype(nullptr), const TimerHandle &a)
Definition timer.hpp:202
friend constexpr bool operator==(const TimerHandle &a, decltype(nullptr))
Definition timer.hpp:196
::rclcpp::Timer element_type
Definition timer.hpp:158
constexpr TimerHandle(void *executor, size_t handle_id)
Definition timer.hpp:164
constexpr TimerHandle()
Definition timer.hpp:160
friend constexpr bool operator!=(const TimerHandle &a, const TimerHandle &b)
Definition timer.hpp:193
friend constexpr bool operator==(const TimerHandle &a, const TimerHandle &b)
Definition timer.hpp:190
constexpr size_t handle_id() const
The packed executor handle this refers to. For introspection and tests.
Definition timer.hpp:188
TimerOps * operator->()
timer_->cancel() and friends.
Definition timer.hpp:172
void reset()
Definition timer.hpp:179
friend constexpr bool operator!=(const TimerHandle &a, decltype(nullptr))
Definition timer.hpp:199
const TimerOps * operator->() const
Const overload of operator->.
Definition timer.hpp:174
Duration time_until_trigger() const
Definition timer.hpp:106
constexpr TimerOps(void *executor, size_t handle_id)
Definition timer.hpp:121
Result cancel()
Definition timer.hpp:76
bool is_ready() const
Definition timer.hpp:98
bool is_canceled() const
Definition timer.hpp:92
void * executor_
Definition timer.hpp:124
Result reset()
Restart the timer from zero elapsed time; un-cancels a cancelled one.
Definition timer.hpp:85
size_t handle_id_
Definition timer.hpp:125
detail::ClockValuePtr get_clock() const
Definition timer.hpp:118
Definition rclcpp.hpp:418
ClockValuePtr timer_clock(void *executor, size_t handle_id)
Definition timer.hpp:52
Definition rclcpp.hpp:75
InplaceFn< void(const TimerInfo &)> TimerInfoCallbackType
Definition timer.hpp:566
::rclcpp::Timer TimerBase
Definition timer.hpp:535
ResultOf< void > Result
Definition result.hpp:247
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
Definition timer.hpp:551
Time expected_call_time
Definition timer.hpp:552
Time actual_call_time
Definition timer.hpp:553
*timer reset()
**timer cancel()