From 2d20c4ec9baa48c013ad463ce04ee8a352fb46eb Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 14:14:00 +0800 Subject: [PATCH 01/12] Fix merge conflicts with main Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- examples/timer_demo/Cargo.toml | 8 + examples/timer_demo/package.xml | 21 + examples/timer_demo/src/main.rs | 22 + examples/worker_demo/src/main.rs | 27 +- rclrs/Cargo.lock | 422 +++++++++- rclrs/src/clock.rs | 5 + rclrs/src/lib.rs | 2 + rclrs/src/node.rs | 78 +- rclrs/src/timer.rs | 777 ++++++++++++++++++ rclrs/src/timer/any_timer_callback.rs | 15 + rclrs/src/timer/into_node_timer_callback.rs | 67 ++ rclrs/src/timer/into_worker_timer_callback.rs | 95 +++ rclrs/src/timer/timer_options.rs | 100 +++ rclrs/src/worker.rs | 67 +- 14 files changed, 1670 insertions(+), 36 deletions(-) create mode 100644 examples/timer_demo/Cargo.toml create mode 100644 examples/timer_demo/package.xml create mode 100644 examples/timer_demo/src/main.rs create mode 100644 rclrs/src/timer.rs create mode 100644 rclrs/src/timer/any_timer_callback.rs create mode 100644 rclrs/src/timer/into_node_timer_callback.rs create mode 100644 rclrs/src/timer/into_worker_timer_callback.rs create mode 100644 rclrs/src/timer/timer_options.rs diff --git a/examples/timer_demo/Cargo.toml b/examples/timer_demo/Cargo.toml new file mode 100644 index 000000000..23b9f1fa1 --- /dev/null +++ b/examples/timer_demo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "timer_demo" +version = "0.1.0" +edition = "2021" + +[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 000000000..684cea8fa --- /dev/null +++ b/examples/timer_demo/package.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<?xml-model + href="http://download.ros.org/schema/package_format3.xsd" + schematypens="http://www.w3.org/2001/XMLSchema"?> +<package format="3"> + <name>examples_timer_demo</name> + <maintainer email="esteve@apache.org">Esteve Fernandez</maintainer> + <!-- This project is not military-sponsored, Jacob's employment contract just requires him to use this email address --> + <maintainer email="jacob.a.hassold.civ@army.mil">Jacob Hassold</maintainer> + <version>0.4.1</version> + <description>Package containing an example of how to use a worker in rclrs.</description> + <license>Apache License 2.0</license> + + <depend>rclrs</depend> + <depend>rosidl_runtime_rs</depend> + <depend>example_interfaces</depend> + + <export> + <build_type>ament_cargo</build_type> + </export> +</package> diff --git a/examples/timer_demo/src/main.rs b/examples/timer_demo/src/main.rs new file mode 100644 index 000000000..608a82f10 --- /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::<usize>(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 253a95fce..bb3a97615 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/Cargo.lock b/rclrs/Cargo.lock index 5fc5e7bdc..834786e1f 100644 --- a/rclrs/Cargo.lock +++ b/rclrs/Cargo.lock @@ -4,10 +4,11 @@ version = 3 [[package]] name = "action_msgs" -version = "1.2.1" +version = "2.0.2" dependencies = [ "builtin_interfaces", "rosidl_runtime_rs", + "service_msgs", "unique_identifier_msgs", ] @@ -45,6 +46,126 @@ dependencies = [ "walkdir", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" @@ -92,13 +213,32 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "builtin_interfaces" -version = "1.2.1" +version = "2.0.2" dependencies = [ "rosidl_runtime_rs", ] +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "cexpr" version = "0.6.0" @@ -125,6 +265,21 @@ dependencies = [ "libloading", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "either" version = "1.15.0" @@ -141,6 +296,44 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + +[[package]] +name = "example_interfaces" +version = "0.12.0" +dependencies = [ + "action_msgs", + "builtin_interfaces", + "rosidl_runtime_rs", + "service_msgs", + "unique_identifier_msgs", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -195,6 +388,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -260,6 +466,24 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "itertools" version = "0.8.2" @@ -278,6 +502,25 @@ dependencies = [ "either", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "libc" version = "0.2.172" @@ -305,6 +548,9 @@ name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "value-bag", +] [[package]] name = "memchr" @@ -352,6 +598,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -364,6 +616,32 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys", +] + [[package]] name = "prettyplease" version = "0.2.32" @@ -403,8 +681,10 @@ name = "rclrs" version = "0.4.1" dependencies = [ "ament_rs", + "async-std", "bindgen", "cfg-if", + "example_interfaces", "futures", "libloading", "rosidl_runtime_rs", @@ -477,6 +757,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "same-file" version = "1.0.6" @@ -515,6 +801,14 @@ dependencies = [ "syn", ] +[[package]] +name = "service_msgs" +version = "2.0.2" +dependencies = [ + "builtin_interfaces", + "rosidl_runtime_rs", +] + [[package]] name = "shlex" version = "1.3.0" @@ -556,11 +850,12 @@ dependencies = [ [[package]] name = "test_msgs" -version = "1.2.1" +version = "2.0.2" dependencies = [ "action_msgs", "builtin_interfaces", "rosidl_runtime_rs", + "service_msgs", "unique_identifier_msgs", ] @@ -586,6 +881,22 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -594,11 +905,17 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unique_identifier_msgs" -version = "2.2.1" +version = "2.5.0" dependencies = [ "rosidl_runtime_rs", ] +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + [[package]] name = "walkdir" version = "2.5.0" @@ -618,6 +935,87 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi-util" version = "0.1.9" @@ -775,8 +1173,20 @@ dependencies = [ [[patch.unused]] name = "rcl_interfaces" -version = "1.2.1" +version = "2.0.2" + +[[patch.unused]] +name = "rclrs" +version = "0.4.1" + +[[patch.unused]] +name = "rclrs_example_msgs" +version = "0.4.1" [[patch.unused]] name = "rosgraph_msgs" -version = "1.2.1" +version = "2.0.2" + +[[patch.unused]] +name = "std_msgs" +version = "5.3.6" diff --git a/rclrs/src/clock.rs b/rclrs/src/clock.rs index 992cd4b44..8dd543455 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<Mutex<rcl_clock_t>> { + &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 366e499b8..f5584fa27 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 dd01d0605..1a0c67224 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,70 @@ impl NodeState { ) } + /// Create a [`Timer`] with a repeating callback. + /// + /// See also: + /// * [`Self::create_timer_oneshot`] + /// * [`Self::create_timer_inert`] + pub fn create_timer_repeating<'a, Args>( + &self, + options: impl IntoTimerOptions<'a>, + callback: impl IntoNodeTimerRepeatingCallback<Args>, + ) -> Result<Timer, RclrsError> { + 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 + /// [`Timer::set_repeating`] or [`Timer::set_oneshot`] or else nothing will happen + /// the following times that the `Timer` elapses. + /// + /// See also: + /// * [`Self::create_timer_repeating`] + /// * [`Self::create_time_inert`] + pub fn create_timer_oneshot<'a, Args>( + &self, + options: impl IntoTimerOptions<'a>, + callback: impl IntoNodeTimerOneshotCallback<Args>, + ) -> Result<Timer, RclrsError> { + 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 [`Timer::set_callback`] or a related method. + /// + /// See also: + /// * [`Self::create_timer_repeating`] + /// * [`Self::create_timer_oneshot`] + pub fn create_timer_inert<'a>( + &self, + options: impl IntoTimerOptions<'a>, + ) -> Result<Timer, RclrsError> { + 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, + options: impl IntoTimerOptions<'a>, + callback: AnyTimerCallback<Node>, + ) -> Result<Timer, RclrsError> { + let options = options.into_timer_options(); + let clock = options.clock.as_clock(self); + TimerState::create( + options.period, + clock, + callback, + self.commands.async_worker_commands(), + &self.handle.context_handle, + ) + } + /// 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/timer.rs b/rclrs/src/timer.rs new file mode 100644 index 000000000..934060fd1 --- /dev/null +++ b/rclrs/src/timer.rs @@ -0,0 +1,777 @@ +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`] +/// - [`NodeState::create_timer_oneshot`] +/// - [`NodeState::create_timer_inert`] +/// +/// Timers can also be created by a [`Worker`], in which case they can access the worker's payload: +/// - [`Worker::create_timer_repeating`] +/// - [`Worker::create_timer_oneshot`] +/// - [`Worker::create_timer_inert`] +/// +/// The API of timers is given by [`TimerState`]. +/// +/// [1]: crate::Executor::spin +pub type Timer = Arc<TimerState<Node>>; + +/// A [`Timer`] that runs on a [`Worker`]. +/// +/// Create a worker timer using [`Worker::create_timer_repeating`], +/// [`Worker::create_timer_oneshot`], or [`Worker::create_timer_inert`]. +pub type WorkerTimer<Payload> = Arc<TimerState<Worker<Payload>>>; + +/// 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<Scope: WorkScope> { + pub(crate) handle: Arc<TimerHandle>, + /// The callback function that runs when the timer is due. + callback: Mutex<Option<AnyTimerCallback<Scope>>>, + /// What was the last time lapse between calls to this timer + last_elapse: Mutex<Duration>, + /// We use Mutex<Option<>> here because we need to construct the TimerState object + /// before we can get the lifecycle handle. + #[allow(unused)] + lifecycle: Mutex<Option<WaitableLifecycle>>, + _ignore: std::marker::PhantomData<Scope>, +} + +impl<Scope: WorkScope> TimerState<Scope> { + /// Gets the period of the timer + pub fn get_timer_period(&self) -> Result<Duration, RclrsError> { + let mut timer_period_ns = 0; + unsafe { + let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + rcl_timer_get_period(&*rcl_timer, &mut timer_period_ns) + } + .ok()?; + + rcl_duration(timer_period_ns) + } + + /// Cancels the timer, stopping the execution of the callback + pub fn cancel(&self) -> Result<(), RclrsError> { + let mut rcl_timer = self.handle.rcl_timer.lock().unwrap(); + let cancel_result = unsafe { rcl_timer_cancel(&mut *rcl_timer) }.ok()?; + Ok(cancel_result) + } + + /// Checks whether the timer is canceled or not + pub fn is_canceled(&self) -> Result<bool, RclrsError> { + let mut is_canceled = false; + unsafe { + let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + rcl_timer_is_canceled(&*rcl_timer, &mut is_canceled) + } + .ok()?; + Ok(is_canceled) + } + + /// Get the last time lapse between calls to the timer. + /// + /// This is different from [`Self::time_since_last_call`] because it remains + /// constant between calls to the Timer. + /// + /// It keeps track of the what the value of [`Self::time_since_last_call`] + /// was immediately before the most recent call to the callback. This will + /// be [`Duration::ZERO`] if the `Timer` has never been triggered. + pub fn last_elapse(&self) -> Duration { + *self.last_elapse.lock().unwrap() + } + + /// Retrieves the time since the last call to the callback + pub fn time_since_last_call(&self) -> Result<Duration, RclrsError> { + let mut time_value_ns: i64 = 0; + unsafe { + let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + rcl_timer_get_time_since_last_call(&*rcl_timer, &mut time_value_ns) + } + .ok()?; + + rcl_duration(time_value_ns) + } + + /// Retrieves the time until the next call of the callback + pub fn time_until_next_call(&self) -> Result<Duration, RclrsError> { + let mut time_value_ns: i64 = 0; + unsafe { + let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + rcl_timer_get_time_until_next_call(&*rcl_timer, &mut time_value_ns) + } + .ok()?; + + rcl_duration(time_value_ns) + } + + /// Resets the timer. + pub fn reset(&self) -> Result<(), RclrsError> { + let mut rcl_timer = self.handle.rcl_timer.lock().unwrap(); + unsafe { rcl_timer_reset(&mut *rcl_timer) }.ok() + } + + /// Checks if the timer is ready (not canceled) + pub fn is_ready(&self) -> Result<bool, RclrsError> { + let is_ready = unsafe { + let mut is_ready: bool = false; + let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + rcl_timer_is_ready(&*rcl_timer, &mut is_ready).ok()?; + is_ready + }; + + Ok(is_ready) + } + + /// Get the clock that this timer runs on. + pub fn clock(&self) -> &Clock { + &self.handle.clock + } + + /// Set a new callback for the timer. This will return whatever callback + /// was already present unless you are calling the function from inside of + /// the timer's callback, in which case you will receive [`None`]. + /// + /// See also: + /// * [`Self::set_repeating`] + /// * [`Self::set_oneshot`] + /// * [`Self::set_inert`]. + pub fn set_callback( + &self, + callback: AnyTimerCallback<Scope>, + ) -> Option<AnyTimerCallback<Scope>> { + self.callback.lock().unwrap().replace(callback) + } + + /// Remove the callback from the timer. + /// + /// This does not cancel the timer; it will continue to wake up and be + /// triggered at its regular period. However, nothing will happen when the + /// timer is triggered until you give a new callback to the timer. + /// + /// You can give the timer a new callback at any time by calling: + /// * [`Self::set_repeating`] + /// * [`Self::set_oneshot`] + pub fn set_inert(&self) -> Option<AnyTimerCallback<Scope>> { + self.set_callback(AnyTimerCallback::Inert) + } + + /// Creates a new timer. Users should call one of [`Node::create_timer`], + /// [`Node::create_timer_repeating`], [`Node::create_timer_oneshot`], or + /// [`Node::create_timer_inert`]. + pub(crate) fn create<'a>( + period: Duration, + clock: Clock, + callback: AnyTimerCallback<Scope>, + commands: &Arc<WorkerCommands>, + context: &ContextHandle, + ) -> Result<Arc<Self>, RclrsError> { + let period = period.as_nanos() as i64; + + // Callbacks will be handled at the rclrs layer. + let rcl_timer_callback: rcl_timer_callback_t = None; + + let rcl_timer = Arc::new(Mutex::new( + // SAFETY: Zero-initializing a timer is always safe + unsafe { rcl_get_zero_initialized_timer() }, + )); + + unsafe { + let mut rcl_clock = clock.get_rcl_clock().lock().unwrap(); + let mut rcl_context = context.rcl_context.lock().unwrap(); + + // SAFETY: Getting a default value is always safe. + let allocator = rcutils_get_default_allocator(); + + let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); + // SAFETY: We lock the lifecycle mutex since rcl_timer_init is not + // thread-safe. + rcl_timer_init( + &mut *rcl_timer.lock().unwrap(), + &mut *rcl_clock, + &mut *rcl_context, + period, + rcl_timer_callback, + allocator, + ) + } + .ok()?; + + let timer = Arc::new(TimerState { + handle: Arc::new(TimerHandle { rcl_timer, clock }), + callback: Mutex::new(Some(callback)), + last_elapse: Mutex::new(Duration::ZERO), + lifecycle: Mutex::default(), + _ignore: Default::default(), + }); + + let (waitable, lifecycle) = Waitable::new( + Box::new(TimerExecutable::<Scope> { + timer: Arc::downgrade(&timer), + handle: Arc::clone(&timer.handle), + }), + Some(Arc::clone(commands.get_guard_condition())), + ); + + *timer.lifecycle.lock().unwrap() = Some(lifecycle); + + commands.add_to_wait_set(waitable); + + Ok(timer) + } + + /// Force the timer to be called, even if it is not ready to be triggered yet. + /// We could consider making this public, but the behavior may confuse users. + fn call(self: &Arc<Self>, any_payload: &mut dyn Any) -> Result<(), RclrsError> { + // Keep track of the time elapsed since the last call. We need to run + // this before we trigger rcl_call. + let last_elapse = self.time_since_last_call().unwrap_or(Duration::ZERO); + *self.last_elapse.lock().unwrap() = last_elapse; + + if let Err(err) = self.rcl_call() { + log_error!("timer", "Unable to call timer: {err:?}",); + } + + let Some(callback) = self.callback.lock().unwrap().take() else { + log_error!( + "timer".once(), + "Timer is missing its callback information. This should not \ + be possible, please report it to the maintainers of rclrs.", + ); + return Ok(()); + }; + + let Some(payload) = any_payload.downcast_mut::<Scope::Payload>() else { + return Err(RclrsError::InvalidPayload { + expected: std::any::TypeId::of::<Scope::Payload>(), + received: (*any_payload).type_id(), + }); + }; + + match callback { + AnyTimerCallback::Repeating(mut callback) => { + callback(payload, self); + self.restore_callback(AnyTimerCallback::Repeating(callback).into()); + } + AnyTimerCallback::OneShot(callback) => { + callback(payload, self); + self.restore_callback(AnyTimerCallback::Inert); + } + AnyTimerCallback::Inert => { + self.restore_callback(AnyTimerCallback::Inert); + } + } + + Ok(()) + } + + /// Updates the state of the rcl_timer to know that it has been called. This + /// should only be called by [`Self::call`]. + /// + /// The callback held by the rcl_timer is null because we store the callback + /// in the [`Timer`] struct. This means there are no side-effects to this + /// except to keep track of when the timer has been called. + fn rcl_call(&self) -> Result<(), RclrsError> { + let mut rcl_timer = self.handle.rcl_timer.lock().unwrap(); + unsafe { rcl_timer_call(&mut *rcl_timer) }.ok() + } + + /// Used by [`Timer::execute`] to restore the state of the callback if and + /// only if the user has not already set a new callback. + fn restore_callback(&self, callback: AnyTimerCallback<Scope>) { + let mut self_callback = self.callback.lock().unwrap(); + if self_callback.is_none() { + *self_callback = Some(callback); + } + } +} + +impl TimerState<Node> { + /// Set a repeating callback for this timer. + /// + /// See also: + /// * [`Self::set_oneshot`] + /// * [`Self::set_inert`] + pub fn set_repeating<Args>( + &self, + f: impl IntoNodeTimerRepeatingCallback<Args>, + ) -> Option<AnyTimerCallback<Node>> { + self.set_callback(f.into_node_timer_repeating_callback()) + } + + /// Set a one-shot callback for the timer. + /// + /// The next time the timer is triggered, the callback will be set to + /// [`AnyTimerCallback::Inert`] after this callback is triggered. To keep the + /// timer useful, you can reset the Timer callback at any time, including + /// inside the one-shot callback itself. + /// + /// See also: + /// * [`Self::set_repeating`] + /// * [`Self::set_inert`] + pub fn set_oneshot<Args>( + &self, + f: impl IntoNodeTimerOneshotCallback<Args>, + ) -> Option<AnyTimerCallback<Node>> { + self.set_callback(f.into_node_timer_oneshot_callback()) + } +} + +impl<Payload: 'static + Send + Sync> TimerState<Worker<Payload>> { + /// Set a repeating callback for this worker timer. + /// + /// See also: + /// * [`Self::set_worker_oneshot`] + /// * [`Self::set_inert`] + pub fn set_worker_repeating<Args>( + &self, + f: impl IntoWorkerTimerRepeatingCallback<Worker<Payload>, Args>, + ) -> Option<AnyTimerCallback<Worker<Payload>>> { + self.set_callback(f.into_worker_timer_repeating_callback()) + } + + /// Set a one-shot callback for the worker timer. + /// + /// The next time the timer is triggered, the callback will be set to + /// [`AnyTimerCallback::Inert`] after this callback is triggered. To keep the + /// timer useful, you can reset the Timer callback at any time, including + /// inside the one-shot callback itself. + /// + /// See also: + /// * [`Self::set_worker_repeating`] + /// * [`Self::set_inert`] + pub fn set_worker_oneshot<Args>( + &self, + f: impl IntoWorkerTimerOneshotCallback<Worker<Payload>, Args>, + ) -> Option<AnyTimerCallback<Worker<Payload>>> { + self.set_callback(f.into_worker_timer_oneshot_callback()) + } +} + +struct TimerExecutable<Scope: WorkScope> { + timer: Weak<TimerState<Scope>>, + handle: Arc<TimerHandle>, +} + +impl<Scope: WorkScope> RclPrimitive for TimerExecutable<Scope> { + unsafe fn execute(&mut self, payload: &mut dyn Any) -> Result<(), RclrsError> { + if let Some(timer) = self.timer.upgrade() { + if timer.is_ready()? { + timer.call(payload)?; + } + } + + Ok(()) + } + + fn kind(&self) -> RclPrimitiveKind { + RclPrimitiveKind::Timer + } + + fn handle(&self) -> RclPrimitiveHandle { + RclPrimitiveHandle::Timer(self.handle.rcl_timer.lock().unwrap()) + } +} + +impl<Scope: WorkScope> PartialEq for TimerState<Scope> { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.handle.rcl_timer, &other.handle.rcl_timer) + } +} + +fn rcl_duration(duration_value_ns: i64) -> Result<Duration, RclrsError> { + if duration_value_ns < 0 { + Err(RclrsError::NegativeDuration(duration_value_ns)) + } else { + Ok(Duration::from_nanos(duration_value_ns as u64)) + } +} + +/// Manage the lifecycle of an `rcl_timer_t`, including managing its dependency +/// on `rcl_clock_t` by ensuring that this dependency are [dropped after][1] +/// the `rcl_timer_t`. +/// +/// [1]: <https://doc.rust-lang.org/reference/destructors.html> +pub(crate) struct TimerHandle { + pub(crate) rcl_timer: Arc<Mutex<rcl_timer_t>>, + clock: Clock, +} + +/// 'Drop' trait implementation to be able to release the resources +impl Drop for TimerHandle { + fn drop(&mut self) { + let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); + // SAFETY: The lifecycle mutex is locked and the clock for the timer + // must still be valid because TimerHandle keeps it alive. + unsafe { rcl_timer_fini(&mut *self.rcl_timer.lock().unwrap()) }; + } +} + +// SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread +// they are running in. Therefore, this type can be safely sent to another thread. +unsafe impl Send for rcl_timer_t {} + +#[cfg(test)] +mod tests { + use super::TimerExecutable; + use crate::*; + use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Duration, + }; + + #[test] + fn traits() { + use crate::test_helpers::*; + + assert_send::<TimerState<Node>>(); + assert_sync::<TimerState<Node>>(); + } + + #[test] + fn test_new_with_system_clock() { + let executor = Context::default().create_basic_executor(); + let result = TimerState::<Node>::create( + Duration::from_millis(1), + Clock::system(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ); + assert!(result.is_ok()); + } + + #[test] + fn test_new_with_steady_clock() { + let executor = Context::default().create_basic_executor(); + let result = TimerState::<Node>::create( + Duration::from_millis(1), + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ); + assert!(result.is_ok()); + } + + #[test] + fn test_new_with_source_clock() { + let (clock, source) = Clock::with_source(); + // No manual time set, it should default to 0 + assert_eq!(clock.now().nsec, 0); + let set_time = 1234i64; + source.set_ros_time_override(set_time); + + // ROS time is set, should return the value that was set + assert_eq!(clock.now().nsec, set_time); + + let executor = Context::default().create_basic_executor(); + let result = TimerState::<Node>::create( + Duration::from_millis(1), + clock, + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ); + assert!(result.is_ok()); + } + + #[test] + fn test_get_period() { + let period = Duration::from_millis(1); + + let executor = Context::default().create_basic_executor(); + + let result = TimerState::<Node>::create( + period, + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ); + + let timer = result.unwrap(); + let timer_period = timer.get_timer_period().unwrap(); + assert_eq!(timer_period, period); + } + + #[test] + fn test_cancel() { + let executor = Context::default().create_basic_executor(); + + let result = TimerState::<Node>::create( + Duration::from_millis(1), + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ); + + let timer = result.unwrap(); + assert!(!timer.is_canceled().unwrap()); + timer.cancel().unwrap(); + assert!(timer.is_canceled().unwrap()); + } + + #[test] + fn test_time_since_last_call_before_first_event() { + let executor = Context::default().create_basic_executor(); + + let result = TimerState::<Node>::create( + Duration::from_millis(2), + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ); + let timer = result.unwrap(); + + let sleep_period = Duration::from_millis(1); + thread::sleep(sleep_period); + + let time_since_last_call = timer.time_since_last_call().unwrap(); + assert!( + time_since_last_call >= sleep_period, + "time_since_last_call: {:?} vs sleep period: {:?}", + time_since_last_call, + sleep_period, + ); + } + + #[test] + fn test_time_until_next_call_before_first_event() { + let executor = Context::default().create_basic_executor(); + let period = Duration::from_millis(2); + + let result = TimerState::<Node>::create( + period, + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ); + let timer = result.unwrap(); + + let time_until_next_call = timer.time_until_next_call().unwrap(); + assert!( + time_until_next_call <= period, + "time_until_next_call: {:?} vs period: {:?}", + time_until_next_call, + period, + ); + } + + #[test] + fn test_reset() { + let executor = Context::default().create_basic_executor(); + let period = Duration::from_millis(2); + let timer = TimerState::<Node>::create( + period, + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ) + .unwrap(); + + // The unwrap will panic if the remaining time is negative + timer.time_until_next_call().unwrap(); + + // Sleep until we're past the timer period + thread::sleep(Duration::from_millis(3)); + + // Now the time until next call should give an error + assert!(matches!( + timer.time_until_next_call(), + Err(RclrsError::NegativeDuration(_)) + )); + + // Reset the timer so its interval begins again + assert!(timer.reset().is_ok()); + + // The unwrap will panic if the remaining time is negative + timer.time_until_next_call().unwrap(); + } + + #[test] + fn test_call() { + let executor = Context::default().create_basic_executor(); + let timer = TimerState::<Node>::create( + Duration::from_millis(1), + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ) + .unwrap(); + + // The unwrap will panic if the remaining time is negative + timer.time_until_next_call().unwrap(); + + // Sleep until we're past the timer period + thread::sleep(Duration::from_micros(1500)); + + // Now the time until the next call should give an error + assert!(matches!( + timer.time_until_next_call(), + Err(RclrsError::NegativeDuration(_)) + )); + + // The unwrap will panic if anything went wrong with the call + timer.call(&mut ()).unwrap(); + + // The unwrap will panic if the remaining time is negative + timer.time_until_next_call().unwrap(); + } + + #[test] + fn test_is_ready() { + let executor = Context::default().create_basic_executor(); + let timer = TimerState::<Node>::create( + Duration::from_millis(1), + Clock::steady(), + (|| {}).into_node_timer_repeating_callback(), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ) + .unwrap(); + + assert!(!timer.is_ready().unwrap()); + + // Sleep until the period has elapsed + thread::sleep(Duration::from_micros(1100)); + + assert!(timer.is_ready().unwrap()); + } + + #[test] + fn test_callback() { + let clock = Clock::steady(); + let initial_time = clock.now(); + + let executor = Context::default().create_basic_executor(); + let executed = Arc::new(AtomicBool::new(false)); + + let timer = TimerState::<Node>::create( + Duration::from_millis(1), + clock, + create_timer_callback_for_testing(initial_time, Arc::clone(&executed)), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ) + .unwrap(); + + timer.call(&mut ()).unwrap(); + assert!(executed.load(Ordering::Acquire)); + } + + #[test] + fn test_execute_when_is_not_ready() { + let clock = Clock::steady(); + let initial_time = clock.now(); + + let executor = Context::default().create_basic_executor(); + let executed = Arc::new(AtomicBool::new(false)); + + let timer = TimerState::<Node>::create( + Duration::from_millis(1), + clock, + create_timer_callback_for_testing(initial_time, Arc::clone(&executed)), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ) + .unwrap(); + + let mut executable = TimerExecutable { + timer: Arc::downgrade(&timer), + handle: Arc::clone(&timer.handle), + }; + + // SAFETY: Node timers expect a payload of () + unsafe { + executable.execute(&mut ()).unwrap(); + } + assert!(!executed.load(Ordering::Acquire)); + } + + #[test] + fn test_execute_when_is_ready() { + let clock = Clock::steady(); + let initial_time = clock.now(); + + let executor = Context::default().create_basic_executor(); + let executed = Arc::new(AtomicBool::new(false)); + + let timer = TimerState::<Node>::create( + Duration::from_millis(1), + clock, + create_timer_callback_for_testing(initial_time, Arc::clone(&executed)), + executor.commands().async_worker_commands(), + &executor.commands().context().handle, + ) + .unwrap(); + + let mut executable = TimerExecutable { + timer: Arc::downgrade(&timer), + handle: Arc::clone(&timer.handle), + }; + + thread::sleep(Duration::from_millis(2)); + + // SAFETY: Node timers expect a payload of () + unsafe { + executable.execute(&mut ()).unwrap(); + } + assert!(executed.load(Ordering::Acquire)); + } + + fn create_timer_callback_for_testing( + initial_time: Time, + executed: Arc<AtomicBool>, + ) -> AnyTimerCallback<Node> { + (move |t: Time| { + assert!(t + .compare_with(&initial_time, |t, initial| t >= initial) + .unwrap()); + executed.store(true, Ordering::Release); + }) + .into_node_timer_oneshot_callback() + } +} diff --git a/rclrs/src/timer/any_timer_callback.rs b/rclrs/src/timer/any_timer_callback.rs new file mode 100644 index 000000000..3c4493d72 --- /dev/null +++ b/rclrs/src/timer/any_timer_callback.rs @@ -0,0 +1,15 @@ +use crate::{TimerState, WorkScope}; +use std::sync::Arc; + +/// A callback that can be triggered when a timer elapses. +pub enum AnyTimerCallback<Scope: WorkScope> { + /// This callback will be triggered repeatedly, each time the period of the + /// timer elapses. + Repeating(Box<dyn FnMut(&mut Scope::Payload, &Arc<TimerState<Scope>>) + Send>), + /// This callback will be triggered exactly once, the first time the period + /// of the timer elapses. + OneShot(Box<dyn FnOnce(&mut Scope::Payload, &Arc<TimerState<Scope>>) + Send>), + /// Do nothing when the timer elapses. This can be replaced later so that + /// the timer does something. + Inert, +} diff --git a/rclrs/src/timer/into_node_timer_callback.rs b/rclrs/src/timer/into_node_timer_callback.rs new file mode 100644 index 000000000..5ef3772fb --- /dev/null +++ b/rclrs/src/timer/into_node_timer_callback.rs @@ -0,0 +1,67 @@ +use crate::{AnyTimerCallback, Node, Time, Timer}; + +/// This trait is used to create timer callbacks for repeating timers in a Node. +pub trait IntoNodeTimerRepeatingCallback<Args>: 'static + Send { + /// Convert a suitable object into a repeating timer callback for the node scope + fn into_node_timer_repeating_callback(self) -> AnyTimerCallback<Node>; +} + +impl<Func> IntoNodeTimerRepeatingCallback<()> for Func +where + Func: FnMut() + 'static + Send, +{ + fn into_node_timer_repeating_callback(mut self) -> AnyTimerCallback<Node> { + AnyTimerCallback::Repeating(Box::new(move |_, _| self())).into() + } +} + +impl<Func> IntoNodeTimerRepeatingCallback<Timer> for Func +where + Func: FnMut(&Timer) + 'static + Send, +{ + fn into_node_timer_repeating_callback(mut self) -> AnyTimerCallback<Node> { + AnyTimerCallback::Repeating(Box::new(move |_, t| self(t))).into() + } +} + +impl<Func> IntoNodeTimerRepeatingCallback<Time> for Func +where + Func: FnMut(Time) + 'static + Send, +{ + fn into_node_timer_repeating_callback(mut self) -> AnyTimerCallback<Node> { + AnyTimerCallback::Repeating(Box::new(move |_, t| self(t.handle.clock.now()))).into() + } +} + +/// This trait is used to create timer callbacks for one-shot timers in a Node. +pub trait IntoNodeTimerOneshotCallback<Args>: 'static + Send { + /// Convert a suitable object into a one-shot timer callback for a node scope + fn into_node_timer_oneshot_callback(self) -> AnyTimerCallback<Node>; +} + +impl<Func> IntoNodeTimerOneshotCallback<()> for Func +where + Func: FnOnce() + 'static + Send, +{ + fn into_node_timer_oneshot_callback(self) -> AnyTimerCallback<Node> { + AnyTimerCallback::OneShot(Box::new(move |_, _| self())).into() + } +} + +impl<Func> IntoNodeTimerOneshotCallback<Timer> for Func +where + Func: FnOnce(&Timer) + 'static + Send, +{ + fn into_node_timer_oneshot_callback(self) -> AnyTimerCallback<Node> { + AnyTimerCallback::OneShot(Box::new(move |_, t| self(t))).into() + } +} + +impl<Func> IntoNodeTimerOneshotCallback<Time> for Func +where + Func: FnOnce(Time) + 'static + Send, +{ + fn into_node_timer_oneshot_callback(self) -> AnyTimerCallback<Node> { + AnyTimerCallback::OneShot(Box::new(move |_, t| self(t.handle.clock.now()))).into() + } +} diff --git a/rclrs/src/timer/into_worker_timer_callback.rs b/rclrs/src/timer/into_worker_timer_callback.rs new file mode 100644 index 000000000..bdb752411 --- /dev/null +++ b/rclrs/src/timer/into_worker_timer_callback.rs @@ -0,0 +1,95 @@ +use crate::{AnyTimerCallback, Time, TimerState, WorkScope}; +use std::sync::Arc; + +/// This trait is used to create timer callbacks for repeating timers in a Worker. +pub trait IntoWorkerTimerRepeatingCallback<Scope: WorkScope, Args>: 'static + Send { + /// Convert a suitable object into a repeating timer callback for a worker scope + fn into_worker_timer_repeating_callback(self) -> AnyTimerCallback<Scope>; +} + +impl<Scope: WorkScope, Func> IntoWorkerTimerRepeatingCallback<Scope, ()> for Func +where + Func: FnMut() + 'static + Send, +{ + fn into_worker_timer_repeating_callback(mut self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::Repeating(Box::new(move |_, _| self())).into() + } +} + +impl<Scope: WorkScope, Func> IntoWorkerTimerRepeatingCallback<Scope, (Scope::Payload,)> for Func +where + Func: FnMut(&mut Scope::Payload) + 'static + Send, +{ + fn into_worker_timer_repeating_callback(mut self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::Repeating(Box::new(move |payload, _| self(payload))).into() + } +} + +impl<Scope: WorkScope, Func> + IntoWorkerTimerRepeatingCallback<Scope, (Scope::Payload, Arc<TimerState<Scope>>)> for Func +where + Func: FnMut(&mut Scope::Payload, &Arc<TimerState<Scope>>) + 'static + Send, +{ + fn into_worker_timer_repeating_callback(self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::Repeating(Box::new(self)).into() + } +} + +impl<Scope: WorkScope, Func> IntoWorkerTimerRepeatingCallback<Scope, (Scope::Payload, Time)> + for Func +where + Func: FnMut(&mut Scope::Payload, Time) + 'static + Send, +{ + fn into_worker_timer_repeating_callback(mut self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::Repeating(Box::new(move |payload, t| { + self(payload, t.handle.clock.now()) + })) + .into() + } +} + +/// This trait is used to create timer callbacks for one-shot timers in a Worker. +pub trait IntoWorkerTimerOneshotCallback<Scope: WorkScope, Args>: 'static + Send { + /// Convert a suitable object into a one-shot timer callback for a worker scope + fn into_worker_timer_oneshot_callback(self) -> AnyTimerCallback<Scope>; +} + +impl<Scope: WorkScope, Func> IntoWorkerTimerOneshotCallback<Scope, ()> for Func +where + Func: FnOnce() + 'static + Send, +{ + fn into_worker_timer_oneshot_callback(self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::OneShot(Box::new(move |_, _| self())).into() + } +} + +impl<Scope: WorkScope, Func> IntoWorkerTimerOneshotCallback<Scope, (Scope::Payload,)> for Func +where + Func: FnOnce(&mut Scope::Payload) + 'static + Send, +{ + fn into_worker_timer_oneshot_callback(self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::OneShot(Box::new(move |payload, _| self(payload))).into() + } +} + +impl<Scope: WorkScope, Func> + IntoWorkerTimerOneshotCallback<Scope, (Scope::Payload, Arc<TimerState<Scope>>)> for Func +where + Func: FnOnce(&mut Scope::Payload, &Arc<TimerState<Scope>>) + 'static + Send, +{ + fn into_worker_timer_oneshot_callback(self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::OneShot(Box::new(self)).into() + } +} + +impl<Scope: WorkScope, Func> IntoWorkerTimerOneshotCallback<Scope, (Scope::Payload, Time)> for Func +where + Func: FnMut(&mut Scope::Payload, Time) + 'static + Send, +{ + fn into_worker_timer_oneshot_callback(mut self) -> AnyTimerCallback<Scope> { + AnyTimerCallback::OneShot(Box::new(move |payload, t| { + self(payload, t.handle.clock.now()) + })) + .into() + } +} diff --git a/rclrs/src/timer/timer_options.rs b/rclrs/src/timer/timer_options.rs new file mode 100644 index 000000000..81bd34164 --- /dev/null +++ b/rclrs/src/timer/timer_options.rs @@ -0,0 +1,100 @@ +use std::time::Duration; + +use crate::{Clock, NodeState}; + +/// Options for creating a [`Timer`][crate::Timer]. +/// +/// The trait [`IntoTimerOptions`] can implicitly convert a single [`Duration`] +/// into `TimerOptions`. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub struct TimerOptions<'a> { + /// The period of the timer's interval + pub period: Duration, + /// The clock that the timer will reference for its intervals + pub clock: TimerClock<'a>, +} + +impl TimerOptions<'_> { + /// Make a new timer with a certain interval period, with all other options + /// as default. + pub fn new(period: Duration) -> Self { + Self { + period, + clock: TimerClock::default(), + } + } +} + +/// Trait to implicitly convert a suitable object into [`TimerOptions`]. +pub trait IntoTimerOptions<'a>: Sized { + /// Convert a suitable object into [`TimerOptions`]. This can be used on + /// [`Duration`] or [`TimerOptions`] itself. + fn into_timer_options(self) -> TimerOptions<'a>; + + /// Use [`std::time::Instant`] for the timer. This the default so you + /// shouldn't generally have to call this. + fn steady_time(self) -> TimerOptions<'a> { + let mut options = self.into_timer_options(); + options.clock = TimerClock::SteadyTime; + options + } + + /// Use [`std::time::SystemTime`] for the timer + fn system_time(self) -> TimerOptions<'a> { + let mut options = self.into_timer_options(); + options.clock = TimerClock::SystemTime; + options + } + + /// Use the node clock for the timer + fn node_time(self) -> TimerOptions<'a> { + let mut options = self.into_timer_options(); + options.clock = TimerClock::NodeTime; + options + } + + /// Use a specific clock for the + fn clock(self, clock: &'a Clock) -> TimerOptions<'a> { + let mut options = self.into_timer_options(); + options.clock = TimerClock::Clock(clock); + options + } +} + +/// This parameter can specify a type of clock for a timer to use +#[derive(Debug, Default, Clone, Copy)] +pub enum TimerClock<'a> { + /// Use [`std::time::Instant`] for tracking time + #[default] + SteadyTime, + /// Use [`std::time::SystemTime`] for tracking time + SystemTime, + /// Use the parent node's clock for tracking time + NodeTime, + /// Use a specific clock for tracking time + Clock(&'a Clock), +} + +impl TimerClock<'_> { + pub(crate) fn as_clock(&self, node: &NodeState) -> Clock { + match self { + TimerClock::SteadyTime => Clock::steady(), + TimerClock::SystemTime => Clock::system(), + TimerClock::NodeTime => node.get_clock(), + TimerClock::Clock(clock) => (*clock).clone(), + } + } +} + +impl<'a> IntoTimerOptions<'a> for TimerOptions<'a> { + fn into_timer_options(self) -> TimerOptions<'a> { + self + } +} + +impl<'a> IntoTimerOptions<'a> for Duration { + fn into_timer_options(self) -> TimerOptions<'a> { + TimerOptions::new(self) + } +} diff --git a/rclrs/src/worker.rs b/rclrs/src/worker.rs index f89da20fc..3c4af1d87 100644 --- a/rclrs/src/worker.rs +++ b/rclrs/src/worker.rs @@ -1,7 +1,9 @@ use crate::{ - log_fatal, IntoWorkerServiceCallback, IntoWorkerSubscriptionCallback, Node, Promise, - RclrsError, ServiceOptions, ServiceState, SubscriptionOptions, SubscriptionState, - WorkerCommands, WorkerService, WorkerSubscription, + log_fatal, AnyTimerCallback, IntoTimerOptions, IntoWorkerServiceCallback, + IntoWorkerSubscriptionCallback, IntoWorkerTimerOneshotCallback, + IntoWorkerTimerRepeatingCallback, Node, Promise, RclrsError, ServiceOptions, ServiceState, + SubscriptionOptions, SubscriptionState, TimerState, WorkerCommands, WorkerService, + WorkerSubscription, WorkerTimer, }; use futures::channel::oneshot; use rosidl_runtime_rs::{Message, Service as IdlService}; @@ -369,6 +371,65 @@ impl<Payload: 'static + Send + Sync> WorkerState<Payload> { ) } + /// Create a [`WorkerTimer`] with a repeating callback. + /// + /// See also: + /// * [`Self::create_timer_oneshot`] + /// * [`Self::create_timer_inert`] + pub fn create_timer_repeating<'a, Args>( + &self, + options: impl IntoTimerOptions<'a>, + callback: impl IntoWorkerTimerRepeatingCallback<Worker<Payload>, Args>, + ) -> Result<WorkerTimer<Payload>, RclrsError> { + self.create_timer(options, callback.into_worker_timer_repeating_callback()) + } + + /// Create a [`WorkerTimer`] whose callback will be triggered once after the + /// period of the timer has elapsed. After that you will need to use + /// [`WorkerTimer::set_worker_oneshot`] or [`WorkerTimer::set_worker_repeating`] + /// or else nothing will happen the following times that the `Timer` elapses. + /// + /// See also: + /// * [`Self::create_timer_repeating`] + /// * [`Self::create_time_inert`] + pub fn create_timer_oneshot<'a, Args>( + &self, + options: impl IntoTimerOptions<'a>, + callback: impl IntoWorkerTimerOneshotCallback<Worker<Payload>, Args>, + ) -> Result<WorkerTimer<Payload>, RclrsError> { + self.create_timer(options, callback.into_worker_timer_oneshot_callback()) + } + + /// Create a [`WorkerTimer`] without a callback. Nothing will happen when this + /// `WorkerTimer` elapses until you use [`WorkerTimer::set_worker_repeating`] + /// or [`WorkerTimer::set_worker_oneshot`]. + /// + /// See also: + /// * [`Self::create_timer_repeating`] + /// * [`Self::create_timer_oneshot`] + pub fn create_timer_inert<'a>( + &self, + options: impl IntoTimerOptions<'a>, + ) -> Result<WorkerTimer<Payload>, RclrsError> { + self.create_timer(options, AnyTimerCallback::Inert) + } + + fn create_timer<'a>( + &self, + options: impl IntoTimerOptions<'a>, + callback: AnyTimerCallback<Worker<Payload>>, + ) -> Result<WorkerTimer<Payload>, RclrsError> { + let options = options.into_timer_options(); + let clock = options.clock.as_clock(&*self.node); + TimerState::create( + options.period, + clock, + callback, + &self.commands, + &self.node.handle().context_handle, + ) + } + /// Used by [`Node`][crate::Node] to create a `WorkerState`. Users should /// call [`Node::create_worker`][crate::NodeState::create_worker] instead of /// this. From c9c2127b4fa90888b221444524d61fdb295d1a37 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 15:55:27 +0800 Subject: [PATCH 02/12] Update documentation of timers Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- examples/timer_demo/Cargo.lock | 973 ++++++++++++++++++ rclrs/src/node.rs | 152 ++- .../subscription/readonly_loaned_message.rs | 2 - rclrs/src/timer.rs | 26 +- rclrs/src/worker.rs | 134 ++- 5 files changed, 1269 insertions(+), 18 deletions(-) create mode 100644 examples/timer_demo/Cargo.lock diff --git a/examples/timer_demo/Cargo.lock b/examples/timer_demo/Cargo.lock new file mode 100644 index 000000000..c342f0f78 --- /dev/null +++ b/examples/timer_demo/Cargo.lock @@ -0,0 +1,973 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "action_msgs" +version = "2.0.2" +dependencies = [ + "builtin_interfaces", + "rosidl_runtime_rs", + "service_msgs", + "unique_identifier_msgs", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "builtin_interfaces" +version = "2.0.2" +dependencies = [ + "rosidl_runtime_rs", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + +[[package]] +name = "example_interfaces" +version = "0.12.0" +dependencies = [ + "action_msgs", + "builtin_interfaces", + "rosidl_runtime_rs", + "service_msgs", + "unique_identifier_msgs", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "value-bag", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "prettyplease" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rclrs" +version = "0.4.1" +dependencies = [ + "async-std", + "bindgen", + "cfg-if", + "futures", + "rosidl_runtime_rs", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rosidl_runtime_rs" +version = "0.4.1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "service_msgs" +version = "2.0.2" +dependencies = [ + "builtin_interfaces", + "rosidl_runtime_rs", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "timer_demo" +version = "0.1.0" +dependencies = [ + "example_interfaces", + "rclrs", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unique_identifier_msgs" +version = "2.5.0" +dependencies = [ + "rosidl_runtime_rs", +] + +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[patch.unused]] +name = "rcl_interfaces" +version = "2.0.2" + +[[patch.unused]] +name = "rclrs_example_msgs" +version = "0.4.1" + +[[patch.unused]] +name = "rosgraph_msgs" +version = "2.0.2" + +[[patch.unused]] +name = "std_msgs" +version = "5.3.6" + +[[patch.unused]] +name = "test_msgs" +version = "2.0.2" diff --git a/rclrs/src/node.rs b/rclrs/src/node.rs index 1a0c67224..777e38981 100644 --- a/rclrs/src/node.rs +++ b/rclrs/src/node.rs @@ -900,6 +900,110 @@ impl NodeState { /// 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: + /// - <code>[FnMut] ()</code> + /// - <code>[FnMut] ([Time][2])</code> + /// - <code>[FnMut] (&[Timer])</code> + /// + /// 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, options: impl IntoTimerOptions<'a>, @@ -910,12 +1014,51 @@ impl NodeState { /// Create a [`Timer`] whose callback will be triggered once after the period /// of the timer has elapsed. After that you will need to use - /// [`Timer::set_repeating`] or [`Timer::set_oneshot`] or else nothing will happen - /// the following times that the `Timer` elapses. + /// [`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_time_inert`] + /// * [`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: + /// - <code>[FnMut] ()</code> + /// - <code>[FnMut] ([Time][2])</code> + /// - <code>[FnMut] (&[Timer])</code> + /// + /// 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, options: impl IntoTimerOptions<'a>, @@ -925,7 +1068,8 @@ impl NodeState { } /// Create a [`Timer`] without a callback. Nothing will happen when this - /// `Timer` elapses until you use [`Timer::set_callback`] or a related method. + /// `Timer` elapses until you use [`TimerState::set_repeating`] or + /// [`TimerState::set_oneshot`]. /// /// See also: /// * [`Self::create_timer_repeating`] diff --git a/rclrs/src/subscription/readonly_loaned_message.rs b/rclrs/src/subscription/readonly_loaned_message.rs index 67ac3fd69..0ea73c1f2 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<T> where T: Message, diff --git a/rclrs/src/timer.rs b/rclrs/src/timer.rs index 934060fd1..a6cd23ef6 100644 --- a/rclrs/src/timer.rs +++ b/rclrs/src/timer.rs @@ -28,24 +28,34 @@ pub use into_worker_timer_callback::*; /// 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`] -/// - [`NodeState::create_timer_oneshot`] -/// - [`NodeState::create_timer_inert`] +/// - [`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: -/// - [`Worker::create_timer_repeating`] -/// - [`Worker::create_timer_oneshot`] -/// - [`Worker::create_timer_inert`] +/// - [`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<TimerState<Node>>; /// A [`Timer`] that runs on a [`Worker`]. /// -/// Create a worker timer using [`Worker::create_timer_repeating`], -/// [`Worker::create_timer_oneshot`], or [`Worker::create_timer_inert`]. +/// 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<Payload> = Arc<TimerState<Worker<Payload>>>; /// The inner state of a [`Timer`]. diff --git a/rclrs/src/worker.rs b/rclrs/src/worker.rs index 3c4af1d87..aa134b77a 100644 --- a/rclrs/src/worker.rs +++ b/rclrs/src/worker.rs @@ -373,9 +373,74 @@ impl<Payload: 'static + Send + Sync> WorkerState<Payload> { /// Create a [`WorkerTimer`] with a repeating callback. /// + /// Unlike timers created from a [`Node`], the callbacks for these timers + /// can have an additional argument to get a mutable borrow of the [`Worker`]'s + /// payload. This allows the timer callback to operate on state data that gets + /// shared with other callbacks. + /// /// See also: /// * [`Self::create_timer_oneshot`] /// * [`Self::create_timer_inert`] + /// + /// # Behavior + /// + /// While the callback of this timer is running, no other callbacks associated + /// with the [`Worker`] will be able to run. This is necessary to guarantee + /// that the mutable borrow of the payload is safe. + /// + /// 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 condier using [`std::thread::spawn`], or for async behaviors + /// you can capture an [`ExecutorCommands`][1] in your callback and use + /// [`ExecutorCommands::run`][2] to issue a task for the executor to run in + /// its async task pool. + /// + /// # Timer Options + /// + /// See [`NodeState::create_timer_repeating`][3] for examples of setting the + /// timer options. + /// + /// # Worker Timer Repeating Callbacks + /// + /// Worker Timer repeating callbacks support four signatures: + /// - <code>[FnMut] ()</code> + /// - <code>[FnMut] ( &mut Payload )</code> + /// - <code>[FnMut] ( &mut Payload, [Time][4] )</code> + /// - <code>[FnMut] ( &mut Payload, &[WorkerTimer]<Payload> )</code> + /// + /// You can choose to access the payload of the worker. You can additionally + /// 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 [`WorkerTimer`] + /// itself, that way if you need to access it from inside the callback, you + /// do not need to worry about capturing a [`Weak`] 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`] callback instead of [`FnMut`], use [`Self::create_timer_oneshot`]. + /// + /// ``` + /// # use rclrs::*; + /// # let executor = Context::default().create_basic_executor(); + /// # let node = executor.create_node("my_node").unwrap(); + /// use std::time::Duration; + /// + /// let worker = node.create_worker::<usize>(0); + /// let timer = worker.create_timer_repeating( + /// Duration::from_secs(1), + /// |count: &mut usize, time: Time| { + /// *count += 1; + /// println!("Drinking my {}th 🧉 at {:?}.", *count, time); + /// }, + /// )?; + /// # Ok::<(), RclrsError>(()) + /// ``` + /// + /// [1]: crate::ExecutorCommands + /// [2]: crate::ExecutorCommands::run + /// [3]: crate::NodeState::create_timer_repeating + /// [4]: crate::Time pub fn create_timer_repeating<'a, Args>( &self, options: impl IntoTimerOptions<'a>, @@ -386,12 +451,73 @@ impl<Payload: 'static + Send + Sync> WorkerState<Payload> { /// Create a [`WorkerTimer`] whose callback will be triggered once after the /// period of the timer has elapsed. After that you will need to use - /// [`WorkerTimer::set_worker_oneshot`] or [`WorkerTimer::set_worker_repeating`] + /// [`TimerState::set_worker_oneshot`] or [`TimerState::set_worker_repeating`] /// or else nothing will happen the following times that the `Timer` elapses. /// /// See also: /// * [`Self::create_timer_repeating`] - /// * [`Self::create_time_inert`] + /// * [`Self::create_timer_inert`] + /// + /// # Behavior + /// + /// While the callback of this timer is running, no other callbacks associated + /// with the [`Worker`] will be able to run. This is necessary to guarantee + /// that the mutable borrow of the payload is safe. + /// + /// 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 condier using `std::thread::spawn`, or for async behaviors + /// you can capture an [`ExecutorCommands`][1] in your callback and use + /// [`ExecutorCommands::run`][2] to issue a task for the executor to run in + /// its async task pool. + /// + /// # Timer Options + /// + /// See [`NodeSate::create_timer_repeating`][3] for examples of setting the + /// timer options. + /// + /// # Worker Timer Oneshot Callbacks + /// + /// Worker Timer oneshot callbacks support four signatures: + /// - <code>[FnOnce] ()</code> + /// - <code>[FnOnce] ( &mut Payload )</code> + /// - <code>[FnOnce] ( &mut Payload, [Time][4] )</code> + /// - <code>[FnOnce] ( &mut Payload, &[WorkerTimer]<Payload> )</code> + /// + /// You can choose to access the payload of the worker. You can additionally + /// 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 [`WorkerTimer`] + /// itself, that way if you need to access it from inside the callback, you + /// do not need to worry about capturing a [`Weak`] and then locking it. This + /// is useful if you need to change the callback of the timer from inside the + /// callback of the timer, which may be needed often for oneshot callbacks. + /// + /// The callback will only be triggered once. After that, this will effectively + /// be an [inert][5] timer. + /// + /// ``` + /// # use rclrs::*; + /// # let executor = Context::default().create_basic_executor(); + /// # let node = executor.create_node("my_node").unwrap(); + /// # let worker = node.create_worker::<()>(()); + /// use std::time::Duration; + /// + /// let timer = worker.create_timer_oneshot( + /// Duration::from_secs(1), + /// || { + /// println!("This will only fire once"); + /// } + /// )?; + /// # Ok::<(), RclrsError>(()) + /// ``` + /// + /// [1]: crate::ExecutorCommands + /// [2]: crate::ExecutorCommands::run + /// [3]: crate::NodeState::create_timer_repeating + /// [4]: crate::Time + /// [5]: Self::create_timer_inert pub fn create_timer_oneshot<'a, Args>( &self, options: impl IntoTimerOptions<'a>, @@ -401,8 +527,8 @@ impl<Payload: 'static + Send + Sync> WorkerState<Payload> { } /// Create a [`WorkerTimer`] without a callback. Nothing will happen when this - /// `WorkerTimer` elapses until you use [`WorkerTimer::set_worker_repeating`] - /// or [`WorkerTimer::set_worker_oneshot`]. + /// `WorkerTimer` elapses until you use [`TimerState::set_worker_repeating`] + /// or [`TimerState::set_worker_oneshot`]. /// /// See also: /// * [`Self::create_timer_repeating`] From 7ad4ece355169accafd07ef39cdf043eb18ebd1b Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 16:02:44 +0800 Subject: [PATCH 03/12] Fix compatibility with kilted and rolling Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- rclrs/src/timer.rs | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/rclrs/src/timer.rs b/rclrs/src/timer.rs index a6cd23ef6..b3db0b178 100644 --- a/rclrs/src/timer.rs +++ b/rclrs/src/timer.rs @@ -227,16 +227,41 @@ impl<Scope: WorkScope> TimerState<Scope> { let allocator = rcutils_get_default_allocator(); let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); - // SAFETY: We lock the lifecycle mutex since rcl_timer_init is not - // thread-safe. - rcl_timer_init( - &mut *rcl_timer.lock().unwrap(), - &mut *rcl_clock, - &mut *rcl_context, - period, - rcl_timer_callback, - allocator, - ) + + // The API for initializing timers changed with the kilted releaase. + #[cfg(any(ros_distro = "humble", ros_distro = "jazzy"))] + { + // SAFETY: We lock the lifecycle mutex since rcl_timer_init is not + // thread-safe. + rcl_timer_init( + &mut *rcl_timer.lock().unwrap(), + &mut *rcl_clock, + &mut *rcl_context, + period, + rcl_timer_callback, + allocator, + ) + } + + // The API for initializing timers changed with the kilted releaase. + // This new API allows you to opt out of automatically starting the + // timer as soon as it is created. We could consider exposing this + // capability to the user, but for now we are just telling it to + // immediately start the timer. + #[cfg(not(any(ros_distro = "humble", ros_distro = "jazzy")))] + { + // SAFETY: We lock the lifecycle mutex since rcl_timer_init is not + // thread-safe. + rcl_timer_init( + &mut *rcl_timer.lock().unwrap(), + &mut *rcl_clock, + &mut *rcl_context, + period, + rcl_timer_callback, + allocator, + true, + ) + } } .ok()?; From 802925bd930f338cc6ff9109e72c19f732567d51 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 21:00:12 +0800 Subject: [PATCH 04/12] Fix typo Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- rclrs/src/timer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rclrs/src/timer.rs b/rclrs/src/timer.rs index b3db0b178..abaac3fb9 100644 --- a/rclrs/src/timer.rs +++ b/rclrs/src/timer.rs @@ -252,7 +252,7 @@ impl<Scope: WorkScope> TimerState<Scope> { { // SAFETY: We lock the lifecycle mutex since rcl_timer_init is not // thread-safe. - rcl_timer_init( + rcl_timer_init2( &mut *rcl_timer.lock().unwrap(), &mut *rcl_clock, &mut *rcl_context, From f24b978af3b2429a562bc4612dcec14bb59321ab Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 21:18:25 +0800 Subject: [PATCH 05/12] Remove Cargo.lock for timers demo Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- examples/timer_demo/Cargo.lock | 973 --------------------------------- 1 file changed, 973 deletions(-) delete mode 100644 examples/timer_demo/Cargo.lock diff --git a/examples/timer_demo/Cargo.lock b/examples/timer_demo/Cargo.lock deleted file mode 100644 index c342f0f78..000000000 --- a/examples/timer_demo/Cargo.lock +++ /dev/null @@ -1,973 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "action_msgs" -version = "2.0.2" -dependencies = [ - "builtin_interfaces", - "rosidl_runtime_rs", - "service_msgs", - "unique_identifier_msgs", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.3.1", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" -dependencies = [ - "async-lock", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.4.0", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "bindgen" -version = "0.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", -] - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel 2.3.1", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "builtin_interfaces" -version = "2.0.2" -dependencies = [ - "rosidl_runtime_rs", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "errno" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.0", - "pin-project-lite", -] - -[[package]] -name = "example_interfaces" -version = "0.12.0" -dependencies = [ - "action_msgs", - "builtin_interfaces", - "rosidl_runtime_rs", - "service_msgs", - "unique_identifier_msgs", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "libc" -version = "0.2.174" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "libloading" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" -dependencies = [ - "cfg-if", - "windows-targets 0.53.2", -] - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -dependencies = [ - "value-bag", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "polling" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "prettyplease" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rclrs" -version = "0.4.1" -dependencies = [ - "async-std", - "bindgen", - "cfg-if", - "futures", - "rosidl_runtime_rs", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rosidl_runtime_rs" -version = "0.4.1" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustversion" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - -[[package]] -name = "service_msgs" -version = "2.0.2" -dependencies = [ - "builtin_interfaces", - "rosidl_runtime_rs", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "slab" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" - -[[package]] -name = "syn" -version = "2.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "timer_demo" -version = "0.1.0" -dependencies = [ - "example_interfaces", - "rclrs", -] - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unique_identifier_msgs" -version = "2.5.0" -dependencies = [ - "rosidl_runtime_rs", -] - -[[package]] -name = "value-bag" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[patch.unused]] -name = "rcl_interfaces" -version = "2.0.2" - -[[patch.unused]] -name = "rclrs_example_msgs" -version = "0.4.1" - -[[patch.unused]] -name = "rosgraph_msgs" -version = "2.0.2" - -[[patch.unused]] -name = "std_msgs" -version = "5.3.6" - -[[patch.unused]] -name = "test_msgs" -version = "2.0.2" From 9b6b1d1197fb33b25f3b320c4a19a5aeaf8cb751 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 23:09:29 +0800 Subject: [PATCH 06/12] Fix example package names Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- examples/logging_demo/Cargo.toml | 2 +- examples/worker_demo/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/logging_demo/Cargo.toml b/examples/logging_demo/Cargo.toml index 778981c51..a959249ac 100644 --- a/examples/logging_demo/Cargo.toml +++ b/examples/logging_demo/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "logging_demo" +name = "examples_logging_demo" version = "0.1.0" edition = "2021" diff --git a/examples/worker_demo/Cargo.toml b/examples/worker_demo/Cargo.toml index 7fd78c74b..2cde2426d 100644 --- a/examples/worker_demo/Cargo.toml +++ b/examples/worker_demo/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "worker_demo" +name = "examples_worker_demo" version = "0.1.0" edition = "2021" From 73b34b37a78b05e7feab91fe96563d4902b21d1f Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 23:15:42 +0800 Subject: [PATCH 07/12] Fix name of timer demo Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- examples/timer_demo/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/timer_demo/Cargo.toml b/examples/timer_demo/Cargo.toml index 23b9f1fa1..b4356436a 100644 --- a/examples/timer_demo/Cargo.toml +++ b/examples/timer_demo/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "timer_demo" +name = "examples_timer_demo" version = "0.1.0" edition = "2021" From 030f59c8c5c66b578b1bf8ad90b7a1ede23b4297 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Sun, 29 Jun 2025 23:27:02 +0800 Subject: [PATCH 08/12] Fix name of parameter demo Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- examples/parameter_demo/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/parameter_demo/Cargo.toml b/examples/parameter_demo/Cargo.toml index 4f90061a9..6a20a9a33 100644 --- a/examples/parameter_demo/Cargo.toml +++ b/examples/parameter_demo/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "parameter_demo" +name = "examples_parameter_demo" version = "0.1.0" edition = "2021" From a589997b24b07b7a934b7431604f25d8e82a815a Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Thu, 17 Jul 2025 20:17:18 +0800 Subject: [PATCH 09/12] Add safety comments Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- rclrs/src/timer.rs | 85 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/rclrs/src/timer.rs b/rclrs/src/timer.rs index abaac3fb9..d84ebc879 100644 --- a/rclrs/src/timer.rs +++ b/rclrs/src/timer.rs @@ -86,7 +86,13 @@ impl<Scope: WorkScope> TimerState<Scope> { pub fn get_timer_period(&self) -> Result<Duration, RclrsError> { let mut timer_period_ns = 0; unsafe { + // SAFETY: The unwrap is safe here since we never use the rcl_timer + // in a way that could panic while the mutex is locked. let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + + // SAFETY: The rcl_timer is kept valid by the TimerState. This C + // function call is thread-safe and only requires a valid rcl_timer + // to be passed in. rcl_timer_get_period(&*rcl_timer, &mut timer_period_ns) } .ok()?; @@ -94,10 +100,22 @@ impl<Scope: WorkScope> TimerState<Scope> { rcl_duration(timer_period_ns) } - /// Cancels the timer, stopping the execution of the callback + /// Cancels the timer, stopping the execution of the callback. + /// + /// [`TimerState::is_ready`] will always return false while the timer is in + /// a cancelled state. [`TimerState::reset`] can be used to revert the timer + /// out of the cancelled state. pub fn cancel(&self) -> Result<(), RclrsError> { - let mut rcl_timer = self.handle.rcl_timer.lock().unwrap(); - let cancel_result = unsafe { rcl_timer_cancel(&mut *rcl_timer) }.ok()?; + let cancel_result = unsafe { + // SAFETY: The unwrap is safe here since we never use the rcl_timer + // in a way that could panic while the mutex is locked. + let mut rcl_timer = self.handle.rcl_timer.lock().unwrap(); + + // SAFETY: The rcl_timer is kept valid by the TimerState. This C + // function call is thread-safe and only requires a valid rcl_timer + // to be passed in. + rcl_timer_cancel(&mut *rcl_timer) + }.ok()?; Ok(cancel_result) } @@ -105,7 +123,13 @@ impl<Scope: WorkScope> TimerState<Scope> { pub fn is_canceled(&self) -> Result<bool, RclrsError> { let mut is_canceled = false; unsafe { + // SAFETY: The unwrap is safe here since we never use the rcl_timer + // in a way that could panic while the mutex is locked. let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + + // SAFETY: The rcl_timer is kept valid by the TimerState. This C + // function call is thread-safe and only requires a valid rcl_timer + // to be passed in. rcl_timer_is_canceled(&*rcl_timer, &mut is_canceled) } .ok()?; @@ -128,7 +152,13 @@ impl<Scope: WorkScope> TimerState<Scope> { pub fn time_since_last_call(&self) -> Result<Duration, RclrsError> { let mut time_value_ns: i64 = 0; unsafe { + // SAFETY: The unwrap is safe here since we never use the rcl_timer + // in a way that could panic while the mutex is locked. let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + + // SAFETY: The rcl_timer is kept valid by the TimerState. This C + // function call is thread-safe and only requires a valid rcl_timer + // to be passed in. rcl_timer_get_time_since_last_call(&*rcl_timer, &mut time_value_ns) } .ok()?; @@ -140,7 +170,13 @@ impl<Scope: WorkScope> TimerState<Scope> { pub fn time_until_next_call(&self) -> Result<Duration, RclrsError> { let mut time_value_ns: i64 = 0; unsafe { + // SAFETY: The unwrap is safe here since we never use the rcl_timer + // in a way that could panic while the mutex is locked. let rcl_timer = self.handle.rcl_timer.lock().unwrap(); + + // SAFETY: The rcl_timer is kept valid by the TimerState. This C + // function call is thread-safe and only requires a valid rcl_timer + // to be passed in. rcl_timer_get_time_until_next_call(&*rcl_timer, &mut time_value_ns) } .ok()?; @@ -149,9 +185,20 @@ impl<Scope: WorkScope> TimerState<Scope> { } /// Resets the timer. + /// + /// For all timers it will reset the last call time to now. For cancelled + /// timers it will revert the timer to no longer being cancelled. pub fn reset(&self) -> Result<(), RclrsError> { + // SAFETY: The unwrap is safe here since we never use the rcl_timer + // in a way that could panic while the mutex is locked. let mut rcl_timer = self.handle.rcl_timer.lock().unwrap(); - unsafe { rcl_timer_reset(&mut *rcl_timer) }.ok() + + unsafe { + // SAFETY: The rcl_timer is kept valid by the TimerState. This C + // function call is thread-safe and only requires a valid rcl_timer + // to be passed in. + rcl_timer_reset(&mut *rcl_timer) + }.ok() } /// Checks if the timer is ready (not canceled) @@ -215,8 +262,10 @@ impl<Scope: WorkScope> TimerState<Scope> { let rcl_timer_callback: rcl_timer_callback_t = None; let rcl_timer = Arc::new(Mutex::new( - // SAFETY: Zero-initializing a timer is always safe - unsafe { rcl_get_zero_initialized_timer() }, + unsafe { + // SAFETY: Zero-initializing a timer is always safe + rcl_get_zero_initialized_timer() + }, )); unsafe { @@ -340,8 +389,16 @@ impl<Scope: WorkScope> TimerState<Scope> { /// in the [`Timer`] struct. This means there are no side-effects to this /// except to keep track of when the timer has been called. fn rcl_call(&self) -> Result<(), RclrsError> { + // SAFETY: The unwrap is safe here since we never use the rcl_timer + // in a way that could panic while the mutex is locked. let mut rcl_timer = self.handle.rcl_timer.lock().unwrap(); - unsafe { rcl_timer_call(&mut *rcl_timer) }.ok() + + unsafe { + // SAFETY: The rcl_timer is kept valid by the TimerState. This C + // function call is thread-safe and only requires a valid rcl_timer + // to be passed in. + rcl_timer_call(&mut *rcl_timer) + }.ok() } /// Used by [`Timer::execute`] to restore the state of the callback if and @@ -469,15 +526,17 @@ pub(crate) struct TimerHandle { impl Drop for TimerHandle { fn drop(&mut self) { let _lifecycle = ENTITY_LIFECYCLE_MUTEX.lock().unwrap(); - // SAFETY: The lifecycle mutex is locked and the clock for the timer - // must still be valid because TimerHandle keeps it alive. - unsafe { rcl_timer_fini(&mut *self.rcl_timer.lock().unwrap()) }; + unsafe { + // SAFETY: The lifecycle mutex is locked and the clock for the timer + // must still be valid because TimerHandle keeps it alive. + rcl_timer_fini(&mut *self.rcl_timer.lock().unwrap()) + }; } } // SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread // they are running in. Therefore, this type can be safely sent to another thread. -unsafe impl Send for rcl_timer_t {} +impl Send for rcl_timer_t {} #[cfg(test)] mod tests { @@ -759,8 +818,8 @@ mod tests { handle: Arc::clone(&timer.handle), }; - // SAFETY: Node timers expect a payload of () unsafe { + // SAFETY: Node timers expect a payload of () executable.execute(&mut ()).unwrap(); } assert!(!executed.load(Ordering::Acquire)); @@ -790,8 +849,8 @@ mod tests { thread::sleep(Duration::from_millis(2)); - // SAFETY: Node timers expect a payload of () unsafe { + // SAFETY: Node timers expect a payload of () executable.execute(&mut ()).unwrap(); } assert!(executed.load(Ordering::Acquire)); From a4fa5cb2d45aed105b2e80337a899081383b54aa Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Thu, 17 Jul 2025 20:26:48 +0800 Subject: [PATCH 10/12] Add an explicit binary name for timer demo Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- examples/timer_demo/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/timer_demo/Cargo.toml b/examples/timer_demo/Cargo.toml index b4356436a..d3bfd1583 100644 --- a/examples/timer_demo/Cargo.toml +++ b/examples/timer_demo/Cargo.toml @@ -3,6 +3,10 @@ name = "examples_timer_demo" version = "0.1.0" edition = "2021" +[[bin]] +name = "timer_demo" +path = "src/main.rs" + [dependencies] rclrs = "0.4" example_interfaces = "*" From 35fa24ac0a53e6a9c70b671a17401c46ddd67900 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Thu, 17 Jul 2025 20:54:47 +0800 Subject: [PATCH 11/12] Restore unsafe tag to Send trait Signed-off-by: Michael X. Grey <greyxmike@gmail.com> --- rclrs/Cargo.lock | 14 +------------- rclrs/src/timer.rs | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/rclrs/Cargo.lock b/rclrs/Cargo.lock index 834786e1f..c874b022b 100644 --- a/rclrs/Cargo.lock +++ b/rclrs/Cargo.lock @@ -726,7 +726,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rosidl_runtime_rs" -version = "0.4.1" +version = "0.4.2" dependencies = [ "cfg-if", "serde", @@ -1175,18 +1175,6 @@ dependencies = [ name = "rcl_interfaces" version = "2.0.2" -[[patch.unused]] -name = "rclrs" -version = "0.4.1" - -[[patch.unused]] -name = "rclrs_example_msgs" -version = "0.4.1" - [[patch.unused]] name = "rosgraph_msgs" version = "2.0.2" - -[[patch.unused]] -name = "std_msgs" -version = "5.3.6" diff --git a/rclrs/src/timer.rs b/rclrs/src/timer.rs index d84ebc879..c1605615c 100644 --- a/rclrs/src/timer.rs +++ b/rclrs/src/timer.rs @@ -536,7 +536,7 @@ impl Drop for TimerHandle { // SAFETY: The functions accessing this type, including drop(), shouldn't care about the thread // they are running in. Therefore, this type can be safely sent to another thread. -impl Send for rcl_timer_t {} +unsafe impl Send for rcl_timer_t {} #[cfg(test)] mod tests { From 775b170811518aa0c4e02f52c45b24963d9d64f7 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" <greyxmike@gmail.com> Date: Fri, 18 Jul 2025 16:31:03 +0800 Subject: [PATCH 12/12] Include authors of https://github.com/ros2-rust/ros2_rust/pull/440 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Agustin Alba Chicar <ag.albachicar@gmail.com> Co-authored-by: Jesús Silva <79662866+JesusSilvaUtrera@users.noreply.github.com> Signed-off-by: Agustin Alba Chicar <ag.albachicar@gmail.com> Signed-off-by: Jesús Silva <79662866+JesusSilvaUtrera@users.noreply.github.com> Signed-off-by: Michael X. Grey <greyxmike@gmail.com>