Skip to content

Commit 5cf50fd

Browse files
committed
Merge in Timer features
Signed-off-by: Michael X. Grey <[email protected]>
2 parents 4a98c8d + c7c1cba commit 5cf50fd

20 files changed

+1317
-80
lines changed

examples/rclrs_timer_demo/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "rclrs_timer_demo"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[[bin]]
7+
name="rclrs_timer_demo"
8+
path="src/rclrs_timer_demo.rs"
9+
10+
11+
[dependencies]
12+
rclrs = "*"

examples/rclrs_timer_demo/package.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<package format="3">
2+
<name>rclrs_timer_demo</name>
3+
<version>0.1.0</version>
4+
<description>Shows how to implement a timer within a Node using rclrs.</description>
5+
<maintainer email="[email protected]">user</maintainer>
6+
<license>TODO: License declaration.</license>
7+
8+
<depend>rclrs</depend>
9+
10+
<export>
11+
<build_type>ament_cargo</build_type>
12+
</export>
13+
</package>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/// Creates a SimpleTimerNode, initializes a node and the timer with a callback
2+
/// that prints the timer callback execution iteration. The callback is executed
3+
/// thanks to the spin, which is in charge of executing the timer's events among
4+
/// other entities' events.
5+
use rclrs::{create_node, Context, Node, RclrsError, Timer};
6+
use std::{
7+
env,
8+
sync::Arc,
9+
time::Duration,
10+
};
11+
12+
/// Contains both the node and timer.
13+
struct SimpleTimerNode {
14+
node: Arc<Node>,
15+
#[allow(unused)]
16+
timer: Arc<Timer>,
17+
}
18+
19+
impl SimpleTimerNode {
20+
/// Creates a node and a timer with a callback.
21+
///
22+
/// The callback will simply print to stdout:
23+
/// "Drinking 🧉 for the xth time every p nanoseconds."
24+
/// where x is the iteration callback counter and p is the period of the timer.
25+
fn new(context: &Context, timer_period: Duration) -> Result<Self, RclrsError> {
26+
let node = create_node(context, "simple_timer_node")?;
27+
let mut x = 0;
28+
let timer = node.create_timer_repeating(
29+
timer_period,
30+
move || {
31+
x += 1;
32+
println!(
33+
"Drinking 🧉 for the {x}th time every {:?}.",
34+
timer_period,
35+
);
36+
},
37+
)?;
38+
Ok(Self { node, timer })
39+
}
40+
}
41+
42+
fn main() -> Result<(), RclrsError> {
43+
let context = Context::new(env::args()).unwrap();
44+
let simple_timer_node = Arc::new(SimpleTimerNode::new(&context, Duration::from_secs(1)).unwrap());
45+
rclrs::spin(simple_timer_node.node.clone())
46+
}

rclrs/src/clock.rs

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,31 @@ impl Clock {
6666
}
6767

6868
fn make(kind: ClockType) -> Self {
69-
let mut rcl_clock;
69+
let rcl_clock;
7070
unsafe {
7171
// SAFETY: Getting a default value is always safe.
72-
rcl_clock = Self::init_generic_clock();
72+
let allocator = rcutils_get_default_allocator();
73+
rcl_clock = Arc::new(Mutex::new(rcl_clock_t {
74+
type_: rcl_clock_type_t::RCL_CLOCK_UNINITIALIZED,
75+
jump_callbacks: std::ptr::null_mut(),
76+
num_jump_callbacks: 0,
77+
get_now: None,
78+
data: std::ptr::null_mut::<std::os::raw::c_void>(),
79+
allocator,
80+
}));
7381
let mut allocator = rcutils_get_default_allocator();
7482
// Function will return Err(_) only if there isn't enough memory to allocate a clock
7583
// object.
76-
rcl_clock_init(kind.into(), &mut rcl_clock, &mut allocator)
84+
rcl_clock_init(kind.into(), &mut *rcl_clock.lock().unwrap(), &mut allocator)
7785
.ok()
7886
.unwrap();
7987
}
80-
Self {
81-
kind,
82-
rcl_clock: Arc::new(Mutex::new(rcl_clock)),
83-
}
88+
Self { kind, rcl_clock }
89+
}
90+
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
8494
}
8595

8696
/// Returns the clock's `ClockType`.
@@ -101,22 +111,6 @@ impl Clock {
101111
clock: Arc::downgrade(&self.rcl_clock),
102112
}
103113
}
104-
105-
/// Helper function to privately initialize a default clock, with the same behavior as
106-
/// `rcl_init_generic_clock`. By defining a private function instead of implementing
107-
/// `Default`, we avoid exposing a public API to create an invalid clock.
108-
// SAFETY: Getting a default value is always safe.
109-
unsafe fn init_generic_clock() -> rcl_clock_t {
110-
let allocator = rcutils_get_default_allocator();
111-
rcl_clock_t {
112-
type_: rcl_clock_type_t::RCL_CLOCK_UNINITIALIZED,
113-
jump_callbacks: std::ptr::null_mut::<rcl_jump_callback_info_t>(),
114-
num_jump_callbacks: 0,
115-
get_now: None,
116-
data: std::ptr::null_mut::<std::os::raw::c_void>(),
117-
allocator,
118-
}
119-
}
120114
}
121115

