use std::{collections::BTreeMap, ffi::CStr, os::raw::c_char};
use crate::{rcl_bindings::*, ParameterValue, RclrsError, ToResult};
struct RclParamsIter<'a> {
node_name_ptrs: &'a [*mut c_char],
rcl_node_params: &'a [rcl_node_params_t],
}
struct RclNodeParamsIter<'a> {
param_name_ptrs: &'a [*mut c_char],
rcl_variants: &'a [rcl_variant_t],
}
impl<'a> Iterator for RclParamsIter<'a> {
type Item = (String, &'a rcl_node_params_t);
fn next(&mut self) -> Option<Self::Item> {
let (next_node_param, rest) = self.rcl_node_params.split_first()?;
self.rcl_node_params = rest;
let (next_node_name_ptr, rest) = self.node_name_ptrs.split_first()?;
self.node_name_ptrs = rest;
debug_assert!(!next_node_name_ptr.is_null());
let mut next_node_name = unsafe {
CStr::from_ptr(*next_node_name_ptr)
.to_string_lossy()
.into_owned()
};
if !next_node_name.starts_with('/') {
next_node_name.insert(0, '/');
}
Some((next_node_name, next_node_param))
}
}
impl RclParamsIter<'_> {
pub unsafe fn new(rcl_params: *const rcl_params_t) -> Self {
if rcl_params.is_null() {
Self {
node_name_ptrs: &[],
rcl_node_params: &[],
}
} else {
unsafe {
let node_name_ptrs =
rcl_from_raw_parts((*rcl_params).node_names, (*rcl_params).num_nodes);
let rcl_node_params =
rcl_from_raw_parts((*rcl_params).params, (*rcl_params).num_nodes);
Self {
node_name_ptrs,
rcl_node_params,
}
}
}
}
}
impl<'a> Iterator for RclNodeParamsIter<'a> {
type Item = (String, &'a rcl_variant_t);
fn next(&mut self) -> Option<Self::Item> {
let (next_param_name_ptr, rest) = self.param_name_ptrs.split_first()?;
self.param_name_ptrs = rest;
let (next_rcl_variant, rest) = self.rcl_variants.split_first()?;
self.rcl_variants = rest;
let next_param_name = unsafe {
CStr::from_ptr(*next_param_name_ptr)
.to_string_lossy()
.into_owned()
};
Some((next_param_name, next_rcl_variant))
}
}
impl<'a> RclNodeParamsIter<'a> {
pub unsafe fn new(rcl_node_params: &'a rcl_node_params_t) -> Self {
let param_name_ptrs = unsafe {
rcl_from_raw_parts(rcl_node_params.parameter_names, rcl_node_params.num_params)
};
let rcl_variants = unsafe {
rcl_from_raw_parts(rcl_node_params.parameter_values, rcl_node_params.num_params)
};
Self {
param_name_ptrs,
rcl_variants,
}
}
}
pub(crate) type ParameterOverrideMap = BTreeMap<String, ParameterValue>;
pub(crate) unsafe fn resolve_parameter_overrides(
node_fqn: &str,
rcl_node_arguments: &rcl_arguments_t,
rcl_global_arguments: &rcl_arguments_t,
) -> Result<ParameterOverrideMap, RclrsError> {
unsafe {
let mut map = BTreeMap::new();
for rcl_arguments in [rcl_global_arguments, rcl_node_arguments] {
let mut rcl_params = std::ptr::null_mut();
rcl_arguments_get_param_overrides(rcl_arguments, &mut rcl_params).ok()?;
for name_to_match in ["/**", node_fqn] {
for (node_name, node_params) in RclParamsIter::new(rcl_params) {
if node_name == name_to_match {
for (param_name, variant) in RclNodeParamsIter::new(node_params) {
let value = ParameterValue::from_rcl_variant(variant);
map.insert(param_name, value);
}
}
}
}
rcl_yaml_node_struct_fini(rcl_params);
}
Ok(map)
}
}
#[cfg(test)]
mod tests {
use std::{error::Error, ffi::CString, io::Write};
use tempfile::NamedTempFile;
use super::*;
const GLOBAL_PARAMS_FILE: &str = r#"
/**:
ros__parameters:
a: 1
b: 1
c: 1
d: 1
e: 1
f: 1
g: 1
h: 1
/my_ns/my_node:
ros__parameters:
a: 2
b: 2
c: 2
d: 2
i: 2
j: 2
k: 2
l: 2
"#;
const NODE_PARAMS_FILE: &str = r#"
/my_ns/my_node:
ros__parameters:
a: 3
b: 3
e: 3
f: 3
i: 3
j: 3
m: 3
n: 3
/my_ns/my_node:
ros__parameters:
a: 4
c: 4
e: 4
g: 4
i: 4
k: 4
m: 4
o: 4
"#;
fn convert_to_rcl_arguments(
param_file_contents: &str,
) -> Result<rcl_arguments_t, Box<dyn Error>> {
let mut params_file = NamedTempFile::new()?;
write!(params_file, "{}", param_file_contents)?;
let params_filename = params_file.path().display().to_string();
let args = ["--ros-args", "--params-file", ¶ms_filename]
.into_iter()
.map(CString::new)
.collect::<Result<Vec<_>, _>>()?;
let args_ptrs = args.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
let mut rcl_arguments = unsafe { rcl_get_zero_initialized_arguments() };
unsafe {
rcl_parse_arguments(
args_ptrs.len() as i32,
args_ptrs.as_ptr(),
rcutils_get_default_allocator(),
&mut rcl_arguments,
);
}
Ok(rcl_arguments)
}
#[test]
fn test_resolve_parameter_overrides() -> Result<(), Box<dyn Error>> {
let node_fqn = "/my_ns/my_node";
let mut rcl_node_arguments = convert_to_rcl_arguments(NODE_PARAMS_FILE)?;
let mut rcl_global_arguments = convert_to_rcl_arguments(GLOBAL_PARAMS_FILE)?;
let overrides_map = unsafe {
resolve_parameter_overrides(node_fqn, &rcl_node_arguments, &rcl_global_arguments)?
};
unsafe {
rcl_arguments_fini(&mut rcl_node_arguments);
rcl_arguments_fini(&mut rcl_global_arguments);
}
let values: Vec<_> = overrides_map
.iter()
.map(|(k, v)| (k.as_str(), v.clone()))
.collect();
assert_eq!(
values,
vec![
("a", ParameterValue::Integer(4)),
("b", ParameterValue::Integer(3)),
("c", ParameterValue::Integer(4)),
("d", ParameterValue::Integer(2)),
("e", ParameterValue::Integer(4)),
("f", ParameterValue::Integer(3)),
("g", ParameterValue::Integer(4)),
("h", ParameterValue::Integer(1)),
("i", ParameterValue::Integer(4)),
("j", ParameterValue::Integer(3)),
("k", ParameterValue::Integer(4)),
("l", ParameterValue::Integer(2)),
("m", ParameterValue::Integer(4)),
("n", ParameterValue::Integer(3)),
("o", ParameterValue::Integer(4)),
]
);
Ok(())
}
}