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
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
30// phase-427 W7 — `Node` is DEFINED in `rclcpp::` (RFC-0089: that namespace is
31// the home). The friend declaration below is qualified, and a qualified friend
32// names an existing entity rather than introducing one, so the name has to be
33// declared first, in `rclcpp::`.
34namespace rclcpp {
35class Node;
36}
37
38namespace rclcpp {
39
40class Timer;
41
49class TimerOps {
50 public:
53
56
59 bool is_canceled() const {
61 }
62
65 bool is_ready() const {
67 }
68
74 int64_t ns = 0;
75 if (executor_ == nullptr ||
77 return Duration();
78 }
79 return Duration::from_nanoseconds(ns);
80 }
81
82 protected:
83 constexpr TimerOps(void* executor, size_t handle_id)
84 : executor_(executor), handle_id_(handle_id) {}
85
86 void* executor_;
87 size_t handle_id_;
88};
89
115class TimerHandle : private TimerOps {
116 public:
121
122 constexpr TimerHandle() : TimerOps(nullptr, 0) {}
125 constexpr TimerHandle(decltype(nullptr)) : TimerOps(nullptr, 0) {}
126 constexpr TimerHandle(void* executor, size_t handle_id) : TimerOps(executor, handle_id) {}
127
131 explicit constexpr operator bool() const { return executor_ != nullptr; }
132
134 TimerOps* operator->() { return this; }
136 const TimerOps* operator->() const { return this; }
137
141 void reset() {
142 if (executor_ != nullptr) {
144 }
145 executor_ = nullptr;
146 handle_id_ = 0;
147 }
148
150 constexpr size_t handle_id() const { return handle_id_; }
151
152 friend constexpr bool operator==(const TimerHandle& a, const TimerHandle& b) {
153 return a.executor_ == b.executor_ && a.handle_id_ == b.handle_id_;
154 }
155 friend constexpr bool operator!=(const TimerHandle& a, const TimerHandle& b) {
156 return !(a == b);
157 }
158 friend constexpr bool operator==(const TimerHandle& a, decltype(nullptr)) {
159 return a.executor_ == nullptr;
160 }
161 friend constexpr bool operator!=(const TimerHandle& a, decltype(nullptr)) {
162 return a.executor_ != nullptr;
163 }
164 friend constexpr bool operator==(decltype(nullptr), const TimerHandle& a) {
165 return a.executor_ == nullptr;
166 }
167 friend constexpr bool operator!=(decltype(nullptr), const TimerHandle& a) {
168 return a.executor_ != nullptr;
169 }
170};
171
187class Timer {
188 public:
204
208 if (!initialized_) return Result(ErrorCode::NotInitialized);
209 return Result(nros_cpp_timer_cancel(executor_, handle_id_));
210 }
211
215 if (!initialized_) return Result(ErrorCode::NotInitialized);
216 return Result(nros_cpp_timer_reset(executor_, handle_id_));
217 }
218
220 bool is_canceled() const {
221 if (!initialized_) return true;
222 return nros_cpp_timer_is_canceled(executor_, handle_id_);
223 }
224
237 bool is_ready() const {
238 if (!initialized_) return false;
239 return nros_cpp_timer_is_ready(executor_, handle_id_);
240 }
241
266 if (!initialized_) return Duration();
267 int64_t ns = 0;
268 if (nros_cpp_timer_time_until_next_call_ns(executor_, handle_id_, &ns) != NROS_CPP_RET_OK) {
269 return Duration();
270 }
271 return Duration::from_nanoseconds(ns);
272 }
273
275 bool is_valid() const { return initialized_; }
276
279 if (initialized_) {
280 nros_cpp_timer_cancel(executor_, handle_id_);
281 initialized_ = false;
282 }
283 // The closure block (if any) is freed here; the runtime no longer
284 // holds a raw pointer into it because we cancelled above.
285 detail::destroy_hosted_block(closure_);
286 }
287
288 // Move semantics (non-copyable)
290 : executor_(other.executor_), handle_id_(other.handle_id_),
291 initialized_(other.initialized_), closure_(other.closure_) {
292 other.executor_ = nullptr;
293 other.initialized_ = false;
294 other.closure_ = nullptr;
295 }
296
298 if (this != &other) {
299 if (initialized_) {
300 nros_cpp_timer_cancel(executor_, handle_id_);
301 }
302 executor_ = other.executor_;
303 handle_id_ = other.handle_id_;
304 initialized_ = other.initialized_;
305 detail::destroy_hosted_block(closure_);
306 closure_ = other.closure_;
307 other.executor_ = nullptr;
308 other.initialized_ = false;
309 other.closure_ = nullptr;
310 }
311 return *this;
312 }
313
316 Timer() : executor_(nullptr), handle_id_(0), initialized_(false), closure_(nullptr) {}
317
329 void attach_closure_block(detail::HostedBlockBase* block) {
330 detail::destroy_hosted_block(closure_);
331 closure_ = block;
332 }
333
334 private:
335 Timer(const Timer&) = delete;
336 Timer& operator=(const Timer&) = delete;
337
338 friend class ::rclcpp::Node;
339
340 void* executor_;
341 size_t handle_id_;
342 bool initialized_;
343
356 void* closure_;
357};
358
359} // namespace rclcpp
360
361// ============================================================================
362// rclcpp::Timer — FLAT. No `TimerBase`, no hierarchy (phase-430 W7 ruling)
363// ============================================================================
364//
365// RFC-0089 §"Timer, studied against RTOS semantics" decided this and the tree
366// disagreed with it: phase-417 W1.a had landed `class TimerBase` with a virtual
367// destructor and `detail::WallTimer : TimerBase` under it — exactly the
368// hierarchy the RFC refused. phase-430 W7 called for a ruling either way. The
369// ruling is DELETE, and it is argued from the executor's dispatch:
370//
371// 1. THE VTABLE HAS NO CALLER. The only virtual member was `~TimerBase()`,
372// and there is no virtual call through a `TimerBase*` anywhere in the
373// tree — there cannot be. The executor's callback slot is
374// `nros_cpp_timer_callback_t`, a raw `void(*)(void*)`, and dispatch goes
375// through the STATIC `detail::WallTimer::trampoline`. The executor never
376// holds a C++ timer object, so it never needs a type-erased base. Even
377// the destructor's virtuality was dead: the cell is built with
378// `std::make_shared<detail::WallTimer>()`, so the control block already
379// records the CONCRETE deleter and destruction through a base pointer was
380// correct without it. A vtable no dispatch uses is cost with no caller,
381// which is what clause 1 of the governing principle refuses.
382//
383// 2. THE NAME PROMISES CHILDREN WE REFUSE TO HAVE. `WallTimer` and
384// `GenericTimer` are upstream's siblings under `TimerBase`, and both stay
385// absent by decision: the clock axis is a RUNTIME FIELD on the flat timer
386// plus a second VERB (`create_timer(clock, …)` beside `create_wall_timer`),
387// never a type parameter — phase-425 landed ROS time in exactly that
388// shape. A base whose one leaf is `detail::`-private advertises a taxonomy
389// the header itself refuses two paragraphs later.
390//
391// 3. THE FLAT SHAPE IS A STRICTLY BETTER KEEP-ALIVE. `create_wall_timer`
392// returned `std::shared_ptr<::rclcpp::Timer>` aliased onto a private cell,
393// as `create_subscription` did. Since phase-476 W2 both return a two-word
394// handle and there is no cell: the callable is in the executor arena.
395//
396// The migration cost is one mechanical rename the compiler demands:
397// `rclcpp::TimerBase::SharedPtr timer_;` becomes `rclcpp::Timer::SharedPtr
398// timer_;`. That is the compile-or-conform rule working as designed, and it is
399// what the RFC calls the honest outcome — we do not have the hierarchy, so we
400// do not take the name for it. Zero non-test call sites in this tree used it.
401//
402// `rclcpp::Timer` is an ours-only name in upstream's namespace (RFC-0089
403// §"Settled: `nros::` is phased out entirely"), so it carries a ledger row with
404// `disposition: extension` and the collision gate watches for `rclcpp::Timer`
405// appearing in the recorded upstream surface.
406//
407// ADOPT-BOUNDED, and both halves of the envelope come with the executor:
408//
409// * PERIOD RESOLUTION IS ONE MILLISECOND. `nros_cpp_timer_create` takes a
410// `uint64_t period_ms`, so `create_wall_timer(500us, …)` truncates to 0 and
411// fires every spin. Activations land on spin boundaries either way, so the
412// achievable cadence was already the spin period — the truncation only
413// makes the floor explicit.
414// * MISSED DEADLINES CATCH UP, where rcl's DROP. `TimerState::fire` keeps the
415// overshoot (`elapsed_ms -= period_ms`, `nros-node/src/timer.rs:298`), so
416// after a stall the callback runs once per `spin_once` until the backlog is
417// drained and the mean cadence is preserved. `rcl_timer_call` instead skips
418// whole missed periods and re-phases onto the grid, firing once. Closing
419// the gap means a missed-deadline POLICY on the executor's timer —
420// Rust-side work, not a loop re-added here (issue 1041).
421//
422// phase-417 — ONE DISPATCH PATH. `create_wall_timer` registers an EXECUTOR
423// timer via `rclcpp::Node::create_wall_timer` (`node.hpp`), so the period
424// arithmetic, the missed-deadline policy and the clock are the executor's,
425// Rust-side. Until this landed, `WallTimer` carried its own
426// `std::chrono::steady_clock` deadline and a `Node::pump()` fired it from
427// `rclcpp::spin` / `spin_some` ONLY. Scheduling in the wrapper is
428// RFC-0019/RFC-0020 violation class 2. Do not reintroduce a second dispatch
429// loop.
430
431namespace rclcpp {
432
469using TimerBase = ::rclcpp::Timer;
470
471// phase-476 W2 deleted `detail::WallTimer`, the heap cell (a `std::function`
472// plus an `rclcpp::Timer`) that gave a capturing timer callback a stable address
473// and that the node kept alive through `owned_entities`. The callable now lives
474// in the executor arena (`nros_cpp_timer_create_capturing`), destroyed when the
475// timer is released, and the returned handle is two words.
476
477} // namespace rclcpp
478
479#endif // NROS_RCLCPP_TIMER_HPP
Definition client.hpp:129
Definition result.hpp:216
Definition timer.hpp:115
friend constexpr bool operator!=(decltype(nullptr), const TimerHandle &a)
Definition timer.hpp:167
constexpr TimerHandle(decltype(nullptr))
Definition timer.hpp:125
friend constexpr bool operator==(decltype(nullptr), const TimerHandle &a)
Definition timer.hpp:164
friend constexpr bool operator==(const TimerHandle &a, decltype(nullptr))
Definition timer.hpp:158
constexpr TimerHandle(void *executor, size_t handle_id)
Definition timer.hpp:126
constexpr TimerHandle()
Definition timer.hpp:122
friend constexpr bool operator!=(const TimerHandle &a, const TimerHandle &b)
Definition timer.hpp:155
friend constexpr bool operator==(const TimerHandle &a, const TimerHandle &b)
Definition timer.hpp:152
constexpr size_t handle_id() const
The packed executor handle this refers to. For introspection and tests.
Definition timer.hpp:150
TimerOps * operator->()
timer_->cancel() and friends.
Definition timer.hpp:134
void reset()
Definition timer.hpp:141
friend constexpr bool operator!=(const TimerHandle &a, decltype(nullptr))
Definition timer.hpp:161
const TimerOps * operator->() const
Const overload of operator->.
Definition timer.hpp:136
Definition timer.hpp:49
Duration time_until_trigger() const
Definition timer.hpp:73
constexpr TimerOps(void *executor, size_t handle_id)
Definition timer.hpp:83
Result cancel()
Stop the timer firing. It stays registered; reset() restarts it.
Definition timer.hpp:52
bool is_ready() const
Definition timer.hpp:65
bool is_canceled() const
Definition timer.hpp:59
void * executor_
Definition timer.hpp:86
Result reset()
Restart the timer from zero elapsed time; un-cancels a cancelled one.
Definition timer.hpp:55
size_t handle_id_
Definition timer.hpp:87
Definition timer.hpp:187
void attach_closure_block(detail::HostedBlockBase *block)
Definition timer.hpp:329
Timer()
Definition timer.hpp:316
Result reset()
Definition timer.hpp:214
Result cancel()
Definition timer.hpp:207
bool is_ready() const
Definition timer.hpp:237
bool is_valid() const
Check if the timer is initialized and valid.
Definition timer.hpp:275
Duration time_until_trigger() const
Definition timer.hpp:265
Timer & operator=(Timer &&other)
Definition timer.hpp:297
Timer(Timer &&other)
Definition timer.hpp:289
~Timer()
Destructor — cancels the timer.
Definition timer.hpp:278
bool is_canceled() const
Check if the timer is cancelled.
Definition timer.hpp:220
Definition rclcpp.hpp:75
ResultOf< void > Result
Definition result.hpp:246
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.