122116
impl Drop for ClockSource {

rclrs/src/error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ pub enum RclrsError {
3232
},
3333
/// It was attempted to add a waitable to a wait set twice.
3434
AlreadyAddedToWaitSet,
35+
/// A negative duration was obtained from rcl which should have been positive.
36+
///
37+
/// The value represents nanoseconds.
38+
NegativeDuration(i64),
3539
/// The guard condition that you tried to trigger is not owned by the
3640
/// [`GuardCondition`][crate::GuardCondition] instance.
3741
UnownedGuardCondition,
@@ -84,6 +88,12 @@ impl Display for RclrsError {
8488
"Could not add entity to wait set because it was already added to a wait set"
8589
)
8690
}
91+
RclrsError::NegativeDuration(duration) => {
92+
write!(
93+
f,
94+
"A duration was negative when it should not have been: {duration:?}"
95+
)
96+
}
8797
RclrsError::UnownedGuardCondition => {
8898
write!(
8999
f,
@@ -130,6 +140,7 @@ impl Error for RclrsError {
130140
// TODO(@mxgrey): We should provide source information for these other types.
131141
// It should be easy to do this using the thiserror crate.
132142
RclrsError::AlreadyAddedToWaitSet => None,
143+
RclrsError::NegativeDuration(_) => None,
133144
RclrsError::UnownedGuardCondition => None,
134145
RclrsError::InvalidPayload { .. } => None,
135146
}

rclrs/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod service;
2020
mod subscription;
2121
mod time;
2222
mod time_source;
23+
mod timer;
2324
mod vendor;
2425
mod wait_set;
2526
mod worker;
@@ -48,5 +49,6 @@ pub use service::*;
4849
pub use subscription::*;
4950
pub use time::*;
5051
use time_source::*;
52+
pub use timer::*;
5153
pub use wait_set::*;
5254
pub use worker::*;

rclrs/src/node.rs

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ use crate::{
3434
Publisher, PublisherOptions, PublisherState, RclrsError, Service, IntoAsyncServiceCallback,
3535
IntoNodeServiceCallback, ServiceOptions, ServiceState, Subscription, IntoAsyncSubscriptionCallback,
3636
IntoNodeSubscriptionCallback, SubscriptionOptions, SubscriptionState, TimeSource, ToLogParams,
37-
ENTITY_LIFECYCLE_MUTEX, IntoWorkerOptions, Worker, WorkerState,
37+
ENTITY_LIFECYCLE_MUTEX, IntoWorkerOptions, Worker, WorkerState, IntoTimerOptions, AnyTimerCallback,
38+
Timer, IntoNodeTimerRepeatingCallback, IntoNodeTimerOneshotCallback, TimerState,
3839
};
3940

4041
/// A processing unit that can communicate with other nodes.
@@ -244,7 +245,7 @@ impl NodeState {
244245
options: impl IntoWorkerOptions<Payload>,
245246
) -> Worker<Payload>
246247
where
247-
Payload: 'static + Send,
248+
Payload: 'static + Send + Sync,
248249
{
249250
let options = options.into_worker_options();
250251
let commands = self.commands.create_worker_commands(Box::new(options.payload));
@@ -555,6 +556,70 @@ impl NodeState {
555556
)
556557
}
557558

559+
/// Create a [`Timer`] with a repeating callback.
560+
///
561+
/// See also:
562+
/// * [`Self::create_timer_oneshot`]
563+
/// * [`Self::create_timer_inert`]
564+
pub fn create_timer_repeating<'a, Args>(
565+
&self,
566+
options: impl IntoTimerOptions<'a>,
567+
callback: impl IntoNodeTimerRepeatingCallback<Args>,
568+
) -> Result<Timer, RclrsError> {
569+
self.create_timer(options, callback.into_node_timer_repeating_callback())
570+
}
571+
572+
/// Create a [`Timer`] whose callback will be triggered once after the period
573+
/// of the timer has elapsed. After that you will need to use
574+
/// [`Timer::set_callback`] or a related method or else nothing will happen
575+
/// the following times that the `Timer` elapses.
576+
///
577+
/// See also:
578+
/// * [`Self::create_timer_repeating`]
579+
/// * [`Self::create_time_inert`]
580+
pub fn create_timer_oneshot<'a, Args>(
581+
&self,
582+
options: impl IntoTimerOptions<'a>,
583+
callback: impl IntoNodeTimerOneshotCallback<Args>,
584+
) -> Result<Timer, RclrsError> {
585+
self.create_timer(options, callback.into_node_timer_oneshot_callback())
586+
}
587+
588+
/// Create a [`Timer`] without a callback. Nothing will happen when this
589+
/// `Timer` elapses until you use [`Timer::set_callback`] or a related method.
590+
///
591+
/// See also:
592+
/// * [`Self::create_timer_repeating`]
593+
/// * [`Self::create_timer_oneshot`]
594+
pub fn create_timer_inert<'a>(
595+
&self,
596+
options: impl IntoTimerOptions<'a>,
597+
) -> Result<Timer, RclrsError> {
598+
self.create_timer(options, AnyTimerCallback::Inert)
599+
}
600+
601+
/// Used internally to create a [`Timer`].
602+
///
603+
/// Downstream users should instead use:
604+
/// * [`Self::create_timer_repeating`]
605+
/// * [`Self::create_timer_oneshot`]
606+
/// * [`Self::create_timer_inert`]
607+
fn create_timer<'a>(
608+
&self,
609+
options: impl IntoTimerOptions<'a>,
610+
callback: AnyTimerCallback<Node>,
611+
) -> Result<Timer, RclrsError> {
612+
let options = options.into_timer_options();
613+
let clock = options.clock.as_clock(self);
614+
TimerState::create(
615+
options.period,
616+
clock,
617+
callback,
618+
self.commands.async_worker_commands(),
619+
&self.handle.context_handle,
620+
)
621+
}
622+
558623
/// Returns the ROS domain ID that the node is using.
559624
///
560625
/// The domain ID controls which nodes can send messages to each other, see the [ROS 2 concept article][1].
@@ -748,6 +813,11 @@ unsafe impl Send for rcl_node_t {}
748813
mod tests {
749814
use crate::{test_helpers::*, *};
750815

816+
use std::{
817+
time::Duration,
818+
sync::{Arc, atomic::{AtomicU64, Ordering}},
819+
};
820+
751821
#[test]
752822
fn traits() {
753823
assert_send::<NodeState>();
@@ -794,6 +864,68 @@ mod tests {
794864
Ok(())
795865
}
796866

867+
#[test]
868+
fn test_create_timer() -> Result<(), RclrsError> {
869+
dbg!();
870+
let mut executor = Context::default().create_basic_executor();
871+
let node = executor.create_node("node_with_timer")?;
872+
873+
let repeat_counter = Arc::new(AtomicU64::new(0));
874+
let repeat_counter_check = Arc::clone(&repeat_counter);
875+
let _repeating_timer = node.create_timer_repeating(
876+
Duration::from_millis(1),
877+
move || {
878+
dbg!();
879+
repeat_counter.fetch_add(1, Ordering::AcqRel);
880+
},
881+
)?;
882+
883+
let oneshot_counter = Arc::new(AtomicU64::new(0));
884+
let oneshot_counter_check = Arc::clone(&oneshot_counter);
885+
let _oneshot_timer = node.create_timer_oneshot(
886+
Duration::from_millis(1)
887+
.node_time(),
888+
move || {
889+
dbg!();
890+
oneshot_counter.fetch_add(1, Ordering::AcqRel);
891+
},
892+
)?;
893+
894+
let oneshot_resetting_counter = Arc::new(AtomicU64::new(0));
895+
let oneshot_resetting_counter_check = Arc::clone(&oneshot_resetting_counter);
896+
let _oneshot_resetting_timer = node.create_timer_oneshot(
897+
Duration::from_millis(1),
898+
move |timer: &Timer| {
899+
dbg!();
900+
recursive_oneshot(timer, oneshot_resetting_counter);
901+
},
902+
);
903+
904+
dbg!();
905+
executor.spin(SpinOptions::new().timeout(Duration::from_millis(10)));
906+
907+
// We give a little leeway to the exact count since timers won't always
908+
// be triggered perfectly. The important thing is that it was
909+
// successfully called repeatedly.
910+
assert!(repeat_counter_check.load(Ordering::Acquire) > 5);
911+
assert!(oneshot_resetting_counter_check.load(Ordering::Acquire) > 5);
912+
913+
// This should only have been triggered exactly once
914+
assert_eq!(oneshot_counter_check.load(Ordering::Acquire), 1);
915+
916+
Ok(())
917+
}
918+
919+
fn recursive_oneshot(
920+
timer: &Timer,
921+
counter: Arc<AtomicU64>,
922+
) {
923+
counter.fetch_add(1, Ordering::AcqRel);
924+
timer.set_oneshot(move |timer: &Timer| {
925+
recursive_oneshot(timer, counter);
926+
});
927+
}
928+
797929
#[test]
798930
fn test_logger_name() -> Result<(), RclrsError> {
799931
// Use helper to create 2 nodes for us

rclrs/src/rcl_bindings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ cfg_if::cfg_if! {
8989
#[derive(Debug)]
9090
pub struct rcl_wait_set_t;
9191

92+
#[repr(C)]
93+
#[derive(Debug)]
94+
pub struct rcl_timer_t;
95+
9296
#[repr(C)]
9397
#[derive(Debug)]
9498
pub struct rcutils_string_array_t;

0 commit comments

Comments
 (0)