Skip to content

Commit 3a6bc46

Browse files
committed
wip: phase 2
1 parent e1ee5f0 commit 3a6bc46

37 files changed

Lines changed: 6021 additions & 218 deletions

rcl-z/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ fn main() {
4242
.clang_args(include_args)
4343
.allowlist_function("rcl_.*")
4444
.allowlist_function("rmw_get_gid_for_publisher")
45+
.allowlist_function("rmw_publisher_count_matched_subscriptions")
46+
.allowlist_function("rmw_subscription_count_matched_publishers")
4547
.allowlist_function("rosidl_typesupport_c__get_service_type_support_handle__type_description_interfaces__srv__GetTypeDescription")
4648
.allowlist_type("rcl_.*")
4749
.blocklist_type("rmw_qos_profile_s")
4850
.blocklist_type("rmw_qos_.*_policy_e")
4951
.allowlist_var("RCL_.*")
52+
.allowlist_var("RMW_.*")
5053
// .no_default("rmw_qos_profile_s")
5154
.default_enum_style(bindgen::EnumVariation::Rust {
5255
non_exhaustive: false,
@@ -131,6 +134,8 @@ fn main() {
131134

132135
println!("cargo:rustc-link-search=native=/usr/local/lib");
133136
println!("cargo:rustc-link-search=native={ament_prefix}/lib/");
137+
138+
println!("cargo:rustc-link-lib=dylib=rcl");
134139
println!("cargo:rustc-link-lib=dylib=rmw");
135140
println!("cargo:rustc-link-lib=dylib=rcutils");
136141
println!("cargo:rustc-link-lib=dylib=fastcdr");

rcl-z/src/context.rs

Lines changed: 289 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,47 @@ use crate::utils::{Notifier, str_from_ptr};
77
use crate::{impl_has_impl_ptr, rclz_try};
88
use ros_z::Builder;
99
use ros_z::context::ZContext;
10+
use std::ffi::c_void;
1011
use std::str::FromStr;
12+
use std::sync::atomic::{AtomicU64, Ordering};
1113
use std::sync::Arc;
1214
use std::{
1315
ffi::{CString, c_char, c_int},
1416
ops::Deref,
1517
};
1618
use zenoh::Result;
1719

20+
// Global instance ID counter
21+
static INSTANCE_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
22+
1823
pub struct ContextImpl {
1924
inner: ZContext,
2025
notifier: Arc<Notifier>,
26+
/// Instance ID for this context (increments globally)
27+
instance_id: u64,
28+
/// Copy of init options given during init
29+
init_options: rcl_init_options_t,
30+
/// RMW context (zero-initialized for now)
31+
rmw_context: rmw_context_t,
32+
/// Whether the context is valid (active/not shutdown)
33+
is_valid: bool,
34+
/// Domain ID for this context
35+
domain_id: usize,
2136
}
2237

2338
impl ContextImpl {
24-
pub(crate) fn new(zcontext: ZContext) -> Self {
39+
pub(crate) fn new(zcontext: ZContext, init_options: rcl_init_options_t, domain_id: usize) -> Self {
40+
// Fetch and increment the global instance ID
41+
let instance_id = INSTANCE_ID_COUNTER.fetch_add(1, Ordering::SeqCst);
42+
2543
Self {
2644
inner: zcontext,
2745
notifier: Arc::new(Notifier::default()),
46+
instance_id,
47+
init_options,
48+
rmw_context: rmw_context_t::default(),
49+
is_valid: true,
50+
domain_id,
2851
}
2952
}
3053

@@ -33,21 +56,76 @@ impl ContextImpl {
3356
name: *const ::std::os::raw::c_char,
3457
namespace_: *const ::std::os::raw::c_char,
3558
context: *mut rcl_context_t,
36-
_options: *const rcl_node_options_t,
59+
options: *const rcl_node_options_t,
3760
) -> Result<NodeImpl> {
61+
// Normalize namespace: zenoh expects namespace WITHOUT leading slash
62+
// but ROS2 provides it WITH leading slash, so we need to strip it for zenoh
63+
let input_namespace = str_from_ptr(namespace_)?;
64+
let normalized_namespace = if input_namespace.is_empty() || input_namespace == "/" {
65+
""
66+
} else {
67+
input_namespace.strip_prefix('/').unwrap_or(input_namespace)
68+
};
69+
3870
let znode = self
3971
.inner
4072
.create_node(str_from_ptr(name)?)
41-
.with_namespace(str_from_ptr(namespace_)?)
73+
.with_namespace(normalized_namespace)
4274
.build()?;
4375

44-
let namespace = CString::from_str(&znode.entity.namespace)?;
45-
let name = CString::from_str(&znode.entity.name)?;
76+
let namespace_str = &znode.entity.namespace;
77+
let name_str = &znode.entity.name;
78+
79+
// Ensure namespace has leading / to match C++ RCL behavior
80+
let namespace_with_slash = if namespace_str.is_empty() {
81+
"/".to_string()
82+
} else {
83+
format!("/{}", namespace_str)
84+
};
85+
86+
// Fully qualified name should always start with /
87+
let fq_name_str = if namespace_with_slash == "/" {
88+
format!("/{}", name_str)
89+
} else {
90+
format!("{}/{}", namespace_with_slash, name_str)
91+
};
92+
93+
// Logger name: remove leading / and replace / with .
94+
let logger_name_str = if namespace_with_slash == "/" {
95+
name_str.to_string()
96+
} else {
97+
namespace_with_slash[1..].replace('/', ".") + "." + name_str
98+
};
99+
100+
let namespace = CString::new(namespace_with_slash)?;
101+
let name = CString::from_str(name_str)?;
102+
let fq_name = CString::new(fq_name_str).unwrap();
103+
let logger_name = CString::new(logger_name_str).unwrap();
104+
105+
// Copy options if provided
106+
let node_options = if options.is_null() {
107+
rcl_node_options_t::default()
108+
} else {
109+
unsafe { std::ptr::read(options) }
110+
};
111+
112+
// Create a dummy rmw_handle (non-null pointer for compatibility)
113+
let rmw_handle = Box::into_raw(Box::new(rmw_node_t::default()));
114+
115+
// Create a zero-initialized guard condition (for now)
116+
let graph_guard_condition = rcl_guard_condition_t::default();
117+
46118
Ok(NodeImpl {
47119
inner: znode,
48120
name,
49121
namespace,
122+
fq_name,
123+
logger_name,
50124
notifier: self.notifier.clone(),
125+
instance_id: self.instance_id,
126+
options: node_options,
127+
rmw_handle,
128+
graph_guard_condition,
51129
})
52130
}
53131

@@ -93,7 +171,14 @@ pub extern "C" fn rcl_publisher_event_init(
93171
publisher: *const rcl_publisher_t,
94172
event_type: rcl_publisher_event_type_t,
95173
) -> rcl_ret_t {
96-
RCL_RET_OK as _
174+
if event.is_null() || publisher.is_null() {
175+
return RCL_RET_INVALID_ARGUMENT as _;
176+
}
177+
// Assume valid types are 0-10, invalid >10
178+
if event_type as i32 > 10 {
179+
return RCL_RET_INVALID_ARGUMENT as _;
180+
}
181+
RCL_RET_UNSUPPORTED as _
97182
}
98183

99184
#[unsafe(no_mangle)]
@@ -102,30 +187,223 @@ pub extern "C" fn rcl_subscription_event_init(
102187
subscription: *const rcl_subscription_t,
103188
event_type: rcl_subscription_event_type_t,
104189
) -> rcl_ret_t {
190+
// FIXME: Follow the event implementation in rmw_zenoh_cpp: /home/circle/Workings/ZettaScale/project/nix-ros/ws/src/rmw_zenoh/rmw_zenoh_cpp/src/detail/event.cpp
191+
if event.is_null() || subscription.is_null() {
192+
return RCL_RET_INVALID_ARGUMENT as _;
193+
}
194+
// Assume valid types are 0-10, invalid >10
195+
if event_type as i32 > 10 {
196+
return RCL_RET_INVALID_ARGUMENT as _;
197+
}
198+
RCL_RET_UNSUPPORTED as _
199+
}
200+
201+
#[unsafe(no_mangle)]
202+
pub extern "C" fn rcl_event_fini(event: *mut rcl_event_t) -> rcl_ret_t {
203+
// FIXME: Follow the event implementation in rmw_zenoh_cpp: /home/circle/Workings/ZettaScale/project/nix-ros/ws/src/rmw_zenoh/rmw_zenoh_cpp/src/detail/event.cpp
105204
RCL_RET_OK as _
106205
}
107206

207+
#[unsafe(no_mangle)]
208+
pub extern "C" fn rcl_event_is_valid(event: *const rcl_event_t) -> bool {
209+
// FIXME: Follow the event implementation in rmw_zenoh_cpp: /home/circle/Workings/ZettaScale/project/nix-ros/ws/src/rmw_zenoh/rmw_zenoh_cpp/src/detail/event.cpp
210+
false
211+
}
212+
213+
#[unsafe(no_mangle)]
214+
pub extern "C" fn rcl_take_event(
215+
event: *const rcl_event_t,
216+
event_status: *mut c_void,
217+
) -> rcl_ret_t {
218+
// FIXME: Follow the event implementation in rmw_zenoh_cpp: /home/circle/Workings/ZettaScale/project/nix-ros/ws/src/rmw_zenoh/rmw_zenoh_cpp/src/detail/event.cpp
219+
if event.is_null() {
220+
return RCL_RET_EVENT_INVALID as _;
221+
}
222+
// Assume the event is invalid since we don't support events
223+
RCL_RET_EVENT_INVALID as _
224+
}
225+
226+
#[unsafe(no_mangle)]
227+
pub extern "C" fn rcl_event_get_rmw_handle(event: *const rcl_event_t) -> *mut rmw_event_t {
228+
// FIXME: print a non-support warnings
229+
std::ptr::null_mut()
230+
}
231+
232+
#[unsafe(no_mangle)]
233+
pub extern "C" fn rcl_get_zero_initialized_event() -> rcl_event_t {
234+
rcl_event_t::default()
235+
}
236+
108237
#[unsafe(no_mangle)]
109238
pub unsafe extern "C" fn rcl_context_is_valid(context: *const rcl_context_t) -> bool {
110-
unsafe { !(*context).impl_.is_null() }
239+
if context.is_null() {
240+
return false;
241+
}
242+
243+
unsafe {
244+
if (*context).impl_.is_null() {
245+
return false;
246+
}
247+
}
248+
249+
// Check if the impl is valid (not shutdown)
250+
match context.borrow_impl() {
251+
Ok(impl_) => impl_.is_valid,
252+
Err(_) => false,
253+
}
111254
}
112255

113256
#[unsafe(no_mangle)]
114257
pub extern "C" fn rcl_shutdown(context: *mut rcl_context_t) -> rcl_ret_t {
115258
tracing::trace!("rcl_shutdown");
116-
rclz_try! {
117-
context.own_impl()?.inner.shutdown()?;
259+
260+
// Check for null pointer
261+
if context.is_null() {
262+
return RCL_RET_INVALID_ARGUMENT as _;
263+
}
264+
265+
// Check if context is zero-initialized (never init'd)
266+
unsafe {
267+
if (*context).impl_.is_null() {
268+
return RCL_RET_INVALID_ARGUMENT as _;
269+
}
270+
}
271+
272+
// Check if context is valid (if not valid, it's already shutdown)
273+
if !unsafe { rcl_context_is_valid(context) } {
274+
return RCL_RET_ALREADY_SHUTDOWN as _;
275+
}
276+
277+
// Shutdown the context
278+
match context.borrow_mut_impl() {
279+
Ok(impl_) => {
280+
match impl_.inner.shutdown() {
281+
Ok(_) => {
282+
// Mark context as invalid
283+
impl_.is_valid = false;
284+
RCL_RET_OK as _
285+
}
286+
Err(e) => {
287+
tracing::error!("rcl_shutdown failed: {e}");
288+
RCL_RET_ERROR as _
289+
}
290+
}
291+
}
292+
Err(_) => RCL_RET_INVALID_ARGUMENT as _,
118293
}
119294
}
120295

121296
#[unsafe(no_mangle)]
122-
pub extern "C" fn rcl_context_fini(_context: *mut rcl_context_t) -> rcl_ret_t {
123-
// FIXME: tracing is not usable at the exit stage
297+
pub extern "C" fn rcl_context_fini(context: *mut rcl_context_t) -> rcl_ret_t {
298+
// TODO: tracing is not usable at the exit stage
124299
// tracing::trace!("rcl_context_fini");
300+
301+
// Check for null argument
302+
if context.is_null() {
303+
return RCL_RET_INVALID_ARGUMENT as _;
304+
}
305+
306+
unsafe {
307+
// If context is zero-initialized (impl is null), return OK
308+
if (*context).impl_.is_null() {
309+
return RCL_RET_OK as _;
310+
}
311+
312+
// If context is still valid (not shutdown), return error
313+
if rcl_context_is_valid(context) {
314+
// In C, this sets an error: "rcl_shutdown() not called on the given context"
315+
return RCL_RET_INVALID_ARGUMENT as _;
316+
}
317+
}
318+
319+
// TODO: Implement proper cleanup of context resources
320+
// For now, just return OK after checks
125321
RCL_RET_OK as _
126322
}
127323

128324
#[unsafe(no_mangle)]
129325
pub extern "C" fn rcl_get_zero_initialized_context() -> rcl_context_t {
130326
rcl_context_t::default()
131327
}
328+
329+
#[unsafe(no_mangle)]
330+
pub extern "C" fn rcl_context_get_instance_id(context: *const rcl_context_t) -> u64 {
331+
match context.borrow_impl() {
332+
Ok(impl_) => {
333+
// Return 0 if context is not valid (shutdown)
334+
if impl_.is_valid {
335+
impl_.instance_id
336+
} else {
337+
0
338+
}
339+
}
340+
Err(_) => 0,
341+
}
342+
}
343+
344+
#[unsafe(no_mangle)]
345+
pub extern "C" fn rcl_context_get_domain_id(
346+
context: *const rcl_context_t,
347+
domain_id: *mut usize,
348+
) -> rcl_ret_t {
349+
if context.is_null() || domain_id.is_null() {
350+
return RCL_RET_INVALID_ARGUMENT as _;
351+
}
352+
match context.borrow_impl() {
353+
Ok(impl_) => {
354+
unsafe {
355+
*domain_id = impl_.domain_id;
356+
}
357+
RCL_RET_OK as _
358+
}
359+
Err(_) => RCL_RET_INVALID_ARGUMENT as _,
360+
}
361+
}
362+
363+
#[unsafe(no_mangle)]
364+
pub extern "C" fn rcl_context_get_init_options(
365+
context: *const rcl_context_t,
366+
) -> *const rcl_init_options_t {
367+
// Check for null argument
368+
if context.is_null() {
369+
return std::ptr::null();
370+
}
371+
372+
// Check if context is zero-initialized
373+
unsafe {
374+
if (*context).impl_.is_null() {
375+
// In C, this sets an error: "context is zero-initialized"
376+
return std::ptr::null();
377+
}
378+
}
379+
380+
// Return pointer to init_options stored in context impl
381+
match context.borrow_impl() {
382+
Ok(impl_) => &impl_.init_options,
383+
Err(_) => std::ptr::null(),
384+
}
385+
}
386+
387+
#[unsafe(no_mangle)]
388+
pub extern "C" fn rcl_context_get_rmw_context(
389+
context: *mut rcl_context_t,
390+
) -> *mut rmw_context_t {
391+
// Check for null argument
392+
if context.is_null() {
393+
return std::ptr::null_mut();
394+
}
395+
396+
// Check if context is zero-initialized
397+
unsafe {
398+
if (*context).impl_.is_null() {
399+
// In C, this sets an error: "context is zero-initialized"
400+
return std::ptr::null_mut();
401+
}
402+
}
403+
404+
// Return pointer to rmw_context stored in context impl
405+
match context.borrow_mut_impl() {
406+
Ok(impl_) => &mut impl_.rmw_context,
407+
Err(_) => std::ptr::null_mut(),
408+
}
409+
}

0 commit comments

Comments
 (0)