mod override_map;
mod range;
mod service;
mod value;
pub(crate) use override_map::*;
pub use range::*;
use service::*;
pub use value::*;
use crate::vendor::rcl_interfaces::msg::rmw::{ParameterType, ParameterValue as RmwParameterValue};
use crate::{
call_string_getter_with_rcl_node, rcl_bindings::*, Node, RclrsError, ENTITY_LIFECYCLE_MUTEX,
};
use std::{
collections::{btree_map::Entry, BTreeMap},
fmt::Debug,
marker::PhantomData,
sync::{Arc, Mutex, RwLock, Weak},
};
#[derive(Clone, Debug)]
struct ParameterOptionsStorage {
description: Arc<str>,
constraints: Arc<str>,
ranges: ParameterRanges,
}
impl<T: ParameterVariant> From<ParameterOptions<T>> for ParameterOptionsStorage {
fn from(opts: ParameterOptions<T>) -> Self {
Self {
description: opts.description,
constraints: opts.constraints,
ranges: opts.ranges.into(),
}
}
}
#[derive(Clone, Debug)]
pub struct ParameterOptions<T: ParameterVariant> {
description: Arc<str>,
constraints: Arc<str>,
ranges: T::Range,
}
impl<T: ParameterVariant> Default for ParameterOptions<T> {
fn default() -> Self {
Self {
description: Arc::from(""),
constraints: Arc::from(""),
ranges: Default::default(),
}
}
}
#[derive(Clone, Debug)]
enum DeclaredValue {
Mandatory(Arc<RwLock<ParameterValue>>),
Optional(Arc<RwLock<Option<ParameterValue>>>),
ReadOnly(ParameterValue),
}
#[must_use]
pub struct ParameterBuilder<'a, T: ParameterVariant> {
name: Arc<str>,
default_value: Option<T>,
ignore_override: bool,
discard_mismatching_prior_value: bool,
discriminator: DiscriminatorFunction<'a, T>,
options: ParameterOptions<T>,
interface: &'a ParameterInterface,
}
impl<'a, T: ParameterVariant> ParameterBuilder<'a, T> {
pub fn default(mut self, value: T) -> Self {
self.default_value = Some(value);
self
}
pub fn ignore_override(mut self) -> Self {
self.ignore_override = true;
self
}
pub fn discard_mismatching_prior_value(mut self) -> Self {
self.discard_mismatching_prior_value = true;
self
}
pub fn discriminate<F>(mut self, f: F) -> Self
where
F: FnOnce(AvailableValues<T>) -> Option<T> + 'a,
{
self.discriminator = Box::new(f);
self
}
pub fn range(mut self, range: T::Range) -> Self {
self.options.ranges = range;
self
}
pub fn description(mut self, description: impl Into<Arc<str>>) -> Self {
self.options.description = description.into();
self
}
pub fn constraints(mut self, constraints: impl Into<Arc<str>>) -> Self {
self.options.constraints = constraints.into();
self
}
pub fn mandatory(self) -> Result<MandatoryParameter<T>, DeclarationError> {
self.try_into()
}
pub fn read_only(self) -> Result<ReadOnlyParameter<T>, DeclarationError> {
self.try_into()
}
pub fn optional(self) -> Result<OptionalParameter<T>, DeclarationError> {
self.try_into()
}
}
impl<T> ParameterBuilder<'_, Arc<[T]>>
where
Arc<[T]>: ParameterVariant,
{
pub fn default_from_iter(mut self, default_value: impl IntoIterator<Item = T>) -> Self {
self.default_value = Some(default_value.into_iter().collect());
self
}
}
impl ParameterBuilder<'_, Arc<[Arc<str>]>> {
pub fn default_string_array<U>(mut self, default_value: U) -> Self
where
U: IntoIterator,
U::Item: Into<Arc<str>>,
{
self.default_value = Some(default_value.into_iter().map(|v| v.into()).collect());
self
}
}
pub struct AvailableValues<'a, T> {
pub default_value: Option<T>,
pub override_value: Option<T>,
pub prior_value: Option<T>,
pub ranges: &'a ParameterRanges,
}
pub fn default_initial_value_discriminator<T: ParameterVariant>(
available: AvailableValues<T>,
) -> Option<T> {
if let Some(prior) = available.prior_value {
if available.ranges.in_range(&prior.clone().into()) {
return Some(prior);
}
}
if available.override_value.is_some() {
return available.override_value;
}
available.default_value
}
type DiscriminatorFunction<'a, T> = Box<dyn FnOnce(AvailableValues<T>) -> Option<T> + 'a>;
impl<T: ParameterVariant> TryFrom<ParameterBuilder<'_, T>> for OptionalParameter<T> {
type Error = DeclarationError;
fn try_from(builder: ParameterBuilder<T>) -> Result<Self, Self::Error> {
let ranges = builder.options.ranges.clone().into();
let initial_value = builder.interface.get_declaration_initial_value::<T>(
&builder.name,
builder.default_value,
builder.ignore_override,
builder.discard_mismatching_prior_value,
builder.discriminator,
&ranges,
)?;
let value = Arc::new(RwLock::new(initial_value.map(|v| v.into())));
builder.interface.store_parameter(
builder.name.clone(),
T::kind(),
DeclaredValue::Optional(value.clone()),
builder.options.into(),
);
Ok(OptionalParameter {
name: builder.name,
value,
ranges,
map: Arc::downgrade(&builder.interface.parameter_map),
_marker: Default::default(),
})
}
}
pub struct MandatoryParameter<T: ParameterVariant> {
name: Arc<str>,
value: Arc<RwLock<ParameterValue>>,
ranges: ParameterRanges,
map: Weak<Mutex<ParameterMap>>,
_marker: PhantomData<T>,
}
impl<T: ParameterVariant + Debug> Debug for MandatoryParameter<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MandatoryParameter")
.field("name", &self.name)
.field("value", &self.get())
.field("range", &self.ranges)
.finish()
}
}
impl<T: ParameterVariant> Drop for MandatoryParameter<T> {
fn drop(&mut self) {
if let Some(map) = self.map.upgrade() {
let storage = &mut map.lock().unwrap().storage;
storage.remove(&self.name);
}
}
}
impl<'a, T: ParameterVariant + 'a> TryFrom<ParameterBuilder<'a, T>> for MandatoryParameter<T> {
type Error = DeclarationError;
fn try_from(builder: ParameterBuilder<T>) -> Result<Self, Self::Error> {
let ranges = builder.options.ranges.clone().into();
let initial_value = builder.interface.get_declaration_initial_value::<T>(
&builder.name,
builder.default_value,
builder.ignore_override,
builder.discard_mismatching_prior_value,
builder.discriminator,
&ranges,
)?;
let Some(initial_value) = initial_value else {
return Err(DeclarationError::NoValueAvailable);
};
let value = Arc::new(RwLock::new(initial_value.into()));
builder.interface.store_parameter(
builder.name.clone(),
T::kind(),
DeclaredValue::Mandatory(value.clone()),
builder.options.into(),
);
Ok(MandatoryParameter {
name: builder.name,
value,
ranges,
map: Arc::downgrade(&builder.interface.parameter_map),
_marker: Default::default(),
})
}
}
pub struct OptionalParameter<T: ParameterVariant> {
name: Arc<str>,
value: Arc<RwLock<Option<ParameterValue>>>,
ranges: ParameterRanges,
map: Weak<Mutex<ParameterMap>>,
_marker: PhantomData<T>,
}
impl<T: ParameterVariant + Debug> Debug for OptionalParameter<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OptionalParameter")
.field("name", &self.name)
.field("value", &self.get())
.field("range", &self.ranges)
.finish()
}
}
impl<T: ParameterVariant> Drop for OptionalParameter<T> {
fn drop(&mut self) {
if let Some(map) = self.map.upgrade() {
let storage = &mut map.lock().unwrap().storage;
storage.remove(&self.name);
}
}
}
pub struct ReadOnlyParameter<T: ParameterVariant> {
name: Arc<str>,
value: ParameterValue,
map: Weak<Mutex<ParameterMap>>,
_marker: PhantomData<T>,
}
impl<T: ParameterVariant + Debug> Debug for ReadOnlyParameter<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReadOnlyParameter")
.field("name", &self.name)
.field("value", &self.value)
.finish()
}
}
impl<T: ParameterVariant> Drop for ReadOnlyParameter<T> {
fn drop(&mut self) {
if let Some(map) = self.map.upgrade() {
let storage = &mut map.lock().unwrap().storage;
storage.remove(&self.name);
}
}
}
impl<'a, T: ParameterVariant + 'a> TryFrom<ParameterBuilder<'a, T>> for ReadOnlyParameter<T> {
type Error = DeclarationError;
fn try_from(builder: ParameterBuilder<T>) -> Result<Self, Self::Error> {
let ranges = builder.options.ranges.clone().into();
let initial_value = builder.interface.get_declaration_initial_value::<T>(
&builder.name,
builder.default_value,
builder.ignore_override,
builder.discard_mismatching_prior_value,
builder.discriminator,
&ranges,
)?;
let Some(initial_value) = initial_value else {
return Err(DeclarationError::NoValueAvailable);
};
let value = initial_value.into();
builder.interface.store_parameter(
builder.name.clone(),
T::kind(),
DeclaredValue::ReadOnly(value.clone()),
builder.options.into(),
);
Ok(ReadOnlyParameter {
name: builder.name,
value,
map: Arc::downgrade(&builder.interface.parameter_map),
_marker: Default::default(),
})
}
}
#[derive(Clone, Debug)]
struct DeclaredStorage {
value: DeclaredValue,
kind: ParameterKind,
options: ParameterOptionsStorage,
}
#[derive(Debug)]
enum ParameterStorage {
Declared(DeclaredStorage),
Undeclared(ParameterValue),
}
impl ParameterStorage {
pub(crate) fn to_parameter_type(&self) -> u8 {
match self {
ParameterStorage::Declared(s) => match s.kind {
ParameterKind::Bool => ParameterType::PARAMETER_BOOL,
ParameterKind::Integer => ParameterType::PARAMETER_INTEGER,
ParameterKind::Double => ParameterType::PARAMETER_DOUBLE,
ParameterKind::String => ParameterType::PARAMETER_STRING,
ParameterKind::ByteArray => ParameterType::PARAMETER_BYTE_ARRAY,
ParameterKind::BoolArray => ParameterType::PARAMETER_BOOL_ARRAY,
ParameterKind::IntegerArray => ParameterType::PARAMETER_INTEGER_ARRAY,
ParameterKind::DoubleArray => ParameterType::PARAMETER_DOUBLE_ARRAY,
ParameterKind::StringArray => ParameterType::PARAMETER_STRING_ARRAY,
ParameterKind::Dynamic => match &s.value {
DeclaredValue::Mandatory(v) => v.read().unwrap().rcl_parameter_type(),
DeclaredValue::Optional(v) => v
.read()
.unwrap()
.as_ref()
.map(|v| v.rcl_parameter_type())
.unwrap_or(ParameterType::PARAMETER_NOT_SET),
DeclaredValue::ReadOnly(v) => v.rcl_parameter_type(),
},
},
ParameterStorage::Undeclared(value) => value.rcl_parameter_type(),
}
}
}
#[derive(Debug, Default)]
pub(crate) struct ParameterMap {
storage: BTreeMap<Arc<str>, ParameterStorage>,
allow_undeclared: bool,
}
impl ParameterMap {
fn validate_parameter_setting(
&self,
name: &str,
value: RmwParameterValue,
) -> Result<ParameterValue, &str> {
let Ok(value): Result<ParameterValue, _> = value.try_into() else {
return Err("Invalid parameter type");
};
match self.storage.get(name) {
Some(entry) => {
if let ParameterStorage::Declared(storage) = entry {
if std::mem::discriminant(&storage.kind)
== std::mem::discriminant(&value.kind())
|| matches!(storage.kind, ParameterKind::Dynamic)
{
if !storage.options.ranges.in_range(&value) {
return Err("Parameter value is out of range");
}
if matches!(&storage.value, DeclaredValue::ReadOnly(_)) {
return Err("Parameter is read only");
}
} else {
return Err(
"Parameter set to different type and dynamic typing is disabled",
);
}
}
}
None => {
if !self.allow_undeclared {
return Err(
"Parameter was not declared and undeclared parameters are not allowed",
);
}
}
}
Ok(value)
}
fn store_parameter(&mut self, name: Arc<str>, value: ParameterValue) {
match self.storage.entry(name) {
Entry::Occupied(mut entry) => match entry.get_mut() {
ParameterStorage::Declared(storage) => match &storage.value {
DeclaredValue::Mandatory(p) => *p.write().unwrap() = value,
DeclaredValue::Optional(p) => *p.write().unwrap() = Some(value),
DeclaredValue::ReadOnly(_) => unreachable!(),
},
ParameterStorage::Undeclared(param) => {
*param = value;
}
},
Entry::Vacant(entry) => {
entry.insert(ParameterStorage::Undeclared(value));
}
}
}
}
impl<T: ParameterVariant> MandatoryParameter<T> {
pub fn get(&self) -> T {
self.value.read().unwrap().clone().try_into().ok().unwrap()
}
pub fn set<U: Into<T>>(&self, value: U) -> Result<(), ParameterValueError> {
let value = value.into().into();
if !self.ranges.in_range(&value) {
return Err(ParameterValueError::OutOfRange);
}
*self.value.write().unwrap() = value;
Ok(())
}
}
impl<T: ParameterVariant> ReadOnlyParameter<T> {
pub fn get(&self) -> T {
self.value.clone().try_into().ok().unwrap()
}
}
impl<T: ParameterVariant> OptionalParameter<T> {
pub fn get(&self) -> Option<T> {
self.value
.read()
.unwrap()
.clone()
.map(|p| p.try_into().ok().unwrap())
}
pub fn set<U: Into<T>>(&self, value: U) -> Result<(), ParameterValueError> {
let value = value.into().into();
if !self.ranges.in_range(&value) {
return Err(ParameterValueError::OutOfRange);
}
*self.value.write().unwrap() = Some(value);
Ok(())
}
pub fn unset(&self) {
*self.value.write().unwrap() = None;
}
}
pub struct Parameters<'a> {
pub(crate) interface: &'a ParameterInterface,
}
#[derive(Debug)]
pub enum ParameterValueError {
OutOfRange,
TypeMismatch,
ReadOnly,
}
impl std::fmt::Display for ParameterValueError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParameterValueError::OutOfRange => write!(f, "parameter value was out of the parameter's range"),
ParameterValueError::TypeMismatch => write!(f, "parameter was stored in a static type and an operation on a different type was attempted"),
ParameterValueError::ReadOnly => write!(f, "a write on a read-only parameter was attempted"),
}
}
}
impl std::error::Error for ParameterValueError {}
#[derive(Debug, PartialEq, Eq)]
pub enum DeclarationError {
AlreadyDeclared,
NoValueAvailable,
OverrideValueTypeMismatch,
PriorValueTypeMismatch,
InitialValueOutOfRange,
InvalidRange,
}
impl std::fmt::Display for DeclarationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeclarationError::AlreadyDeclared => write!(f, "parameter was already declared, but a new declaration was attempted"),
DeclarationError::NoValueAvailable => {
write!(f, "parameter was declared as non-optional but no value was available, either through a user specified default, a command-line override, or a previously set value")
},
DeclarationError::OverrideValueTypeMismatch => {
write!(f, "the override value that was provided has the wrong type")
},
DeclarationError::PriorValueTypeMismatch => {
write!(f, "the value that the parameter was already set to has the wrong type")
},
DeclarationError::InitialValueOutOfRange => write!(f, "the initial value that was selected is out of range"),
DeclarationError::InvalidRange => write!(f, "an invalid range was provided to a parameter declaration (i.e. lower bound > higher bound)"),
}
}
}
impl std::error::Error for DeclarationError {}
impl Parameters<'_> {
pub fn get<T: ParameterVariant>(&self, name: &str) -> Option<T> {
let storage = &self.interface.parameter_map.lock().unwrap().storage;
let storage = storage.get(name)?;
match storage {
ParameterStorage::Declared(storage) => match &storage.value {
DeclaredValue::Mandatory(p) => p.read().unwrap().clone().try_into().ok(),
DeclaredValue::Optional(p) => {
p.read().unwrap().clone().and_then(|p| p.try_into().ok())
}
DeclaredValue::ReadOnly(p) => p.clone().try_into().ok(),
},
ParameterStorage::Undeclared(value) => value.clone().try_into().ok(),
}
}
pub fn set<T: ParameterVariant>(
&self,
name: impl Into<Arc<str>>,
value: T,
) -> Result<(), ParameterValueError> {
let mut map = self.interface.parameter_map.lock().unwrap();
let name: Arc<str> = name.into();
match map.storage.entry(name) {
Entry::Occupied(mut entry) => {
match entry.get_mut() {
ParameterStorage::Declared(param) => {
if T::kind() == param.kind {
let value = value.into();
if !param.options.ranges.in_range(&value) {
return Err(ParameterValueError::OutOfRange);
}
match ¶m.value {
DeclaredValue::Mandatory(p) => *p.write().unwrap() = value,
DeclaredValue::Optional(p) => *p.write().unwrap() = Some(value),
DeclaredValue::ReadOnly(_) => {
return Err(ParameterValueError::ReadOnly);
}
}
} else {
return Err(ParameterValueError::TypeMismatch);
}
}
ParameterStorage::Undeclared(param) => {
*param = value.into();
}
}
}
Entry::Vacant(entry) => {
entry.insert(ParameterStorage::Undeclared(value.into()));
}
}
Ok(())
}
}
pub(crate) struct ParameterInterface {
parameter_map: Arc<Mutex<ParameterMap>>,
override_map: ParameterOverrideMap,
services: Mutex<Option<ParameterService>>,
}
impl ParameterInterface {
pub(crate) fn new(
rcl_node: &rcl_node_t,
node_arguments: &rcl_arguments_t,
global_arguments: &rcl_arguments_t,
) -> Result<Self, RclrsError> {
let override_map = unsafe {
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
let fqn = call_string_getter_with_rcl_node(rcl_node, rcl_node_get_fully_qualified_name);
resolve_parameter_overrides(&fqn, node_arguments, global_arguments)?
};
Ok(ParameterInterface {
parameter_map: Default::default(),
override_map,
services: Mutex::new(None),
})
}
pub(crate) fn declare<'a, T: ParameterVariant + 'a>(
&'a self,
name: Arc<str>,
) -> ParameterBuilder<'a, T> {
ParameterBuilder {
name,
default_value: None,
ignore_override: false,
discard_mismatching_prior_value: false,
discriminator: Box::new(default_initial_value_discriminator::<T>),
options: Default::default(),
interface: self,
}
}
pub(crate) fn create_services(&self, node: &Node) -> Result<(), RclrsError> {
*self.services.lock().unwrap() =
Some(ParameterService::new(node, self.parameter_map.clone())?);
Ok(())
}
fn get_declaration_initial_value<'a, T: ParameterVariant + 'a>(
&self,
name: &str,
default_value: Option<T>,
ignore_override: bool,
discard_mismatching_prior: bool,
discriminator: DiscriminatorFunction<T>,
ranges: &ParameterRanges,
) -> Result<Option<T>, DeclarationError> {
ranges.validate()?;
let override_value: Option<T> = if ignore_override {
None
} else if let Some(override_value) = self.override_map.get(name).cloned() {
Some(
override_value
.try_into()
.map_err(|_| DeclarationError::OverrideValueTypeMismatch)?,
)
} else {
None
};
let prior_value =
if let Some(prior_value) = self.parameter_map.lock().unwrap().storage.get(name) {
match prior_value {
ParameterStorage::Declared(_) => return Err(DeclarationError::AlreadyDeclared),
ParameterStorage::Undeclared(param) => match param.clone().try_into() {
Ok(prior) => Some(prior),
Err(_) => {
if !discard_mismatching_prior {
return Err(DeclarationError::PriorValueTypeMismatch);
}
None
}
},
}
} else {
None
};
let selection = discriminator(AvailableValues {
default_value,
override_value,
prior_value,
ranges,
});
if let Some(initial_value) = &selection {
if !ranges.in_range(&initial_value.clone().into()) {
return Err(DeclarationError::InitialValueOutOfRange);
}
}
Ok(selection)
}
fn store_parameter(
&self,
name: Arc<str>,
kind: ParameterKind,
value: DeclaredValue,
options: ParameterOptionsStorage,
) {
self.parameter_map.lock().unwrap().storage.insert(
name,
ParameterStorage::Declared(DeclaredStorage {
options,
value,
kind,
}),
);
}
pub(crate) fn allow_undeclared(&self) {
self.parameter_map.lock().unwrap().allow_undeclared = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
#[test]
fn test_parameter_override_errors() {
let executor = Context::new(
[
String::from("--ros-args"),
String::from("-p"),
String::from("declared_int:=10"),
],
InitOptions::default(),
)
.unwrap()
.create_basic_executor();
let node = executor
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
assert!(matches!(
node.declare_parameter("declared_int")
.default(1.0)
.mandatory(),
Err(DeclarationError::OverrideValueTypeMismatch)
));
assert!(node
.declare_parameter("declared_int")
.default(1.0)
.ignore_override()
.mandatory()
.is_ok());
let range = ParameterRange {
upper: Some(5),
..Default::default()
};
assert!(matches!(
node.declare_parameter("declared_int")
.default(1)
.range(range.clone())
.mandatory(),
Err(DeclarationError::InitialValueOutOfRange)
));
assert!(node
.declare_parameter("declared_int")
.default(1)
.range(range)
.ignore_override()
.mandatory()
.is_ok());
}
#[test]
fn test_parameter_setting_declaring() {
let executor = Context::new(
[
String::from("--ros-args"),
String::from("-p"),
String::from("declared_int:=10"),
String::from("-p"),
String::from("double_array:=[1.0, 2.0]"),
String::from("-p"),
String::from("optional_bool:=true"),
String::from("-p"),
String::from("non_declared_string:='param'"),
],
InitOptions::default(),
)
.unwrap()
.create_basic_executor();
let node = executor
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
let overridden_int = node
.declare_parameter("declared_int")
.default(123)
.mandatory()
.unwrap();
assert_eq!(overridden_int.get(), 10);
let new_param = node
.declare_parameter("new_param")
.default(2.0)
.mandatory()
.unwrap();
assert_eq!(new_param.get(), 2.0);
assert_eq!(
node.use_undeclared_parameters().get::<f64>("new_param"),
Some(2.0)
);
assert!(node
.use_undeclared_parameters()
.get::<i64>("new_param")
.is_none());
assert!(matches!(
node.use_undeclared_parameters().set("new_param", 42),
Err(ParameterValueError::TypeMismatch)
));
assert!(node
.use_undeclared_parameters()
.set("new_param", 10.0)
.is_ok());
assert_eq!(
node.use_undeclared_parameters().get("new_param"),
Some(10.0)
);
assert_eq!(new_param.get(), 10.0);
new_param.set(5.0).unwrap();
assert_eq!(new_param.get(), 5.0);
assert_eq!(node.use_undeclared_parameters().get("new_param"), Some(5.0));
assert_eq!(
node.use_undeclared_parameters()
.get::<f64>("non_existing_param"),
None
);
assert_eq!(
node.use_undeclared_parameters()
.get::<Arc<str>>("non_declared_string"),
None
);
{
node.use_undeclared_parameters()
.set("new_bool", true)
.unwrap();
let bool_param = node
.declare_parameter("new_bool")
.default(false)
.mandatory()
.unwrap();
assert!(bool_param.get());
}
{
node.use_undeclared_parameters()
.set("new_bool", true)
.unwrap();
let bool_param = node
.declare_parameter("new_bool")
.default(false)
.optional()
.unwrap();
assert_eq!(bool_param.get(), Some(true));
}
let optional_param = node
.declare_parameter("non_existing_bool")
.optional()
.unwrap();
assert_eq!(optional_param.get(), None);
optional_param.set(true).unwrap();
assert_eq!(optional_param.get(), Some(true));
optional_param.unset();
assert_eq!(optional_param.get(), None);
let optional_param2 = node
.declare_parameter("non_existing_bool2")
.default(false)
.optional()
.unwrap();
assert_eq!(optional_param2.get(), Some(false));
let optional_param3 = node
.declare_parameter("optional_bool")
.default(false)
.optional()
.unwrap();
assert_eq!(optional_param3.get(), Some(true));
let array_param = node
.declare_parameter("double_array")
.default_from_iter(vec![10.0, 20.0])
.mandatory()
.unwrap();
assert_eq!(array_param.get()[0], 1.0);
assert_eq!(array_param.get()[1], 2.0);
let array_param = node
.declare_parameter("string_array")
.default_string_array(vec!["Hello", "World"])
.mandatory()
.unwrap();
assert_eq!(array_param.get()[0], "Hello".into());
assert_eq!(array_param.get()[1], "World".into());
node.use_undeclared_parameters()
.set("undeclared_int", 42)
.unwrap();
let undeclared_int = node
.declare_parameter("undeclared_int")
.default(10)
.mandatory()
.unwrap();
assert_eq!(undeclared_int.get(), 42);
}
#[test]
fn test_override_undeclared_set_priority() {
let executor = Context::new(
[
String::from("--ros-args"),
String::from("-p"),
String::from("declared_int:=10"),
],
InitOptions::default(),
)
.unwrap()
.create_basic_executor();
let node = executor
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
node.use_undeclared_parameters()
.set("declared_int", 20)
.unwrap();
let param = node
.declare_parameter("declared_int")
.default(30)
.mandatory()
.unwrap();
assert_eq!(param.get(), 20);
}
#[test]
fn test_parameter_scope_redeclaring() {
let executor = Context::new(
[
String::from("--ros-args"),
String::from("-p"),
String::from("declared_int:=10"),
],
InitOptions::default(),
)
.unwrap()
.create_basic_executor();
let node = executor
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
{
let param = node
.declare_parameter("declared_int")
.default(1)
.mandatory()
.unwrap();
assert_eq!(param.get(), 10);
param.set(2).unwrap();
assert_eq!(param.get(), 2);
assert!(matches!(
node.declare_parameter("declared_int")
.default(1)
.mandatory(),
Err(DeclarationError::AlreadyDeclared)
));
}
{
let param = node
.declare_parameter::<i64>("declared_int")
.mandatory()
.unwrap();
assert_eq!(param.get(), 10);
}
assert!(node
.use_undeclared_parameters()
.get::<i64>("declared_int")
.is_none());
node.use_undeclared_parameters()
.set("declared_int", 1.0)
.unwrap();
assert_eq!(
node.use_undeclared_parameters().get::<f64>("declared_int"),
Some(1.0)
);
}
#[test]
fn test_parameter_ranges() {
let node = Context::default()
.create_basic_executor()
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
let range = ParameterRange {
lower: Some(10),
upper: Some(-10),
step: Some(3),
};
assert!(matches!(
node.declare_parameter("int_param")
.default(5)
.range(range)
.mandatory(),
Err(DeclarationError::InvalidRange)
));
let range = ParameterRange {
lower: Some(-10),
upper: Some(10),
step: Some(-1),
};
assert!(matches!(
node.declare_parameter("int_param")
.default(5)
.range(range)
.mandatory(),
Err(DeclarationError::InvalidRange)
));
let range = ParameterRange {
lower: Some(-10),
upper: Some(10),
step: Some(3),
};
assert!(matches!(
node.declare_parameter("out_of_range_int")
.default(100)
.range(range.clone())
.mandatory(),
Err(DeclarationError::InitialValueOutOfRange)
));
assert!(matches!(
node.declare_parameter("wrong_step_int")
.default(-9)
.range(range.clone())
.mandatory(),
Err(DeclarationError::InitialValueOutOfRange)
));
let param = node
.declare_parameter("int_param")
.default(-7)
.range(range)
.mandatory()
.unwrap();
assert!(param.set(10).is_ok());
assert!(matches!(
node.use_undeclared_parameters().set("int_param", 100),
Err(ParameterValueError::OutOfRange)
));
assert!(matches!(
node.use_undeclared_parameters().set("int_param", -9),
Err(ParameterValueError::OutOfRange)
));
assert!(node
.use_undeclared_parameters()
.set("int_param", -4)
.is_ok());
let range = ParameterRange {
lower: Some(-10.0),
upper: Some(10.0),
step: Some(3.0),
};
assert!(matches!(
node.declare_parameter("out_of_range_double")
.default(100.0)
.range(range.clone())
.optional(),
Err(DeclarationError::InitialValueOutOfRange)
));
assert!(matches!(
node.declare_parameter("wrong_step_double")
.default(-9.0)
.range(range.clone())
.read_only(),
Err(DeclarationError::InitialValueOutOfRange)
));
let param = node
.declare_parameter("double_param")
.default(-7.0)
.range(range.clone())
.mandatory()
.unwrap();
assert!(param.set(10.0).is_ok());
assert!(matches!(
param.set(-7.001),
Err(ParameterValueError::OutOfRange)
));
assert!(param.set(-7.0 - f64::EPSILON * 10.0).is_ok());
assert!(param.set(-7.0 + f64::EPSILON * 10.0).is_ok());
assert!(param.set(10.0 - f64::EPSILON * 10.0).is_ok());
assert!(param.set(10.0 + f64::EPSILON * 10.0).is_ok());
assert!(param.set(-10.0 - f64::EPSILON * 10.0).is_ok());
assert!(param.set(-10.0 + f64::EPSILON * 10.0).is_ok());
assert!(matches!(
node.use_undeclared_parameters().set("double_param", 100.0),
Err(ParameterValueError::OutOfRange)
));
assert!(matches!(
node.use_undeclared_parameters().set("double_param", -9.0),
Err(ParameterValueError::OutOfRange)
));
assert!(node
.use_undeclared_parameters()
.set("double_param", -4.0)
.is_ok());
}
#[test]
fn test_readonly_parameters() {
let node = Context::default()
.create_basic_executor()
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
let param = node
.declare_parameter("int_param")
.default(100)
.read_only()
.unwrap();
assert!(matches!(
node.declare_parameter("int_param").default(100).read_only(),
Err(DeclarationError::AlreadyDeclared)
));
assert_eq!(param.get(), 100);
assert_eq!(
node.use_undeclared_parameters().get::<i64>("int_param"),
Some(100)
);
assert!(matches!(
node.use_undeclared_parameters().set("int_param", 10),
Err(ParameterValueError::ReadOnly)
));
}
#[test]
fn test_preexisting_value_error() {
let node = Context::default()
.create_basic_executor()
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
node.use_undeclared_parameters()
.set("int_param", 100)
.unwrap();
assert!(matches!(
node.declare_parameter("int_param").default(1.0).mandatory(),
Err(DeclarationError::PriorValueTypeMismatch)
));
let range = ParameterRange {
lower: Some(-10),
upper: Some(10),
step: Some(3),
};
assert!(matches!(
node.declare_parameter("int_param")
.default(-1)
.range(range.clone())
.discriminate(|available| { available.prior_value })
.mandatory(),
Err(DeclarationError::InitialValueOutOfRange)
));
{
let param = node
.declare_parameter("int_param")
.default(1.0)
.discard_mismatching_prior_value()
.mandatory()
.unwrap();
assert_eq!(param.get(), 1.0);
}
{
node.use_undeclared_parameters()
.set("int_param", 100)
.unwrap();
let param = node
.declare_parameter("int_param")
.default(5)
.range(range)
.mandatory()
.unwrap();
assert_eq!(param.get(), 5);
}
}
#[test]
fn test_optional_parameter_apis() {
let node = Context::default()
.create_basic_executor()
.create_node(&format!("param_test_node_{}", line!()))
.unwrap();
node.declare_parameter::<i64>("int_param")
.optional()
.unwrap();
}
}