Skip to content

Commit 8b29904

Browse files
mxgreyagalbachicarJesusSilvaUtrera
authored andcommitted
feat: timers API (ros2-rust#480)
* Fix merge conflicts with main Signed-off-by: Michael X. Grey <[email protected]> * Update documentation of timers Signed-off-by: Michael X. Grey <[email protected]> * Fix compatibility with kilted and rolling Signed-off-by: Michael X. Grey <[email protected]> * Fix typo Signed-off-by: Michael X. Grey <[email protected]> * Remove Cargo.lock for timers demo Signed-off-by: Michael X. Grey <[email protected]> * Fix example package names Signed-off-by: Michael X. Grey <[email protected]> * Fix name of timer demo Signed-off-by: Michael X. Grey <[email protected]> * Fix name of parameter demo Signed-off-by: Michael X. Grey <[email protected]> * Add safety comments Signed-off-by: Michael X. Grey <[email protected]> * Add an explicit binary name for timer demo Signed-off-by: Michael X. Grey <[email protected]> * Restore unsafe tag to Send trait Signed-off-by: Michael X. Grey <[email protected]> * Include authors of ros2-rust#440 Co-authored-by: Agustin Alba Chicar <[email protected]> Co-authored-by: Jesús Silva <[email protected]> Signed-off-by: Agustin Alba Chicar <[email protected]> Signed-off-by: Jesús Silva <[email protected]> Signed-off-by: Michael X. Grey <[email protected]> * Add safety comment Signed-off-by: Michael X. Grey <[email protected]> * Finish sentence Signed-off-by: Michael X. Grey <[email protected]> * Timers have a dependency on Node when using NodeTime Signed-off-by: Michael X. Grey <[email protected]> * Move timer_demo to examples repo Signed-off-by: Michael X. Grey <[email protected]> * Expand on timer docs Signed-off-by: Michael X. Grey <[email protected]> * trigger change Signed-off-by: Michael X. Grey <[email protected]> --------- Signed-off-by: Michael X. Grey <[email protected]> Signed-off-by: Agustin Alba Chicar <[email protected]> Signed-off-by: Jesús Silva <[email protected]> Co-authored-by: Agustin Alba Chicar <[email protected]> Co-authored-by: Jesús Silva <[email protected]>
1 parent 9dea948 commit 8b29904

File tree

10 files changed

+1611
-11
lines changed

10 files changed

+1611
-11
lines changed

rclrs/src/clock.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ impl Clock {
8888
Self { kind, rcl_clock }
8989
}
9090

91+
/// Returns the clock's `rcl_clock_t`.
92+
pub(crate) fn get_rcl_clock(&self) -> &Arc<Mutex<rcl_clock_t>> {
93+
&self.rcl_clock
94+
}
95+
9196
/// Returns the clock's `ClockType`.
9297
pub fn clock_type(&self) -> ClockType {
9398
self.kind

rclrs/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ mod service;
193193
mod subscription;
194194
mod time;
195195
mod time_source;
196+
mod timer;
196197
pub mod vendor;
197198
mod wait_set;
198199
mod worker;
@@ -221,5 +222,6 @@ pub use service::*;
221222
pub use subscription::*;
222223
pub use time::*;
223224
use time_source::*;
225+
pub use timer::*;
224226
pub use wait_set::*;
225227
pub use worker::*;

rclrs/src/node.rs

Lines changed: 231 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ use async_std::future::timeout;
2929
use rosidl_runtime_rs::Message;
3030

3131
use crate::{
32-
rcl_bindings::*, Client, ClientOptions, ClientState, Clock, ContextHandle, ExecutorCommands,
33-
IntoAsyncServiceCallback, IntoAsyncSubscriptionCallback, IntoNodeServiceCallback,
34-
IntoNodeSubscriptionCallback, LogParams, Logger, ParameterBuilder, ParameterInterface,
35-
ParameterVariant, Parameters, Promise, Publisher, PublisherOptions, PublisherState, RclrsError,
36-
Service, ServiceOptions, ServiceState, Subscription, SubscriptionOptions, SubscriptionState,
37-
TimeSource, ToLogParams, Worker, WorkerOptions, WorkerState, ENTITY_LIFECYCLE_MUTEX,
32+
rcl_bindings::*, AnyTimerCallback, Client, ClientOptions, ClientState, Clock, ContextHandle,
33+
ExecutorCommands, IntoAsyncServiceCallback, IntoAsyncSubscriptionCallback,
34+
IntoNodeServiceCallback, IntoNodeSubscriptionCallback, IntoNodeTimerOneshotCallback,
35+
IntoNodeTimerRepeatingCallback, IntoTimerOptions, LogParams, Logger, ParameterBuilder,
36+
ParameterInterface, ParameterVariant, Parameters, Promise, Publisher, PublisherOptions,
37+
PublisherState, RclrsError, Service, ServiceOptions, ServiceState, Subscription,
38+
SubscriptionOptions, SubscriptionState, TimeSource, Timer, TimerState, ToLogParams, Worker,
39+
WorkerOptions, WorkerState, ENTITY_LIFECYCLE_MUTEX,
3840
};
3941

4042
/// A processing unit that can communicate with other nodes. See the API of
@@ -908,6 +910,229 @@ impl NodeState {
908910
)
909911
}
910912

913+
/// Create a [`Timer`] with a repeating callback.
914+
///
915+
/// This has similar behavior to `rclcpp::Node::create_timer` by periodically
916+
/// triggering the callback of the timer. For a one-shot timer alternative,
917+
/// see [`NodeState::create_timer_oneshot`].
918+
///
919+
/// See also:
920+
/// * [`Self::create_timer_oneshot`]
921+
/// * [`Self::create_timer_inert`]
922+
///
923+
/// # Behavior
924+
///
925+
/// While the callback of this timer is running, no other callbacks associated
926+
/// with this node will be able to run. This is in contrast to callbacks given
927+
/// to [`Self::create_subscription`] which can run multiple times in parallel.
928+
///
929+
/// Since the callback of this timer may block other callbacks from being able
930+
/// to run, it is strongly recommended to ensure that the callback returns
931+
/// quickly. If the callback needs to trigger long-running behavior then you
932+
/// can consider using [`std::thread::spawn`], or for async behaviors you can
933+
/// capture an [`ExecutorCommands`] in your callback and use [`ExecutorCommands::run`]
934+
/// to issue a task for the executor to run in its async task pool.
935+
///
936+
/// Since these callbacks are blocking, you may use [`FnMut`] here instead of
937+
/// being limited to [`Fn`].
938+
///
939+
/// # Timer Options
940+
///
941+
/// You can choose both
942+
/// 1. a timer period (duration) which determines how often the callback is triggered
943+
/// 2. a clock to measure the passage of time
944+
///
945+
/// Both of these choices are expressed by [`TimerOptions`][1].
946+
///
947+
/// By default the steady clock time will be used, but you could choose
948+
/// node time instead if you want the timer to automatically use simulated
949+
/// time when running as part of a simulation:
950+
/// ```
951+
/// # use rclrs::*;
952+
/// # let executor = Context::default().create_basic_executor();
953+
/// # let node = executor.create_node("my_node").unwrap();
954+
/// use std::time::Duration;
955+
///
956+
/// let timer = node.create_timer_repeating(
957+
/// TimerOptions::new(Duration::from_secs(1))
958+
/// .node_time(),
959+
/// || {
960+
/// println!("Triggering once each simulated second");
961+
/// },
962+
/// )?;
963+
/// # Ok::<(), RclrsError>(())
964+
/// ```
965+
///
966+
/// If there is a specific manually-driven clock you want to use, you can
967+
/// also select that:
968+
/// ```
969+
/// # use rclrs::*;
970+
/// # let executor = Context::default().create_basic_executor();
971+
/// # let node = executor.create_node("my_node").unwrap();
972+
/// use std::time::Duration;
973+
///
974+
/// let (my_clock, my_source) = Clock::with_source();
975+
///
976+
/// let timer = node.create_timer_repeating(
977+
/// TimerOptions::new(Duration::from_secs(1))
978+
/// .clock(&my_clock),
979+
/// || {
980+
/// println!("Triggering once each simulated second");
981+
/// },
982+
/// )?;
983+
///
984+
/// my_source.set_ros_time_override(1_500_000_000);
985+
/// # Ok::<(), RclrsError>(())
986+
/// ```
987+
///
988+
/// If you are okay with the default choice of clock (steady clock) then you
989+
/// can choose to simply pass a duration in as the options:
990+
/// ```
991+
/// # use rclrs::*;
992+
/// # let executor = Context::default().create_basic_executor();
993+
/// # let node = executor.create_node("my_node").unwrap();
994+
/// use std::time::Duration;
995+
///
996+
/// let timer = node.create_timer_repeating(
997+
/// Duration::from_secs(1),
998+
/// || {
999+
/// println!("Triggering per steady clock second");
1000+
/// },
1001+
/// )?;
1002+
/// # Ok::<(), RclrsError>(())
1003+
/// ```
1004+
///
1005+
/// # Node Timer Repeating Callbacks
1006+
///
1007+
/// Node Timer repeating callbacks support three signatures:
1008+
/// - <code>[FnMut] ()</code>
1009+
/// - <code>[FnMut] ([Time][2])</code>
1010+
/// - <code>[FnMut] (&[Timer])</code>
1011+
///
1012+
/// You can choose to receive the current time when the callback is being
1013+
/// triggered.
1014+
///
1015+
/// Or instead of the current time, you can get a borrow of the [`Timer`]
1016+
/// itself, that way if you need to access it from inside the callback, you
1017+
/// do not need to worry about capturing a [`Weak`][3] and then locking it.
1018+
/// This is useful if you need to change the callback of the timer from inside
1019+
/// the callback of the timer.
1020+
///
1021+
/// For an [`FnOnce`] instead of [`FnMut`], use [`Self::create_timer_oneshot`].
1022+
///
1023+
/// [1]: crate::TimerOptions
1024+
/// [2]: crate::Time
1025+
/// [3]: std::sync::Weak
1026+
pub fn create_timer_repeating<'a, Args>(
1027+
self: &Arc<Self>,
1028+
options: impl IntoTimerOptions<'a>,
1029+
callback: impl IntoNodeTimerRepeatingCallback<Args>,
1030+
) -> Result<Timer, RclrsError> {
1031+
self.create_timer_internal(options, callback.into_node_timer_repeating_callback())
1032+
}
1033+
1034+
/// Create a [`Timer`] whose callback will be triggered once after the period
1035+
/// of the timer has elapsed. After that you will need to use
1036+
/// [`TimerState::set_repeating`] or [`TimerState::set_oneshot`] or else
1037+
/// nothing will happen the following times that the `Timer` elapses.
1038+
///
1039+
/// This does not have an equivalent in `rclcpp`.
1040+
///
1041+
/// See also:
1042+
/// * [`Self::create_timer_repeating`]
1043+
/// * [`Self::create_timer_inert`]
1044+
///
1045+
/// # Behavior
1046+
///
1047+
/// While the callback of this timer is running, no other callbacks associated
1048+
/// with this node will be able to run. This is in contrast to callbacks given
1049+
/// to [`Self::create_subscription`] which can run multiple times in parallel.
1050+
///
1051+
/// Since the callback of this timer may block other callbacks from being able
1052+
/// to run, it is strongly recommended to ensure that the callback returns
1053+
/// quickly. If the callback needs to trigger long-running behavior then you
1054+
/// can consider using [`std::thread::spawn`], or for async behaviors you can
1055+
/// capture an [`ExecutorCommands`] in your callback and use [`ExecutorCommands::run`]
1056+
/// to issue a task for the executor to run in its async task pool.
1057+
///
1058+
/// Since these callbacks will only be triggered once, you may use [`FnOnce`] here.
1059+
///
1060+
/// # Timer Options
1061+
///
1062+
/// See [`NodeSate::create_timer_repeating`][3] for examples of setting the
1063+
/// timer options.
1064+
///
1065+
/// # Node Timer Oneshot Callbacks
1066+
///
1067+
/// Node Timer OneShot callbacks support three signatures:
1068+
/// - <code>[FnOnce] ()</code>
1069+
/// - <code>[FnOnce] ([Time][2])</code>
1070+
/// - <code>[FnOnce] (&[Timer])</code>
1071+
///
1072+
/// You can choose to receive the current time when the callback is being
1073+
/// triggered.
1074+
///
1075+
/// Or instead of the current time, you can get a borrow of the [`Timer`]
1076+
/// itself, that way if you need to access it from inside the callback, you
1077+
/// do not need to worry about capturing a [`Weak`][3] and then locking it.
1078+
/// This is useful if you need to change the callback of the timer from inside
1079+
/// the callback of the timer.
1080+
///
1081+
/// [2]: crate::Time
1082+
/// [3]: std::sync::Weak
1083+
pub fn create_timer_oneshot<'a, Args>(
1084+
self: &Arc<Self>,
1085+
options: impl IntoTimerOptions<'a>,
1086+
callback: impl IntoNodeTimerOneshotCallback<Args>,
1087+
) -> Result<Timer, RclrsError> {
1088+
self.create_timer_internal(options, callback.into_node_timer_oneshot_callback())
1089+
}
1090+
1091+
/// Create a [`Timer`] without a callback. Nothing will happen when this
1092+
/// `Timer` elapses until you use [`TimerState::set_repeating`] or
1093+
/// [`TimerState::set_oneshot`].
1094+
///
1095+
/// This function is not usually what you want. An inert timer is usually
1096+
/// just a follow-up state to a oneshot timer which is waiting to be given
1097+
/// a new callback to run. However, you could use this method to declare a
1098+
/// timer whose callbacks you will start to feed in at a later.
1099+
///
1100+
/// There is no equivalent to this function in `rclcpp`.
1101+
///
1102+
/// See also:
1103+
/// * [`Self::create_timer_repeating`]
1104+
/// * [`Self::create_timer_oneshot`]
1105+
pub fn create_timer_inert<'a>(
1106+
self: &Arc<Self>,
1107+
options: impl IntoTimerOptions<'a>,
1108+
) -> Result<Timer, RclrsError> {
1109+
self.create_timer_internal(options, AnyTimerCallback::Inert)
1110+
}
1111+
1112+
/// Used internally to create any kind of [`Timer`].
1113+
///
1114+
/// Downstream users should instead use:
1115+
/// * [`Self::create_timer_repeating`]
1116+
/// * [`Self::create_timer_oneshot`]
1117+
/// * [`Self::create_timer_inert`]
1118+
fn create_timer_internal<'a>(
1119+
self: &Arc<Self>,
1120+
options: impl IntoTimerOptions<'a>,
1121+
callback: AnyTimerCallback<Node>,
1122+
) -> Result<Timer, RclrsError> {
1123+
let options = options.into_timer_options();
1124+
let clock = options.clock.as_clock(self);
1125+
let node = options.clock.is_node_time().then(|| Arc::clone(self));
1126+
TimerState::create(
1127+
options.period,
1128+
clock,
1129+
callback,
1130+
self.commands.async_worker_commands(),
1131+
&self.handle.context_handle,
1132+
node,
1133+
)
1134+
}
1135+
9111136
/// Returns the ROS domain ID that the node is using.
9121137
///
9131138
/// The domain ID controls which nodes can send messages to each other, see the [ROS 2 concept article][1].

rclrs/src/subscription/readonly_loaned_message.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ use crate::{rcl_bindings::*, subscription::SubscriptionHandle, ToResult};
1212
///
1313
/// This type may be used in subscription callbacks to receive a message. The
1414
/// loan is returned by dropping the `ReadOnlyLoanedMessage`.
15-
///
16-
/// [1]: crate::SubscriptionState::take_loaned
1715
pub struct ReadOnlyLoanedMessage<T>
1816
where
1917
T: Message,

0 commit comments

Comments
 (0)