-
Notifications
You must be signed in to change notification settings - Fork 0
Add ROS2 timers #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
ed5382b
to
fd720b0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work on this it's looking real clean. There wasn't much to comment on, but I've left some thoughts and questions.
rclrs/src/timer.rs
Outdated
// Therefore, this type can be safely sent to another thread. | ||
unsafe impl Send for rcl_timer_t {} | ||
|
||
/// Manage the lifecycle of an `rcl_timer_t`, including managing its dependencies |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Manage the lifecycle of an `rcl_timer_t`, including managing its dependencies | |
/// Manage the lifecycle of an `rcl_timer_t`, and its dependencies |
rclrs/src/timer.rs
Outdated
pub fn new<F>( | ||
context: &Context, | ||
clock: Clock, | ||
period: Duration, | ||
callback: F, | ||
) -> Result<Self, RclrsError> | ||
where | ||
F: FnMut(&mut Timer) + 'static + Send + Sync, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we get away with a lifetime guarantee something like the suggested? What is the benefit of a 'static lifetime here?
pub fn new<F>( | |
context: &Context, | |
clock: Clock, | |
period: Duration, | |
callback: F, | |
) -> Result<Self, RclrsError> | |
where | |
F: FnMut(&mut Timer) + 'static + Send + Sync, | |
pub fn new<'a, F>( | |
context: &'a Context, | |
clock: Clock, | |
period: Duration, | |
callback: F, | |
) -> Result<Self, RclrsError> | |
where | |
F: FnMut(&mut Timer) + 'a+ Send + Sync, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one often gets me. Keep in mind the 'static
trait bound is subtly different from the 'static
reference lifetime (as per rust by example).
By my understanding the practical implication of a 'static
trait bound (as seen here) is that Timer needs to own the callback, since that fulfills the requirement that "the receiver can hold on to the type for as long as they want and it will never become invalid until they drop it". This includes owning anything captured by a closure being used as the callback.
If we wanted to switch to lifetimes it would probably need to be more like:
pub fn new<'a, F>(
context: &Context,
clock: Clock,
period: Duration,
callback: F,
) -> Result<Timer<'a>, RclrsError>
where
F: FnMut(&mut Timer) + 'a + Send + Sync,
Since the callback needs to live at least as long as the Timer class, so they're what need to share the lifetime (the &Context
just contains an Arc
that can be cloned).
This works and does mean you can pass in a &mut
to closures used for the callback, but there's a trade-off in that we can no longer store the Timer class inside an Arc.
The trade-off comes from Rusts subtyping and variance system. I've only got a basic understanding of it, but it seems Arc<T>
creates a new lifetime that is independent of the object it's storing, so Arc<T>
itself needs a 'static
trait bound on T
(as Arc is invariant over T), so the compiler won't allow storing a Timer<'a>
that isn't 'static
.
With that in mind, the benefits of explicitly tracking the lifetime doesn't seem to outweigh the costs. Closures can easily "own" any data they reference just by using Arc<Mutex>
types, working around an inability to use &mut
, but there doesn't seem to be a neat workaround to not being able to put Timer
classes inside Arc
s.
All of that is to say I think we're best served by sticking with 'static
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes good sense, thanks for digging into it and providing a nice succinct answer!
rclrs/src/timer.rs
Outdated
let new_period = Duration::from_millis(100); | ||
|
||
// Calling set_period will trigger the debug_assert check on the rcl return value. | ||
timer.set_period(new_period.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per: https://github.com/geoff-imdex/ros2_rust/actions/runs/11564633206/job/32190192780#step:11:395
timer.set_period(new_period.clone()); | |
timer.set_period(new_period); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed.
rclrs/src/timer.rs
Outdated
// SAFETY: | ||
// * The timer is initialized, which is guaranteed by the constructor. | ||
// * The elapsed_time pointer is allocated on the stack and is valid for the duration of this function. | ||
let ret = unsafe { rcl_timer_get_time_since_last_call(&mut *timer, &mut elapsed_time) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per: https://github.com/geoff-imdex/ros2_rust/actions/runs/11564633206/job/32190192780#step:11:395
let ret = unsafe { rcl_timer_get_time_since_last_call(&mut *timer, &mut elapsed_time) }; | |
let ret = unsafe { rcl_timer_get_time_since_last_call(& *timer, &mut elapsed_time) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed.
rclrs/src/timer.rs
Outdated
// * The timer is initialized, which is guaranteed by the constructor. | ||
// * The remaining_time pointer is allocated on the stack and is valid for the duration of this function. | ||
unsafe { | ||
rcl_timer_get_time_until_next_call(&mut *timer, &mut remaining_time).ok()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per: https://github.com/geoff-imdex/ros2_rust/actions/runs/11564633206/job/32190192780#step:11:395
rcl_timer_get_time_until_next_call(&mut *timer, &mut remaining_time).ok()?; | |
rcl_timer_get_time_until_next_call(& *timer, &mut remaining_time).ok()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed.
rclrs/src/timer.rs
Outdated
// SAFETY: | ||
// * The timer is initialized, which is guaranteed by the constructor. | ||
// * The is_ready pointer is allocated on the stack and is valid for the duration of this function. | ||
let ret = unsafe { rcl_timer_is_ready(&mut *timer, &mut is_ready) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Per: https://github.com/geoff-imdex/ros2_rust/actions/runs/11564633206/job/32190192780#step:11:395
let ret = unsafe { rcl_timer_is_ready(&mut *timer, &mut is_ready) }; | |
let ret = unsafe { rcl_timer_is_ready(& *timer, &mut is_ready) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed.
rclrs/src/timer.rs
Outdated
/// [5]: crate::Timer::execute | ||
/// [6]: crate::WaitSet | ||
pub struct Timer { | ||
callback: Arc<Mutex<dyn FnMut(&mut Timer) + Send + Sync>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per: https://github.com/geoff-imdex/ros2_rust/actions/runs/11564633206/job/32190192780#step:11:395
Could be simplified with a type alias.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, fixed.
Note that the upstream rclrs repo has an open PR that introduces async executors, and it will make some significant changes to how wait sets work: ros2-rust#421 Be ready for some merge conflicts down the road. I do have a plan to introduce timers on top of the async execution PR, but I'm currently waiting until that PR gets merged first. If you have a need for timers and are interested in jumping forward to the async execution implementation (at the risk of some API instability), then I'd be happy to make a branch that includes timers. |
@mxgrey I appreciate the heads up, that's very useful to know. It looks like you've done a lot of great work on the async executor, that's a nice improvement. We'll go ahead with the sync version in the short term, as that unblocks our immediate need for timers, but the less time we spend diverged from upstream the better. If you're willing to do an async timer implementation that would be amazing. You're more than welcome to copy anything here, I would assume the Timer struct will be largely transferrable. Once the async execution is merged into main I might be able to help port timers across too. |
597f539
to
58f95ed
Compare
58f95ed
to
27a619e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good. Only one extremely pedantic comment/question - the rcl_bindings:: in the timer.rs
explicitly includes all the used functions (nice), however this a long list and many other places seem to use the rcl_bindings::*
approach unless they're pulling in a trivially small amount of functions. Not sure it's worth changing, but I had to mention
This PR adds ROS2 timers and enables them to be triggered by waitsets.