isaac_sim_bridge/articulation/
cmd_vel.rs1use std::sync::{Arc, OnceLock};
3
4use crate::ffi::CmdVel;
5use crate::producer::{ProducerRegistry, ProducerSlot};
6use crate::sensor::Sensor;
7
8pub struct CmdVelChannel;
16
17impl Sensor for CmdVelChannel {
18 const NAME: &'static str = "cmd_vel";
19}
20
21#[unsafe(no_mangle)]
22pub extern "C" fn isaac_sim_bridge_registry_cmd_vel() -> *const ProducerRegistry<CmdVel> {
23 static SLOT: OnceLock<Box<ProducerRegistry<CmdVel>>> = OnceLock::new();
24 let r = SLOT.get_or_init(|| Box::new(ProducerRegistry::new()));
25 Box::as_ref(r) as *const ProducerRegistry<CmdVel>
26}
27
28fn registry() -> &'static ProducerRegistry<CmdVel> {
29 unsafe { &*isaac_sim_bridge_registry_cmd_vel() }
30}
31
32pub fn register_cmd_vel_producer(target_id: impl Into<String>) -> Arc<ProducerSlot<CmdVel>> {
36 registry().register(target_id)
37}
38
39pub fn cmd_vel_producer_count() -> usize {
41 registry().count()
42}
43
44pub fn register_cmd_vel_consumer<F>(cb: F)
53where
54 F: Fn(&str, &CmdVel) + Send + Sync + 'static,
55{
56 registry().add_observer(cb);
57}
58
59pub fn cmd_vel_consumer_count() -> usize {
61 registry().observer_count()
62}
63
64pub fn poll_cmd_vel(target_id: &str, out: &mut CmdVel) -> bool {
69 if let Some(slot) = registry().lookup(target_id) {
70 if let Some(v) = slot.latest() {
71 *out = *v;
72 return true;
73 }
74 }
75 false
76}
77
78pub fn peek_cmd_vel(target_id: &str) -> Option<CmdVel> {
82 let mut out = CmdVel::default();
83 poll_cmd_vel(target_id, &mut out).then_some(out)
84}
85
86#[cfg(test)]
87mod tests {
88 use std::sync::atomic::{AtomicUsize, Ordering};
89 use std::sync::{Arc, Mutex};
90
91 use super::*;
92
93 fn make_cmd_vel(linear_x: f32) -> CmdVel {
94 CmdVel {
95 linear_x,
96 linear_y: 0.0,
97 linear_z: 0.0,
98 angular_x: 0.0,
99 angular_y: 0.0,
100 angular_z: 0.1,
101 timestamp_ns: 1_000_000,
102 }
103 }
104
105 #[test]
106 fn poll_returns_published_value() {
107 let target = "/test/articulation/poll_returns";
108 let slot = register_cmd_vel_producer(target);
109 slot.publish(make_cmd_vel(0.5));
110
111 let mut out = CmdVel::default();
112 assert!(poll_cmd_vel(target, &mut out));
113 assert_eq!(out.linear_x, 0.5);
114 }
115
116 #[test]
117 fn poll_misses_when_no_producer() {
118 let mut out = CmdVel::default();
119 assert!(!poll_cmd_vel(
120 "/test/articulation/never_registered",
121 &mut out
122 ));
123 }
124
125 #[test]
126 fn poll_misses_until_first_publish() {
127 let target = "/test/articulation/no_publish_yet";
128 let _slot = register_cmd_vel_producer(target);
129 let mut out = CmdVel::default();
130 assert!(!poll_cmd_vel(target, &mut out));
131 }
132
133 #[test]
134 fn peek_misses_until_first_publish() {
135 let target = "/test/articulation/peek_no_publish_yet";
136 let slot = register_cmd_vel_producer(target);
137 assert!(
138 peek_cmd_vel(target).is_none(),
139 "peek returns None before first publish"
140 );
141 slot.publish(make_cmd_vel(0.7));
142 let got = peek_cmd_vel(target).expect("peek returns Some after publish");
143 assert_eq!(got.linear_x, 0.7);
144 }
145
146 #[test]
147 fn two_consumers_see_correct_target_and_peek_reads_current_value() {
148 let target_a = "/test/articulation/observer_a";
149 let target_b = "/test/articulation/observer_b";
150
151 let slot_a = register_cmd_vel_producer(target_a);
152 let slot_b = register_cmd_vel_producer(target_b);
153
154 let fires_a = Arc::new(AtomicUsize::new(0));
155 let fires_b = Arc::new(AtomicUsize::new(0));
156 let peeked_a: Arc<Mutex<Option<CmdVel>>> = Arc::new(Mutex::new(None));
157 let peeked_b: Arc<Mutex<Option<CmdVel>>> = Arc::new(Mutex::new(None));
158
159 let fires_a2 = Arc::clone(&fires_a);
160 let peeked_a2 = Arc::clone(&peeked_a);
161 let ta = target_a.to_string();
162 register_cmd_vel_consumer(move |tid, _v| {
163 if tid == ta {
164 fires_a2.fetch_add(1, Ordering::SeqCst);
165 *peeked_a2.lock().unwrap() = peek_cmd_vel(&ta);
167 }
168 });
169
170 let fires_b2 = Arc::clone(&fires_b);
171 let peeked_b2 = Arc::clone(&peeked_b);
172 let tb = target_b.to_string();
173 register_cmd_vel_consumer(move |tid, _v| {
174 if tid == tb {
175 fires_b2.fetch_add(1, Ordering::SeqCst);
176 *peeked_b2.lock().unwrap() = peek_cmd_vel(&tb);
177 }
178 });
179
180 slot_a.publish(make_cmd_vel(1.0));
181 slot_b.publish(make_cmd_vel(2.0));
182
183 assert_eq!(fires_a.load(Ordering::SeqCst), 1, "consumer_a fired once");
184 assert_eq!(fires_b.load(Ordering::SeqCst), 1, "consumer_b fired once");
185
186 let pa = peeked_a.lock().unwrap().expect("consumer_a peeked Some");
187 assert_eq!(pa.linear_x, 1.0, "consumer_a saw linear_x=1.0 via peek");
188
189 let pb = peeked_b.lock().unwrap().expect("consumer_b peeked Some");
190 assert_eq!(pb.linear_x, 2.0, "consumer_b saw linear_x=2.0 via peek");
191 }
192}