diff --git a/examples/timer_demo/Cargo.toml b/examples/timer_demo/Cargo.toml
new file mode 100644
index 00000000..d3bfd158
--- /dev/null
+++ b/examples/timer_demo/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "examples_timer_demo"
+version = "0.1.0"
+edition = "2021"
+
+[[bin]]
+name = "timer_demo"
+path = "src/main.rs"
+
+[dependencies]
+rclrs = "0.4"
+example_interfaces = "*"
diff --git a/examples/timer_demo/package.xml b/examples/timer_demo/package.xml
new file mode 100644
index 00000000..684cea8f
--- /dev/null
+++ b/examples/timer_demo/package.xml
@@ -0,0 +1,21 @@
+
+
+
+ examples_timer_demo
+ Esteve Fernandez
+
+ Jacob Hassold
+ 0.4.1
+ Package containing an example of how to use a worker in rclrs.
+ Apache License 2.0
+
+ rclrs
+ rosidl_runtime_rs
+ example_interfaces
+
+
+ ament_cargo
+
+
diff --git a/examples/timer_demo/src/main.rs b/examples/timer_demo/src/main.rs
new file mode 100644
index 00000000..608a82f1
--- /dev/null
+++ b/examples/timer_demo/src/main.rs
@@ -0,0 +1,22 @@
+/// Creates a SimpleTimerNode, initializes a node and the timer with a callback
+/// that prints the timer callback execution iteration. The callback is executed
+/// thanks to the spin, which is in charge of executing the timer's events among
+/// other entities' events.
+use rclrs::*;
+use std::time::Duration;
+
+fn main() -> Result<(), RclrsError> {
+ let mut executor = Context::default_from_env()?.create_basic_executor();
+ let node = executor.create_node("timer_demo")?;
+ let worker = node.create_worker::(0);
+ let timer_period = Duration::from_secs(1);
+ let _timer = worker.create_timer_repeating(timer_period, move |count: &mut usize| {
+ *count += 1;
+ println!(
+ "Drinking 🧉 for the {}th time every {:?}.",
+ *count, timer_period,
+ );
+ })?;
+
+ executor.spin(SpinOptions::default()).first_error()
+}
diff --git a/examples/worker_demo/src/main.rs b/examples/worker_demo/src/main.rs
index 253a95fc..bb3a9761 100644
--- a/examples/worker_demo/src/main.rs
+++ b/examples/worker_demo/src/main.rs
@@ -1,5 +1,5 @@
use rclrs::*;
-use std::sync::Arc;
+use std::time::Duration;
fn main() -> Result<(), RclrsError> {
let mut executor = Context::default_from_env()?.create_basic_executor();
@@ -15,27 +15,12 @@ fn main() -> Result<(), RclrsError> {
},
)?;
- // // Use this timer-based implementation when timers are available instead
- // // of using std::thread::spawn.
- // let _timer = worker.create_timer_repeating(
- // Duration::from_secs(1),
- // move |data: &mut String| {
- // let msg = example_interfaces::msg::String {
- // data: data.clone()
- // };
-
- // publisher.publish(msg).ok();
- // }
- // )?;
-
- std::thread::spawn(move || loop {
- std::thread::sleep(std::time::Duration::from_secs(1));
- let publisher = Arc::clone(&publisher);
- let _ = worker.run(move |data: &mut String| {
+ let _timer =
+ worker.create_timer_repeating(Duration::from_secs(1), move |data: &mut String| {
let msg = example_interfaces::msg::String { data: data.clone() };
- publisher.publish(msg).unwrap();
- });
- });
+
+ publisher.publish(msg).ok();
+ })?;
println!(
"Beginning repeater... \n >> \
diff --git a/rclrs/src/clock.rs b/rclrs/src/clock.rs
index 992cd4b4..8dd54345 100644
--- a/rclrs/src/clock.rs
+++ b/rclrs/src/clock.rs
@@ -88,6 +88,11 @@ impl Clock {
Self { kind, rcl_clock }
}
+ /// Returns the clock's `rcl_clock_t`.
+ pub(crate) fn get_rcl_clock(&self) -> &Arc> {
+ &self.rcl_clock
+ }
+
/// Returns the clock's `ClockType`.
pub fn clock_type(&self) -> ClockType {
self.kind
diff --git a/rclrs/src/lib.rs b/rclrs/src/lib.rs
index 366e499b..f5584fa2 100644
--- a/rclrs/src/lib.rs
+++ b/rclrs/src/lib.rs
@@ -189,6 +189,7 @@ mod service;
mod subscription;
mod time;
mod time_source;
+mod timer;
mod vendor;
mod wait_set;
mod worker;
@@ -217,5 +218,6 @@ pub use service::*;
pub use subscription::*;
pub use time::*;
use time_source::*;
+pub use timer::*;
pub use wait_set::*;
pub use worker::*;
diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs
index dd01d060..b6b32162 100644
--- a/rclrs/src/node.rs
+++ b/rclrs/src/node.rs
@@ -29,12 +29,14 @@ use async_std::future::timeout;
use rosidl_runtime_rs::Message;
use crate::{
- rcl_bindings::*, Client, ClientOptions, ClientState, Clock, ContextHandle, ExecutorCommands,
- IntoAsyncServiceCallback, IntoAsyncSubscriptionCallback, IntoNodeServiceCallback,
- IntoNodeSubscriptionCallback, LogParams, Logger, ParameterBuilder, ParameterInterface,
- ParameterVariant, Parameters, Promise, Publisher, PublisherOptions, PublisherState, RclrsError,
- Service, ServiceOptions, ServiceState, Subscription, SubscriptionOptions, SubscriptionState,
- TimeSource, ToLogParams, Worker, WorkerOptions, WorkerState, ENTITY_LIFECYCLE_MUTEX,
+ rcl_bindings::*, AnyTimerCallback, Client, ClientOptions, ClientState, Clock, ContextHandle,
+ ExecutorCommands, IntoAsyncServiceCallback, IntoAsyncSubscriptionCallback,
+ IntoNodeServiceCallback, IntoNodeSubscriptionCallback, IntoNodeTimerOneshotCallback,
+ IntoNodeTimerRepeatingCallback, IntoTimerOptions, LogParams, Logger, ParameterBuilder,
+ ParameterInterface, ParameterVariant, Parameters, Promise, Publisher, PublisherOptions,
+ PublisherState, RclrsError, Service, ServiceOptions, ServiceState, Subscription,
+ SubscriptionOptions, SubscriptionState, TimeSource, Timer, TimerState, ToLogParams, Worker,
+ WorkerOptions, WorkerState, ENTITY_LIFECYCLE_MUTEX,
};
/// A processing unit that can communicate with other nodes. See the API of
@@ -893,6 +895,216 @@ impl NodeState {
)
}
+ /// Create a [`Timer`] with a repeating callback.
+ ///
+ /// See also:
+ /// * [`Self::create_timer_oneshot`]
+ /// * [`Self::create_timer_inert`]
+ ///
+ /// # Behavior
+ ///
+ /// While the callback of this timer is running, no other callbacks associated
+ /// with this node will be able to run. This is in contrast to callbacks given
+ /// to [`Self::create_subscription`] which can run multiple times in parallel.
+ ///
+ /// Since the callback of this timer may block other callbacks from being able
+ /// to run, it is strongly recommended to ensure that the callback returns
+ /// quickly. If the callback needs to trigger long-running behavior then you
+ /// can consider using [`std::thread::spawn`], or for async behaviors you can
+ /// capture an [`ExecutorCommands`] in your callback and use [`ExecutorCommands::run`]
+ /// to issue a task for the executor to run in its async task pool.
+ ///
+ /// Since these callbacks are blocking, you may use [`FnMut`] here instead of
+ /// being limited to [`Fn`].
+ ///
+ /// # Timer Options
+ ///
+ /// You can choose both
+ /// 1. a timer period (duration) which determines how often the callback is triggered
+ /// 2. a clock to measure the passage of time
+ ///
+ /// Both of these choices are expressed by [`TimerOptions`][1].
+ ///
+ /// By default the steady clock time will be used, but you could choose
+ /// node time instead if you want the timer to automatically use simulated
+ /// time when running as part of a simulation:
+ /// ```
+ /// # use rclrs::*;
+ /// # let executor = Context::default().create_basic_executor();
+ /// # let node = executor.create_node("my_node").unwrap();
+ /// use std::time::Duration;
+ ///
+ /// let timer = node.create_timer_repeating(
+ /// TimerOptions::new(Duration::from_secs(1))
+ /// .node_time(),
+ /// || {
+ /// println!("Triggering once each simulated second");
+ /// },
+ /// )?;
+ /// # Ok::<(), RclrsError>(())
+ /// ```
+ ///
+ /// If there is a specific manually-driven clock you want to use, you can
+ /// also select that:
+ /// ```
+ /// # use rclrs::*;
+ /// # let executor = Context::default().create_basic_executor();
+ /// # let node = executor.create_node("my_node").unwrap();
+ /// use std::time::Duration;
+ ///
+ /// let (my_clock, my_source) = Clock::with_source();
+ ///
+ /// let timer = node.create_timer_repeating(
+ /// TimerOptions::new(Duration::from_secs(1))
+ /// .clock(&my_clock),
+ /// || {
+ /// println!("Triggering once each simulated second");
+ /// },
+ /// )?;
+ ///
+ /// my_source.set_ros_time_override(1_500_000_000);
+ /// # Ok::<(), RclrsError>(())
+ /// ```
+ ///
+ /// If you are okay with the default choice of clock (steady clock) then you
+ /// can choose to simply pass a duration in as the options:
+ /// ```
+ /// # use rclrs::*;
+ /// # let executor = Context::default().create_basic_executor();
+ /// # let node = executor.create_node("my_node").unwrap();
+ /// use std::time::Duration;
+ ///
+ /// let timer = node.create_timer_repeating(
+ /// Duration::from_secs(1),
+ /// || {
+ /// println!("Triggering per steady clock second");
+ /// },
+ /// )?;
+ /// # Ok::<(), RclrsError>(())
+ /// ```
+ ///
+ /// # Node Timer Repeating Callbacks
+ ///
+ /// Node Timer repeating callbacks support three signatures:
+ /// - [FnMut] ()
+ /// - [FnMut] ([Time][2])
+ /// - [FnMut] (&[Timer])
+ ///
+ /// You can choose to receive the current time when the callback is being
+ /// triggered.
+ ///
+ /// Or instead of the current time, you can get a borrow of the [`Timer`]
+ /// itself, that way if you need to access it from inside the callback, you
+ /// do not need to worry about capturing a [`Weak`][3] and then locking it.
+ /// This is useful if you need to change the callback of the timer from inside
+ /// the callback of the timer.
+ ///
+ /// For an [`FnOnce`] instead of [`FnMut`], use [`Self::create_timer_oneshot`].
+ ///
+ /// [1]: crate::TimerOptions
+ /// [2]: crate::Time
+ /// [3]: std::sync::Weak
+ pub fn create_timer_repeating<'a, Args>(
+ self: &Arc,
+ options: impl IntoTimerOptions<'a>,
+ callback: impl IntoNodeTimerRepeatingCallback,
+ ) -> Result {
+ self.create_timer(options, callback.into_node_timer_repeating_callback())
+ }
+
+ /// Create a [`Timer`] whose callback will be triggered once after the period
+ /// of the timer has elapsed. After that you will need to use
+ /// [`TimerState::set_repeating`] or [`TimerState::set_oneshot`] or else
+ /// nothing will happen the following times that the `Timer` elapses.
+ ///
+ /// See also:
+ /// * [`Self::create_timer_repeating`]
+ /// * [`Self::create_timer_inert`]
+ ///
+ /// # Behavior
+ ///
+ /// While the callback of this timer is running, no other callbacks associated
+ /// with this node will be able to run. This is in contrast to callbacks given
+ /// to [`Self::create_subscription`] which can run multiple times in parallel.
+ ///
+ /// Since the callback of this timer may block other callbacks from being able
+ /// to run, it is strongly recommended to ensure that the callback returns
+ /// quickly. If the callback needs to trigger long-running behavior then you
+ /// can consider using [`std::thread::spawn`], or for async behaviors you can
+ /// capture an [`ExecutorCommands`] in your callback and use [`ExecutorCommands::run`]
+ /// to issue a task for the executor to run in its async task pool.
+ ///
+ /// Since these callbacks will only be triggered once, you may use [`FnOnce`] here.
+ ///
+ /// # Timer Options
+ ///
+ /// See [`NodeSate::create_timer_repeating`][3] for examples of setting the
+ /// timer options.
+ ///
+ /// # Node Timer Oneshot Callbacks
+ ///
+ /// Node Timer repeating callbacks support three signatures:
+ /// - [FnMut] ()
+ /// - [FnMut] ([Time][2])
+ /// - [FnMut] (&[Timer])
+ ///
+ /// You can choose to receive the current time when the callback is being
+ /// triggered.
+ ///
+ /// Or instead of the current time, you can get a borrow of the [`Timer`]
+ /// itself, that way if you need to access it from inside the callback, you
+ /// do not need to worry about capturing a [`Weak`][3] and then locking it.
+ /// This is useful if you need to change the callback of the timer from inside
+ /// the callback of the timer.
+ ///
+ /// [2]: crate::Time
+ /// [3]: std::sync::Weak
+ pub fn create_timer_oneshot<'a, Args>(
+ self: &Arc,
+ options: impl IntoTimerOptions<'a>,
+ callback: impl IntoNodeTimerOneshotCallback,
+ ) -> Result {
+ self.create_timer(options, callback.into_node_timer_oneshot_callback())
+ }
+
+ /// Create a [`Timer`] without a callback. Nothing will happen when this
+ /// `Timer` elapses until you use [`TimerState::set_repeating`] or
+ /// [`TimerState::set_oneshot`].
+ ///
+ /// See also:
+ /// * [`Self::create_timer_repeating`]
+ /// * [`Self::create_timer_oneshot`]
+ pub fn create_timer_inert<'a>(
+ self: &Arc,
+ options: impl IntoTimerOptions<'a>,
+ ) -> Result {
+ self.create_timer(options, AnyTimerCallback::Inert)
+ }
+
+ /// Used internally to create a [`Timer`].
+ ///
+ /// Downstream users should instead use:
+ /// * [`Self::create_timer_repeating`]
+ /// * [`Self::create_timer_oneshot`]
+ /// * [`Self::create_timer_inert`]
+ fn create_timer<'a>(
+ self: &Arc,
+ options: impl IntoTimerOptions<'a>,
+ callback: AnyTimerCallback,
+ ) -> Result {
+ let options = options.into_timer_options();
+ let clock = options.clock.as_clock(self);
+ let node = options.clock.is_node_time().then(|| Arc::clone(self));
+ TimerState::create(
+ options.period,
+ clock,
+ callback,
+ self.commands.async_worker_commands(),
+ &self.handle.context_handle,
+ node,
+ )
+ }
+
/// Returns the ROS domain ID that the node is using.
///
/// The domain ID controls which nodes can send messages to each other, see the [ROS 2 concept article][1].
diff --git a/rclrs/src/subscription/readonly_loaned_message.rs b/rclrs/src/subscription/readonly_loaned_message.rs
index 67ac3fd6..0ea73c1f 100644
--- a/rclrs/src/subscription/readonly_loaned_message.rs
+++ b/rclrs/src/subscription/readonly_loaned_message.rs
@@ -12,8 +12,6 @@ use crate::{rcl_bindings::*, subscription::SubscriptionHandle, ToResult};
///
/// This type may be used in subscription callbacks to receive a message. The
/// loan is returned by dropping the `ReadOnlyLoanedMessage`.
-///
-/// [1]: crate::SubscriptionState::take_loaned
pub struct ReadOnlyLoanedMessage
where
T: Message,
diff --git a/rclrs/src/timer.rs b/rclrs/src/timer.rs
new file mode 100644
index 00000000..22f8f5d2
--- /dev/null
+++ b/rclrs/src/timer.rs
@@ -0,0 +1,892 @@
+use crate::{
+ clock::Clock, context::ContextHandle, error::RclrsError, log_error, rcl_bindings::*, Node,
+ RclPrimitive, RclPrimitiveHandle, RclPrimitiveKind, ToLogParams, ToResult, Waitable,
+ WaitableLifecycle, WorkScope, Worker, WorkerCommands, ENTITY_LIFECYCLE_MUTEX,
+};
+// TODO: fix me when the callback type is properly defined.
+// use std::fmt::Debug;
+use std::{
+ any::Any,
+ sync::{Arc, Mutex, Weak},
+ time::Duration,
+};
+
+mod any_timer_callback;
+pub use any_timer_callback::*;
+
+mod timer_options;
+pub use timer_options::*;
+
+mod into_node_timer_callback;
+pub use into_node_timer_callback::*;
+
+mod into_worker_timer_callback;
+pub use into_worker_timer_callback::*;
+
+/// Struct for executing periodic events.
+///
+/// The executor needs to be [spinning][1] for a timer's callback to be triggered.
+///
+/// Timers can be created by a [`Node`] using one of these methods:
+/// - [`NodeState::create_timer_repeating`][2]
+/// - [`NodeState::create_timer_oneshot`][3]
+/// - [`NodeState::create_timer_inert`][4]
+///
+/// Timers can also be created by a [`Worker`], in which case they can access the worker's payload:
+/// - [`WorkerState::create_timer_repeating`][5]
+/// - [`WorkerState::create_timer_oneshot`][6]
+/// - [`WorkerState::create_timer_inert`][7]
+///
+/// The API of timers is given by [`TimerState`].
+///
+/// [1]: crate::Executor::spin
+/// [2]: crate::NodeState::create_timer_repeating
+/// [3]: crate::NodeState::create_timer_oneshot
+/// [4]: crate::NodeState::create_timer_inert
+/// [5]: crate::WorkerState::create_timer_repeating
+/// [6]: crate::WorkerState::create_timer_oneshot
+/// [7]: crate::WorkerState::create_timer_inert
+pub type Timer = Arc>;
+
+/// A [`Timer`] that runs on a [`Worker`].
+///
+/// Create a worker timer using [`create_timer_repeating`][1],
+/// [`create_timer_oneshot`][2], or [`create_timer_inert`][3].
+///
+/// [1]: crate::WorkerState::create_timer_repeating
+/// [2]: crate::WorkerState::create_timer_oneshot
+/// [3]: crate::WorkerState::create_timer_inert
+pub type WorkerTimer = Arc>>;
+
+/// The inner state of a [`Timer`].
+///
+/// This is public so that you can choose to create a [`Weak`] reference to it
+/// if you want to be able to refer to a [`Timer`] in a non-owning way. It is
+/// generally recommended to manage the `TimerState` inside of an [`Arc`], and
+/// [`Timer`] is provided as a convenience alias for that.
+///
+/// The public API of [`Timer`] is implemented via `TimerState`.
+///
+/// Timers that run inside of a [`Worker`] are represented by [`WorkerTimer`].
+pub struct TimerState {
+ pub(crate) handle: Arc,
+ /// The callback function that runs when the timer is due.
+ callback: Mutex