use std::{
ffi::CString,
os::raw::c_char,
string::String,
sync::{Arc, Mutex},
vec::Vec,
};
use crate::{rcl_bindings::*, Executor, ExecutorRuntime, LoggingLifecycle, RclrsError, ToResult};
pub(crate) static ENTITY_LIFECYCLE_MUTEX: Mutex<()> = Mutex::new(());
impl Drop for rcl_context_t {
fn drop(&mut self) {
unsafe {
if rcl_context_is_valid(self) {
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
rcl_shutdown(self);
rcl_context_fini(self);
}
}
}
}
unsafe impl Send for rcl_context_t {}
#[derive(Clone)]
pub struct Context {
pub(crate) handle: Arc<ContextHandle>,
}
impl std::fmt::Debug for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Context")
.field("handle", &self.handle.rcl_context.lock())
.finish()
}
}
pub(crate) struct ContextHandle {
pub(crate) rcl_context: Mutex<rcl_context_t>,
#[allow(unused)]
logging: Arc<LoggingLifecycle>,
}
impl Default for Context {
fn default() -> Self {
Self::new([], InitOptions::default()).expect("Failed to instantiate a default context")
}
}
impl Context {
pub fn new(
args: impl IntoIterator<Item = String>,
options: InitOptions,
) -> Result<Self, RclrsError> {
let mut rcl_context = unsafe { rcl_get_zero_initialized_context() };
let cstring_args: Vec<CString> = args
.into_iter()
.map(|arg| {
CString::new(arg.as_str()).map_err(|err| RclrsError::StringContainsNul {
err,
s: arg.clone(),
})
})
.collect::<Result<_, _>>()?;
let c_args: Vec<*const c_char> = cstring_args.iter().map(|arg| arg.as_ptr()).collect();
unsafe {
let allocator = rcutils_get_default_allocator();
let mut rcl_init_options = options.into_rcl(allocator)?;
let ret = {
let _lifecycle_lock = ENTITY_LIFECYCLE_MUTEX.lock().unwrap();
rcl_init(
c_args.len() as i32,
if c_args.is_empty() {
std::ptr::null()
} else {
c_args.as_ptr()
},
&rcl_init_options,
&mut rcl_context,
)
.ok()
};
rcl_init_options_fini(&mut rcl_init_options).ok()?;
ret?;
}
let logging = unsafe { LoggingLifecycle::configure(&rcl_context)? };
Ok(Self {
handle: Arc::new(ContextHandle {
rcl_context: Mutex::new(rcl_context),
logging,
}),
})
}
pub fn from_env(options: InitOptions) -> Result<Self, RclrsError> {
Self::new(std::env::args(), options)
}
pub fn default_from_env() -> Result<Self, RclrsError> {
Self::new(std::env::args(), InitOptions::default())
}
pub fn create_executor<E>(&self, runtime: E) -> Executor
where
E: 'static + ExecutorRuntime + Send,
{
Executor::new(Arc::clone(&self.handle), runtime)
}
pub fn domain_id(&self) -> usize {
let mut domain_id: usize = 0;
let ret = unsafe {
rcl_context_get_domain_id(
&mut *self.handle.rcl_context.lock().unwrap(),
&mut domain_id,
)
};
debug_assert_eq!(ret, 0);
domain_id
}
pub fn ok(&self) -> bool {
let rcl_context = &mut *self.handle.rcl_context.lock().unwrap();
unsafe { rcl_context_is_valid(rcl_context) }
}
}
#[derive(Default, Clone)]
pub struct InitOptions {
domain_id: Option<usize>,
}
impl InitOptions {
pub fn new() -> InitOptions {
Self::default()
}
pub fn with_domain_id(mut self, domain_id: Option<usize>) -> InitOptions {
self.domain_id = domain_id;
self
}
pub fn set_domain_id(&mut self, domain_id: Option<usize>) {
self.domain_id = domain_id;
}
pub fn domain_id(&self) -> Option<usize> {
self.domain_id
}
fn into_rcl(self, allocator: rcutils_allocator_s) -> Result<rcl_init_options_t, RclrsError> {
unsafe {
let mut rcl_init_options = rcl_get_zero_initialized_init_options();
rcl_init_options_init(&mut rcl_init_options, allocator).ok()?;
if let Some(domain_id) = self.domain_id {
rcl_init_options_set_domain_id(&mut rcl_init_options, domain_id);
}
Ok(rcl_init_options)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn traits() {
use crate::test_helpers::*;
assert_send::<Context>();
assert_sync::<Context>();
}
#[test]
fn test_create_context() -> Result<(), RclrsError> {
let _ = Context::new(vec![], InitOptions::default())?;
Ok(())
}
#[test]
fn test_context_ok() -> Result<(), RclrsError> {
let created_context = Context::new(vec![], InitOptions::default()).unwrap();
assert!(created_context.ok());
Ok(())
}
}