use std::{marker::PhantomData, ops::{Deref, DerefMut}};
use std::sync::{Mutex, MutexGuard};
use std::ffi::c_char;
use crate::mujoco_c::{mj_version, mjVERSION_HEADER};
pub(crate) const ERROR_BUF_LEN: usize = 100;
pub(crate) fn write_ascii_to_buf(buf: &mut [c_char], value: &str) {
assert!(value.is_ascii(), "value must be valid ASCII");
let c_string = std::ffi::CString::new(value).unwrap();
let bytes = c_string.into_bytes_with_nul();
let dest: &mut [u8] = bytemuck::cast_slice_mut(buf);
dest[..bytes.len()].copy_from_slice(&bytes);
dest[bytes.len()..].fill(0);
}
pub(crate) fn optional_sparse_addr_range<T>(addr_array: &[T], id: usize, data_len: usize) -> Option<(usize, usize)>
where
T: Into<i64> + Copy,
{
let adr: i64 = addr_array[id].into();
if adr < 0 {
return None;
}
let adr = adr as usize;
let len = addr_array
.get(id + 1..)
.unwrap_or(&[])
.iter()
.find(|&&next| next.into() != -1)
.map(|&next| next.into() as usize)
.unwrap_or(data_len)
- adr;
Some((adr, len))
}
#[doc(hidden)]
#[macro_export]
macro_rules! set_flag {
($flags:expr, $mask:expr, $enabled:expr) => {
if $enabled {
$flags |= $mask;
} else {
$flags &= !$mask;
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! mj_model_dyn_range {
($model:expr, $id:expr, nq) => {
$crate::util::optional_sparse_addr_range($model.jnt_qposadr(), $id, $model.nq() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, nv) => {
$crate::util::optional_sparse_addr_range($model.jnt_dofadr(), $id, $model.nv() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, nsensordata) => {
$crate::util::optional_sparse_addr_range($model.sensor_adr(), $id, $model.nsensordata() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, ntupledata) => {
$crate::util::optional_sparse_addr_range($model.tuple_adr(), $id, $model.ntupledata() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, ntexdata) => {
$crate::util::optional_sparse_addr_range($model.tex_adr(), $id, $model.ntexdata() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, nnumericdata) => {
$crate::util::optional_sparse_addr_range($model.numeric_adr(), $id, $model.nnumericdata() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, nhfielddata) => {
$crate::util::optional_sparse_addr_range($model.hfield_adr(), $id, $model.nhfielddata() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, na) => {
$crate::util::optional_sparse_addr_range($model.actuator_actadr(), $id, $model.na() as usize).unwrap_or((0, 0))
};
($model:expr, $id:expr, nJten) => {
$crate::util::optional_sparse_addr_range($model.ten_j_rowadr(), $id, $model.n_jten() as usize).unwrap_or((0, 0))
};
}
#[derive(Debug)]
pub struct PointerViewMut<'d, T> {
ptr: *mut T,
len: usize,
phantom: PhantomData<&'d mut ()>
}
impl<'d, T> PointerViewMut<'d, T> {
pub(crate) const fn new(ptr: *mut T, len: usize, phantom: PhantomData<&'d mut ()>) -> Self {
Self {ptr, len, phantom}
}
}
impl<T> PartialEq for PointerViewMut<'_, T> {
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr && self.len == other.len
}
}
impl<T> Eq for PointerViewMut<'_, T> {}
impl<T> Deref for PointerViewMut<'_, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
if self.ptr.is_null() {
return &[];
}
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
impl<T> DerefMut for PointerViewMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
if self.ptr.is_null() {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
}
}
#[derive(Debug)]
pub struct PointerViewUnsafeMut<'d, T> {
ptr: *mut T,
len: usize,
phantom: PhantomData<&'d mut ()>
}
impl<'d, T> PointerViewUnsafeMut<'d, T> {
pub(crate) const fn new(ptr: *mut T, len: usize, phantom: PhantomData<&'d mut ()>) -> Self {
Self { ptr, len, phantom }
}
pub unsafe fn as_mut_slice(&mut self) -> &mut [T] {
if self.ptr.is_null() {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
}
}
impl<T> PartialEq for PointerViewUnsafeMut<'_, T> {
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr && self.len == other.len
}
}
impl<T> Eq for PointerViewUnsafeMut<'_, T> {}
impl<T> Deref for PointerViewUnsafeMut<'_, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
if self.ptr.is_null() {
return &[];
}
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
#[derive(Debug)]
pub struct PointerView<'d, T> {
ptr: *const T,
len: usize,
phantom: PhantomData<&'d ()>
}
impl<'d, T> PointerView<'d, T> {
pub(crate) const fn new(ptr: *const T, len: usize, phantom: PhantomData<&'d ()>) -> Self {
Self {ptr, len, phantom}
}
}
impl<T> PartialEq for PointerView<'_, T> {
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr && self.len == other.len
}
}
impl<T> Eq for PointerView<'_, T> {}
impl<T> Deref for PointerView<'_, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
if self.ptr.is_null() {
return &[];
}
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! eval_or_expand {
(@eval $(true)? { $($data:tt)* } ) => { $($data)* };
(@eval false { $($data:tt)* } ) => {};
}
#[macro_export]
#[doc(hidden)]
macro_rules! view_creator {
(
$self:expr, $view:ident, $data:expr,
[$($([$prefix_field:ident])? $field:ident : $type_:ty $([$force:ident])?),*],
[$($([$prefix_field_ro:ident])? $field_ro:ident : $type_ro:ty $([$force_ro:ident])?),*],
[$($([$prefix_opt_field:ident])? $opt_field:ident : $type_opt:ty $([$force_opt:ident])?),*],
$ptr_view:expr,
$ptr_view_ro:expr
) => {
paste::paste! {
unsafe {
$view {
$(
$field: $ptr_view(
$crate::maybe_force_cast!($data.[<$($prefix_field)? $field>].add($self.$field.0), $type_ $(, $force)?),
$self.$field.1,
std::marker::PhantomData
),
)*
$(
$field_ro: $ptr_view_ro(
$crate::maybe_force_cast!($data.[<$($prefix_field_ro)? $field_ro>].add($self.$field_ro.0), $type_ro $(, $force_ro)?),
$self.$field_ro.1,
std::marker::PhantomData
),
)*
$(
$opt_field: if $self.$opt_field.1 > 0 {
Some($ptr_view(
$crate::maybe_force_cast!($data.[<$($prefix_opt_field)? $opt_field>].add($self.$opt_field.0), $type_opt $(, $force_opt)?),
$self.$opt_field.1,
std::marker::PhantomData
))
} else {
None
},
)*
}
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! info_method {
(
$info_type:ident, $([$model:ident],)?
$type_:ident,
[$($attr:ident: $len:expr),*],
[$($attr_ffi:ident: $len_ffi:ident $(* $multiplier:expr)?),*],
[$($attr_dyn:ident: $ffi_len_dyn:ident $(* $offset_mult:expr)?),*]
) => {
paste::paste! {
#[doc = concat!(
"Returns a [`", stringify!([<Mj $type_:camel $info_type Info>]), "`] for the named ", stringify!($type_), ", ",
"containing the indices required to create views into [`Mj", stringify!($info_type), "`] arrays.\n\n",
"Call [`view`](", stringify!([<Mj $type_:camel $info_type Info>]), "::view) or ",
"[`try_view`](", stringify!([<Mj $type_:camel $info_type Info>]), "::try_view) on the result to obtain the actual view.\n\n",
"# Panics\n",
"Panics if `name` contains a `\\0` byte."
)]
#[allow(non_snake_case)]
pub fn $type_(&self, name: &str) -> Option<[<Mj $type_:camel $info_type Info>]> {
let model_ref = self$(.$model())?;
let id = model_ref.name_to_id(MjtObj::[<mjOBJ_ $type_:upper>], name)?;
let model_ffi = model_ref.ffi();
let id = id as usize;
$(
let $attr = (id * $len, $len);
)*
$(
let $attr_ffi = (
id * model_ffi.$len_ffi as usize $( * $multiplier)*,
model_ffi.$len_ffi as usize $( * $multiplier)*,
);
)*
$(
let $attr_dyn = {
let (dyn_start, dyn_len) = $crate::mj_model_dyn_range!(model_ref, id, $ffi_len_dyn);
(dyn_start $(* $offset_mult)?, dyn_len $(* $offset_mult)?)
};
)*
let model_signature = model_ffi.signature;
Some([<Mj $type_:camel $info_type Info>] { name: name.to_string(), id, model_signature, $($attr,)* $($attr_ffi,)* $($attr_dyn),* })
}
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! info_with_view {
(
$info_type:ident, $name:ident,
[$($([$prefix_attr:ident])? $attr:ident: $type_:ty $([$force:ident])?),*],
[$($([$prefix_attr_ro:ident])? $attr_ro:ident: $type_ro:ty $([$force_ro:ident])?),*],
[$($([$prefix_opt_attr:ident])? $opt_attr:ident: $type_opt:ty $([$force_opt:ident])?),*]
$(,$generics:ty: $bound:ty)?
) => {
paste::paste! {
#[doc = "Index ranges required to create views into [`Mj" $info_type "`] arrays for a " $name "."]
#[allow(non_snake_case)]
#[derive(Debug, Clone)]
pub struct [<Mj $name:camel $info_type Info>] {
pub name: String,
pub id: usize,
model_signature: u64,
$(
$attr: (usize, usize),
)*
$(
$attr_ro: (usize, usize),
)*
$(
$opt_attr: (usize, usize),
)*
}
impl [<Mj $name:camel $info_type Info>] {
pub fn model_signature(&self) -> u64 {
self.model_signature
}
#[doc = concat!(
"Returns a mutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
"Fields listed as read-only use [`PointerViewUnsafeMut`](crate::util::PointerViewUnsafeMut): ",
"read is safe, mutation requires [`as_mut_slice`](crate::util::PointerViewUnsafeMut::as_mut_slice) and `unsafe`.\n\n",
"# Errors\n",
"Returns [`SignatureMismatch`](", stringify!([<Mj $info_type Error>]), "::SignatureMismatch) if `",
stringify!($info_type), "` was built from a different model than this `Info`."
)]
pub fn try_view_mut<'p $(, $generics: $bound)?>(&self, [<$info_type:lower>]: &'p mut [<Mj $info_type>]$(<$generics>)?) -> Result<[<Mj $name:camel $info_type ViewMut>]<'p>, $crate::error::[<Mj $info_type Error>]> {
let destination_signature = [<$info_type:lower>].signature();
if self.model_signature != destination_signature {
return Err($crate::error::[<Mj $info_type Error>]::SignatureMismatch {
source: self.model_signature,
destination: destination_signature,
});
}
Ok(view_creator!(self, [<Mj $name:camel $info_type ViewMut>], [<$info_type:lower>].ffi(),
[$($([$prefix_attr])? $attr : $type_ $([$force])?),*],
[$($([$prefix_attr_ro])? $attr_ro : $type_ro $([$force_ro])?),*],
[$($([$prefix_opt_attr])? $opt_attr : $type_opt $([$force_opt])?),*],
$crate::util::PointerViewMut::new,
$crate::util::PointerViewUnsafeMut::new))
}
#[doc = concat!(
"Returns a mutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
"Fields listed as read-only use [`PointerViewUnsafeMut`](crate::util::PointerViewUnsafeMut): ",
"read is safe, mutation requires [`as_mut_slice`](crate::util::PointerViewUnsafeMut::as_mut_slice) and `unsafe`.\n\n",
"# Panics\n",
"Panics if `", stringify!($info_type), "` was built from a different model than this `Info`. ",
"Use [`try_view_mut`](Self::try_view_mut) to handle this as a `Result`."
)]
pub fn view_mut<'p $(, $generics: $bound)?>(&self, [<$info_type:lower>]: &'p mut [<Mj $info_type>]$(<$generics>)?) -> [<Mj $name:camel $info_type ViewMut>]<'p> {
self.try_view_mut([<$info_type:lower>]).unwrap_or_else(|_| panic!("model signature mismatch"))
}
#[doc = concat!(
"Returns an immutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
"# Errors\n",
"Returns [`SignatureMismatch`](", stringify!([<Mj $info_type Error>]), "::SignatureMismatch) if `",
stringify!($info_type), "` was built from a different model than this `Info`."
)]
pub fn try_view<'p $(, $generics: $bound)?>(&self, [<$info_type:lower>]: &'p [<Mj $info_type>]$(<$generics>)?) -> Result<[<Mj $name:camel $info_type View>]<'p>, $crate::error::[<Mj $info_type Error>]> {
let destination_signature = [<$info_type:lower>].signature();
if self.model_signature != destination_signature {
return Err($crate::error::[<Mj $info_type Error>]::SignatureMismatch {
source: self.model_signature,
destination: destination_signature,
});
}
Ok(view_creator!(self, [<Mj $name:camel $info_type View>], [<$info_type:lower>].ffi(),
[$($([$prefix_attr])? $attr : $type_ $([$force])?),*],
[$($([$prefix_attr_ro])? $attr_ro : $type_ro $([$force_ro])?),*],
[$($([$prefix_opt_attr])? $opt_attr : $type_opt $([$force_opt])?),*],
$crate::util::PointerView::new,
$crate::util::PointerView::new))
}
#[doc = concat!(
"Returns an immutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
"# Panics\n",
"Panics if `", stringify!($info_type), "` was built from a different model than this `Info`. ",
"Use [`try_view`](Self::try_view) to handle this as a `Result`."
)]
pub fn view<'p $(, $generics: $bound)?>(&self, [<$info_type:lower>]: &'p [<Mj $info_type>]$(<$generics>)?) -> [<Mj $name:camel $info_type View>]<'p> {
self.try_view([<$info_type:lower>]).unwrap_or_else(|_| panic!("model signature mismatch"))
}
}
#[doc = "Mutable view into [`Mj" $info_type "`] arrays for a " $name ".\n\n"
"Read-write fields are [`PointerViewMut`](crate::util::PointerViewMut); "
"read-only fields are [`PointerViewUnsafeMut`](crate::util::PointerViewUnsafeMut), "
"which require [`as_mut_slice`](crate::util::PointerViewUnsafeMut::as_mut_slice) and explicit `unsafe` to mutate."]
#[allow(non_snake_case)]
#[derive(Debug)]
pub struct [<Mj $name:camel $info_type ViewMut>]<'d> {
$(
#[doc = concat!("Mutable view of `", stringify!($attr), "`.")]
pub $attr: $crate::util::PointerViewMut<'d, $type_>,
)*
$(
#[doc = concat!("Read-only view of `", stringify!($attr_ro), "`. Requires `unsafe` for mutation.")]
pub $attr_ro: $crate::util::PointerViewUnsafeMut<'d, $type_ro>,
)*
$(
#[doc = concat!("Optional mutable view of `", stringify!($opt_attr), "`.")]
pub $opt_attr: Option<$crate::util::PointerViewMut<'d, $type_opt>>,
)*
}
impl [<Mj $name:camel $info_type ViewMut>]<'_> {
pub fn zero(&mut self) {
$(
self.$attr.fill(bytemuck::Zeroable::zeroed());
)*
$(
if let Some(x) = &mut self.$opt_attr {
x.fill(bytemuck::Zeroable::zeroed());
}
)*
}
}
#[doc = "Immutable view into [`Mj" $info_type "`] arrays for a " $name "."]
#[allow(non_snake_case)]
#[derive(Debug)]
pub struct [<Mj $name:camel $info_type View>]<'d> {
$(
#[doc = concat!("View of `", stringify!($attr), "`.")]
pub $attr: $crate::util::PointerView<'d, $type_>,
)*
$(
#[doc = concat!("View of `", stringify!($attr_ro), "`.")]
pub $attr_ro: $crate::util::PointerView<'d, $type_ro>,
)*
$(
#[doc = concat!("Optional view of `", stringify!($opt_attr), "`.")]
pub $opt_attr: Option<$crate::util::PointerView<'d, $type_opt>>,
)*
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! getter_setter {
(@cast force { $($content:tt)* } ) => {
$crate::util::force_cast($($content)*)
};
(@cast { $($content:tt)* } ) => {
$($content)*.into()
};
(get, [$($([$ffi:ident])? $name:ident $(+ $symbol:tt)?: bool; $comment:expr);* $(;)?]) => {paste::paste!{
$(
#[doc = concat!("Check ", $comment)]
pub fn [<$name:camel:snake $($symbol)?>](&self) -> bool {
(self$(.$ffi())?.$name as i32) != 0
}
)*
}};
(get, [$($([$ffi:ident $(,$ffi_mut:ident)?])? $((allow_mut = $cfg_mut:literal))? $name:ident $(+ $symbol:tt)?: & $type:ty; $comment:expr);* $(;)?]) => {paste::paste!{
$(
#[doc = concat!("Return an immutable reference to ", $comment)]
pub fn [<$name:camel:snake $($symbol)?>](&self) -> &$type {
&self$(.$ffi())?.$name
}
$crate::eval_or_expand! {
@eval $($cfg_mut)? {
#[doc = concat!("Return a mutable reference to ", $comment)]
pub fn [<$name:camel:snake _mut>](&mut self) -> &mut $type {
#[allow(unused_unsafe)]
unsafe { &mut self$(.$($ffi_mut())?)?.$name }
}
}
}
)*
}};
(get, [$($([$ffi:ident])? $name:ident $(+ $symbol:tt)?: $type:ty $([$cast_type:tt])?; $comment:expr);* $(;)?]) => {paste::paste!{
$(
#[doc = concat!("Return value of ", $comment)]
pub fn [<$name:camel:snake $($symbol)?>](&self) -> $type {
#[allow(unused_unsafe)]
unsafe { $crate::getter_setter!(@cast $($cast_type)? { self$(.$ffi())?.$name }) }
}
)*
}};
(set, [$($([$ffi_mut:ident])? $name:ident: $type:ty $([$cast_type:tt])? $({$check:expr , $reason:literal})? $(=> $err:ty)?; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[doc = concat!("Set ", $comment $(, "\n\n# Errors\nReturns ", $reason, ".")?)]
pub fn [<set_ $name:camel:snake>](&mut self, value: $type) $(-> Result<(), $err>)? {
$(($check)(value)?;)?
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = $crate::getter_setter!(@cast $($cast_type)? { value }) };
$(Ok::<(), $err>(()))?
}
)*
}
};
(with, [$($inner:tt)*]) => {
$crate::getter_setter!(@with_body [Self], [$($inner)*]);
};
([&] with, [$($inner:tt)*]) => {
$crate::getter_setter!(@with_body [&mut Self], [$($inner)*]);
};
(@with_body [$ret_ty:ty], [$($([$ffi_mut:ident])? $name:ident: $type:ty $([$cast_type:tt])? $({$check:expr , $reason:literal})?; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[allow(unused_mut)]
#[doc = concat!("Builder method for setting ", $comment $(, "\n\n# Panics\nPanics with ", $reason, ".")?)]
pub fn [<with_ $name:camel:snake>](mut self: $ret_ty, value: $type) -> $ret_ty {
$(($check)(value).expect("invalid builder argument");)?
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = $crate::getter_setter!(@cast $($cast_type)? { value }) };
self
}
)*
}
};
(get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : bool ; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)? : bool ; $comment );* ]);
$crate::getter_setter!(set, [ $($([$ffi_mut])? $name : bool ; $comment );* ]);
};
(get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : $type:ty $([$cast_type:tt])? $({$check:expr , $reason:literal})? $(=> $err:ty)?; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)? : $type $([$cast_type])? ; $comment );* ]);
$crate::getter_setter!(set, [ $($([$ffi_mut])? $name : $type $([$cast_type])? $({$check , $reason})? $(=> $err)?; $comment );* ]);
};
($([$token:tt])? with, get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : bool ; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)? : bool ; $comment );* ]);
$crate::getter_setter!(set, [ $($([$ffi_mut])? $name : bool ; $comment );* ]);
$crate::getter_setter!($([$token])? with, [ $($([$ffi_mut])? $name : bool ; $comment );* ]);
};
($([$token:tt])? with, get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : $type:ty $([$cast_type:tt])? $({$check:expr , $reason:literal})? $(=> $err:ty)?; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)?: $type $([$cast_type])? ; $comment );* ]);
$crate::getter_setter!(set, [ $($([$ffi_mut])? $name : $type $([$cast_type])? $({$check , $reason})? $(=> $err)?; $comment );* ]);
$crate::getter_setter!($([$token])? with, [ $($([$ffi_mut])? $name : $type $([$cast_type])? $({$check , $reason})?; $comment );* ]);
};
($([$token:tt])? with, get, [$( $([$ffi: ident, $ffi_mut:ident])? $((allow_mut = $allow_mut:literal))? $name:ident $(+ $symbol:tt)? : & $type:ty $({$check:expr , $reason:literal})? ; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(get, [ $($([$ffi, $ffi_mut])? $((allow_mut = $allow_mut))? $name $(+ $symbol)? : & $type ; $comment );* ]);
$crate::getter_setter!($([$token])? with, [ $( $([$ffi_mut])? $name : $type $({$check , $reason})? ; $comment );* ]);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! builder_setters {
($($name:ident: $type:ty $(where $generic_type:ident: $generic:path)?; $comment:expr);* $(;)?) => {
$(
#[doc = concat!("Set ", $comment)]
pub fn $name$(<$generic_type: $generic>)?(mut self, value: $type) -> Self {
self.$name = value.into();
self
}
)*
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! array_mut_doc {
(unsafe, $doc:literal) => {
concat!("Mutable slice of the ", $doc, " array.\n\n# Safety\n\nDirect mutation of this array bypasses MuJoCo's internal consistency checks. The caller must ensure that all values written remain valid for MuJoCo's internal state.")
};
($doc:literal) => {
concat!("Mutable slice of the ", $doc, " array.")
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! array_slice_dyn {
($($((mut = $unsafe_mut:ident))? $name:ident: $($as_ptr:ident $as_mut_ptr:ident)? &[$type:ty $([$force:ident])?; $doc:literal; $($len_accessor:tt)*]),*) => {
paste::paste! {
$(
#[doc = concat!("Immutable slice of the ", $doc," array.")]
pub fn [<$name:camel:snake>](&self) -> &[$type] {
let length = self.$($len_accessor)* as usize;
let ptr = $crate::maybe_force_cast!(self.ffi().$name$(.$as_ptr())?, $type $(, $force)?);
if ptr.is_null() || length == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts(ptr, length) }
}
#[doc = $crate::array_mut_doc!($($unsafe_mut,)? $doc)]
pub $($unsafe_mut)? fn [<$name:camel:snake _mut>](&mut self) -> &mut [$type] {
let length = self.$($len_accessor)* as usize;
let ptr = $crate::maybe_force_cast!(unsafe { self.ffi_mut().$name$(.$as_mut_ptr())? }, $type $(, $force)?);
if ptr.is_null() || length == 0 {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut(ptr, length) }
}
)*
}
};
(summed { $( $(($unsafe_mut:ident))? $name:ident: &[$type:ty; $doc:literal; [$multiplier:literal ; ($($len_array:tt)*) ; ($($len_array_length:tt)*)]]),* }) => {
paste::paste! {
$(
#[doc = concat!("Immutable slice of the ", $doc," array.")]
pub fn [<$name:camel:snake>](&self) -> &[[$type; $multiplier]] {
let length_array_length = self.$($len_array_length)* as usize;
let data_ptr = self.ffi().$name;
let length_ptr = self.$($len_array)*;
if data_ptr.is_null() || length_ptr.is_null() || length_array_length == 0 {
return &[];
}
let length = unsafe { std::slice::from_raw_parts(
length_ptr,
length_array_length
).iter().map(|&x| x as u32).sum::<u32>() as usize };
if length == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts($crate::maybe_force_cast!(data_ptr, [$type; $multiplier], force), length) }
}
#[doc = $crate::array_mut_doc!($($unsafe_mut,)? $doc)]
pub $($unsafe_mut)? fn [<$name:camel:snake _mut>](&mut self) -> &mut [[$type; $multiplier]] {
let length_array_length = self.$($len_array_length)* as usize;
let data_ptr = unsafe { self.ffi_mut().$name };
let length_ptr = self.$($len_array)*;
if data_ptr.is_null() || length_ptr.is_null() || length_array_length == 0 {
return &mut [];
}
let length = unsafe { std::slice::from_raw_parts(
length_ptr,
length_array_length
).iter().map(|&x| x as u32).sum::<u32>() as usize };
if length == 0 {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut($crate::maybe_force_cast!(data_ptr, [$type; $multiplier], force), length) }
}
)*
}
};
(sublen_dep {$( $(($unsafe_mut:ident))? $name:ident: $($as_ptr:ident $as_mut_ptr:ident)? &[[$type:ty; $($inner_len_accessor:tt)*] $([$force:ident])?; $doc:literal; $($len_accessor:tt)*]),*}) => {
paste::paste! {
$(
#[doc = concat!("Immutable slice of the ", $doc," array.")]
pub fn [<$name:camel:snake>](&self) -> &[$type] {
let length = self.$($len_accessor)* as usize * (self.$($inner_len_accessor)*) as usize;
let ptr = $crate::maybe_force_cast!(self.ffi().$name$(.$as_ptr())?, $type $(, $force)?);
if ptr.is_null() || length == 0 {
return &[];
}
unsafe { std::slice::from_raw_parts(ptr, length) }
}
#[doc = $crate::array_mut_doc!($($unsafe_mut,)? $doc)]
pub $($unsafe_mut)? fn [<$name:camel:snake _mut>](&mut self) -> &mut [$type] {
let length = self.$($len_accessor)* as usize * (self.$($inner_len_accessor)*) as usize;
let ptr = $crate::maybe_force_cast!(unsafe { self.ffi_mut().$name$(.$as_mut_ptr())? }, $type $(, $force)?);
if ptr.is_null() || length == 0 {
return &mut [];
}
unsafe { std::slice::from_raw_parts_mut(ptr, length) }
}
)*
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! c_str_as_str_method {
(get {$($([$ffi:ident])? $name:ident $([$sub_index_name:ident: $sub_index_type:ty])?; $comment:literal; )*}) => {
$(
#[doc = concat!("Returns ", $comment, "\n\n# Panics", "\nPanics if the buffer has no NUL terminator or if the resulting string contains invalid UTF-8.")]
pub fn $name(&self $(, $sub_index_name: $sub_index_type)? ) -> &str {
let bytes: &[u8] = bytemuck::cast_slice(&self$(.$ffi())?.$name$([$sub_index_name])?[..]);
std::ffi::CStr::from_bytes_until_nul(bytes)
.expect("no NUL terminator in C string buffer")
.to_str().unwrap()
}
)*
};
(set {$($([$ffi:ident])? $name:ident $([$sub_index_name:ident: $sub_index_type:ty])?; $comment:literal; )*}) => {paste::paste!{
$(
#[doc = concat!("Sets ", $comment, "\n\n# Panics", "\nPanics when `", stringify!($name), "` contains invalid ASCII, an interior NUL byte, or is too long.")]
pub fn [<set_ $name>](&mut self, $($sub_index_name: $sub_index_type,)? $name: &str) {
$crate::util::write_ascii_to_buf(
&mut self$(.$ffi())?.$name$([$sub_index_name])?,
$name,
);
}
)*
}};
(with {$($([$ffi:ident])? $name:ident $([$sub_index_name:ident: $sub_index_type:ty])?; $comment:literal; )*}) => {paste::paste!{
$(
#[doc = concat!("Builder method for setting ", $comment, "\n\n# Panics", "\nPanics when `", stringify!($name), "` contains invalid ASCII, an interior NUL byte, or is too long.")]
pub fn [<with_ $name>](mut self, $($sub_index_name: $sub_index_type,)? $name: &str) -> Self {
$crate::util::write_ascii_to_buf(
&mut self$(.$ffi())?.$name$([$sub_index_name])?,
$name,
);
self
}
)*
}};
(with, get, set {$($other:tt)*}) => {
$crate::c_str_as_str_method!(get {$($other)*});
$crate::c_str_as_str_method!(set {$($other)*});
$crate::c_str_as_str_method!(with {$($other)*});
};
(get, set {$($other:tt)*}) => {
$crate::c_str_as_str_method!(get {$($other)*});
$crate::c_str_as_str_method!(set {$($other)*});
};
(with, set {$($other:tt)*}) => {
$crate::c_str_as_str_method!(set {$($other)*});
$crate::c_str_as_str_method!(with {$($other)*});
};
(with, get {$($other:tt)*}) => {
$crate::c_str_as_str_method!(get {$($other)*});
$crate::c_str_as_str_method!(with {$($other)*});
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! assert_relative_eq {
($a:expr, $b:expr, epsilon = $eps:expr) => {{
let (a, b, eps) = ($a as f64, $b as f64, $eps as f64);
assert!((a - b).abs() <= eps, "left={:?} right={:?} eps={:?}", a, b, eps);
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! cast_mut_info {
($value:expr $(, $debug_expr:expr)?) => {
{
match bytemuck::checked::try_cast_mut($value) {
Ok(v) => v,
Err(e) => {
let evaluated = format!("{:?}", $value);
#[allow(unused)]
let mut debug_info = String::new();
$(
debug_info = format!(" (debug info: '{} = {}')", stringify!($debug_expr), $debug_expr);
)?
panic!(
"failed to cast expression '{}', which evaluates to '{}' into requested type (error: {})\
{debug_info} --- \
most likely you have a bug in your program.",
stringify!($value), evaluated, e
);
}
}
}
};
}
pub fn assert_mujoco_version() {
let linked_version = unsafe { mj_version() as u32 };
let mujoco_rs_version_string = option_env!("CARGO_PKG_VERSION").unwrap_or_else(|| "unknown+mj-unknown");
assert_eq!(
linked_version, mjVERSION_HEADER,
"linked MuJoCo version value ({linked_version}) does not match expected version value ({mjVERSION_HEADER}), \
with which MuJoCo-rs {mujoco_rs_version_string} FFI bindings were generated.",
);
}
#[inline(always)]
pub unsafe fn force_cast<T, U>(val: T) -> U {
const {
assert!(std::mem::size_of::<T>() == std::mem::size_of::<U>());
assert!(std::mem::align_of::<T>() == std::mem::align_of::<U>());
}
#[repr(C)]
union Transmuter<T, U> {
from: std::mem::ManuallyDrop<T>,
to: std::mem::ManuallyDrop<U>,
}
unsafe { std::mem::ManuallyDrop::into_inner(Transmuter { from: std::mem::ManuallyDrop::new(val) }.to) }
}
#[inline(always)]
pub const fn assert_ptr_cast_valid<Src, Dst>(_ptr: *const Src) {
const {
assert!(std::mem::size_of::<Dst>().is_multiple_of(std::mem::size_of::<Src>()),
"ptr cast: target size must be a multiple of source size");
assert!(std::mem::align_of::<Src>() == std::mem::align_of::<Dst>(),
"ptr cast: source alignment must be == target alignment");
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! maybe_force_cast {
($ptr:expr, $type:ty) => { $ptr };
($ptr:expr, $type:ty, force) => {{
let ptr = $ptr;
$crate::util::assert_ptr_cast_valid::<_, $type>(ptr as *const _);
ptr.cast::<$type>()
}};
}
pub trait LockUnpoison<T> {
fn lock_unpoison(&self) -> MutexGuard<'_, T>;
}
impl<T> LockUnpoison<T> for Mutex<T> {
fn lock_unpoison(&self) -> MutexGuard<'_, T> {
match self.lock() {
Ok(lock) => lock,
Err(e) => {
self.clear_poison();
e.into_inner()
}
}
}
}
#[cfg(feature = "viewer")]
pub(crate) trait ThreeWayMerge {
fn merge(&mut self, other: &mut Self, other_prev: &mut Self);
}
#[cfg(feature = "viewer")]
impl<T: Copy + PartialEq> ThreeWayMerge for T {
#[inline]
fn merge(&mut self, other: &mut Self, other_prev: &mut Self) {
if *other != *other_prev {
*self = *other;
}
*other = *self;
*other_prev = *other;
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use std::ffi::c_char;
use super::LockUnpoison;
#[test]
fn test_lock_unpoison_recovers_poisoned_mutex() {
let mutex = Arc::new(Mutex::new(42_i32));
let mutex_clone = Arc::clone(&mutex);
let _ = std::panic::catch_unwind(move || {
let _guard = mutex_clone.lock().unwrap();
panic!("intentional panic to poison mutex");
});
assert!(mutex.lock().is_err(), "mutex should be poisoned");
let value = *mutex.lock_unpoison();
assert_eq!(value, 42, "inner value must be preserved after unpoison");
assert!(mutex.lock().is_ok(), "mutex should no longer be poisoned");
}
fn non_negative(v: i32) -> Result<(), &'static str> {
if v >= 0 { Ok(()) } else { Err("must be non-negative") }
}
fn first_non_negative(a: [f64; 2]) -> Result<(), &'static str> {
if a[0] >= 0.0 { Ok(()) } else { Err("first must be non-negative") }
}
#[test]
#[allow(clippy::bool_assert_comparison)]
#[allow(dead_code)]
fn test_getter_setter_all_arms() {
use crate::getter_setter as gs;
struct GetBool { flag: i32 }
impl GetBool { gs!(get, [flag: bool; "flag.";]); }
assert_eq!(GetBool { flag: 1 }.flag(), true);
assert_eq!(GetBool { flag: 0 }.flag(), false);
struct GetRef { arr: [f64; 3] }
impl GetRef { gs!(get, [arr: &[f64; 3]; "array.";]); }
let mut gr = GetRef { arr: [1.0, 2.0, 3.0] };
assert_eq!(gr.arr(), &[1.0, 2.0, 3.0]);
gr.arr_mut()[0] = 9.0;
assert_eq!(gr.arr(), &[9.0, 2.0, 3.0]);
struct GetRefNoMut { arr: [f64; 2] }
impl GetRefNoMut { gs!(get, [(allow_mut = false) arr: &[f64; 2]; "array.";]); }
assert_eq!(GetRefNoMut { arr: [4.0, 5.0] }.arr(), &[4.0, 5.0]);
struct GetVal { val: i32 }
impl GetVal { gs!(get, [val + _renamed: i32; "value.";]); }
assert_eq!(GetVal { val: 7 }.val_renamed(), 7);
struct ForceGet { v: i32 }
impl ForceGet { gs!(get, [v: i32 [force]; "v.";]); }
assert_eq!(ForceGet { v: 42 }.v(), 42);
struct Set { a: i32, b: i32 }
impl Set {
gs!(set, [
a: i32; "a.";
b: i32 { non_negative, "`Err` when negative" } => &'static str; "b.";
]);
}
let mut s = Set { a: 0, b: 0 };
s.set_a(5);
assert_eq!(s.a, 5);
assert_eq!(s.set_b(3), Ok(()));
assert_eq!(s.b, 3);
assert_eq!(s.set_b(-1), Err("must be non-negative"));
assert_eq!(s.b, 3);
struct ForceSet { a: i32, b: i32 }
impl ForceSet {
gs!(set, [
a: i32 [force]; "a.";
b: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "b.";
]);
}
let mut fs = ForceSet { a: 0, b: 0 };
fs.set_a(8);
assert_eq!(fs.a, 8);
assert_eq!(fs.set_b(2), Ok(()));
assert_eq!(fs.set_b(-1), Err("must be non-negative"));
assert_eq!(fs.b, 2);
struct With { v: i32 }
impl With { gs!(with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
assert_eq!(With { v: 0 }.with_v(6).v, 6);
struct RefWith { v: i32 }
impl RefWith { gs!([&] with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
let mut rw = RefWith { v: 0 };
rw.with_v(4);
assert_eq!(rw.v, 4);
struct ForceWith { v: i32 }
impl ForceWith { gs!(with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
assert_eq!(ForceWith { v: 0 }.with_v(3).v, 3);
struct ForceRefWith { v: i32 }
impl ForceRefWith { gs!([&] with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
let mut frw = ForceRefWith { v: 0 };
frw.with_v(5);
assert_eq!(frw.v, 5);
struct ForceGetSet { v: i32 }
impl ForceGetSet { gs!(get, set, [v: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "v.";]); }
let mut fgs = ForceGetSet { v: 0 };
assert_eq!(fgs.set_v(11), Ok(()));
assert_eq!(fgs.v(), 11);
assert_eq!(fgs.set_v(-1), Err("must be non-negative"));
struct GetSetBool { flag: i32 }
impl GetSetBool { gs!(get, set, [flag: bool; "flag.";]); }
let mut gsb = GetSetBool { flag: 1 };
assert_eq!(gsb.flag(), true);
gsb.set_flag(false);
assert_eq!(gsb.flag(), false);
struct GetSetVal { count: i32, checked: i32 }
impl GetSetVal {
gs!(get, set, [
count: i32; "count.";
checked: i32 { non_negative, "`Err` when negative" } => &'static str; "checked.";
]);
}
let mut gsv = GetSetVal { count: 5, checked: 0 };
gsv.set_count(10);
assert_eq!(gsv.count(), 10);
assert_eq!(gsv.set_checked(7), Ok(()));
assert_eq!(gsv.checked(), 7);
assert_eq!(gsv.set_checked(-1), Err("must be non-negative"));
assert_eq!(gsv.checked(), 7);
struct WithGetSetBool { flag: i32 }
impl WithGetSetBool { gs!(with, get, set, [flag: bool; "flag.";]); }
assert_eq!(WithGetSetBool { flag: 0 }.with_flag(true).flag(), true);
struct RefWithGetSetBool { flag: i32 }
impl RefWithGetSetBool { gs!([&] with, get, set, [flag: bool; "flag.";]); }
let mut rwgsb = RefWithGetSetBool { flag: 0 };
rwgsb.with_flag(true);
assert_eq!(rwgsb.flag(), true);
struct WithGetSetVal { v: i32 }
impl WithGetSetVal { gs!(with, get, set, [v: i32; "v.";]); }
let mut wgsv = WithGetSetVal { v: 0 }.with_v(3);
assert_eq!(wgsv.v(), 3);
wgsv.set_v(4);
assert_eq!(wgsv.v(), 4);
struct RefWithGetSetVal { v: i32, w: i32 }
impl RefWithGetSetVal {
gs!([&] with, get, set, [
v: i32; "v.";
w: i32 { non_negative, "`Err` when negative" } => &'static str; "w.";
]);
}
let mut rwgsv = RefWithGetSetVal { v: 0, w: 0 };
rwgsv.with_v(1);
assert_eq!(rwgsv.v(), 1);
assert_eq!(rwgsv.set_w(2), Ok(()));
assert_eq!(rwgsv.w(), 2);
assert_eq!(rwgsv.set_w(-1), Err("must be non-negative"));
struct ForceWithGetSet { v: i32 }
impl ForceWithGetSet { gs!(with, get, set, [v: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "v.";]); }
let fwgs = ForceWithGetSet { v: 0 }.with_v(9);
assert_eq!(fwgs.v(), 9);
struct ForceRefWithGetSet { v: i32 }
impl ForceRefWithGetSet { gs!([&] with, get, set, [v: i32 [force]; "v.";]); }
let mut frwgs = ForceRefWithGetSet { v: 0 };
frwgs.with_v(6);
assert_eq!(frwgs.v(), 6);
struct WithGetRef { arr: [f64; 3] }
impl WithGetRef { gs!(with, get, [arr: &[f64; 3]; "array.";]); }
let wgr = WithGetRef { arr: [0.0; 3] }.with_arr([1.0, 2.0, 3.0]);
assert_eq!(wgr.arr(), &[1.0, 2.0, 3.0]);
struct RefWithGetRef { arr: [f64; 2] }
impl RefWithGetRef { gs!([&] with, get, [arr: &[f64; 2] { first_non_negative, "`Err` when the first element is negative" }; "array.";]); }
let mut rwgr = RefWithGetRef { arr: [0.0; 2] };
rwgr.with_arr([5.0, 6.0]);
assert_eq!(rwgr.arr(), &[5.0, 6.0]);
}
#[test]
#[should_panic(expected = "invalid builder argument")]
fn test_with_check_panics() {
struct S { v: i32 }
impl S { crate::getter_setter!(with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
S { v: 0 }.with_v(-1);
}
#[test]
#[should_panic(expected = "invalid builder argument")]
fn test_ref_with_check_panics() {
struct S { v: i32 }
impl S { crate::getter_setter!([&] with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
S { v: 0 }.with_v(-1);
}
#[test]
#[should_panic(expected = "invalid builder argument")]
fn test_force_with_check_panics() {
struct S { v: i32 }
impl S { crate::getter_setter!(with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
S { v: 0 }.with_v(-1);
}
#[test]
#[should_panic(expected = "invalid builder argument")]
fn test_force_ref_with_check_panics() {
struct S { v: i32 }
impl S { crate::getter_setter!([&] with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
S { v: 0 }.with_v(-1);
}
#[test]
#[should_panic(expected = "invalid builder argument")]
#[allow(dead_code)] fn test_with_get_set_aggregate_check_panics() {
struct S { w: i32 }
impl S {
crate::getter_setter!([&] with, get, set, [
w: i32 { non_negative, "`Err` when negative" } => &'static str; "w.";
]);
}
S { w: 0 }.with_w(-1);
}
#[test]
#[should_panic(expected = "invalid builder argument")]
#[allow(dead_code)] fn test_force_with_get_set_aggregate_check_panics() {
struct S { v: i32 }
impl S {
crate::getter_setter!(with, get, set, [
v: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "v.";
]);
}
S { v: 0 }.with_v(-1);
}
#[test]
#[should_panic(expected = "invalid builder argument")]
#[allow(dead_code)] fn test_with_get_ref_aggregate_check_panics() {
struct S { arr: [f64; 2] }
impl S {
crate::getter_setter!([&] with, get, [arr: &[f64; 2] { first_non_negative, "`Err` when the first element is negative" }; "array.";]);
}
S { arr: [0.0; 2] }.with_arr([-1.0, 0.0]);
}
#[test]
fn test_cast_mut_info_both_arms() {
let mut val: [u8; 4] = [7, 0, 0, 0];
let r: &mut [u8; 4] = crate::cast_mut_info!(&mut val);
r[0] = 42;
assert_eq!(val[0], 42);
let idx: usize = 0;
let r2: &mut [u8; 4] = crate::cast_mut_info!(&mut val, idx);
r2[0] = 99;
assert_eq!(val[0], 99);
}
#[test]
fn test_c_str_as_str_method_combination_arms() {
struct GetSet { name: [c_char; 16] }
impl GetSet {
crate::c_str_as_str_method!(get, set { name; "name."; });
}
struct WithSet { label: [c_char; 16] }
impl WithSet {
crate::c_str_as_str_method!(with, set { label; "label."; });
}
struct WithGet { title: [c_char; 16] }
impl WithGet {
crate::c_str_as_str_method!(with, get { title; "title."; });
}
let mut gs = GetSet { name: [0; 16] };
gs.set_name("hello");
assert_eq!(gs.name(), "hello");
let ws = WithSet { label: [0; 16] }.with_label("world");
let bytes: &[u8] = bytemuck::cast_slice(&ws.label[..]);
assert!(bytes.starts_with(b"world\0"), "with_label did not write label");
let mut ws2 = WithSet { label: [0; 16] };
ws2.set_label("bye");
let bytes2: &[u8] = bytemuck::cast_slice(&ws2.label[..]);
assert!(bytes2.starts_with(b"bye\0"), "set_label did not write label");
let wg = WithGet { title: [0; 16] }.with_title("foo");
assert_eq!(wg.title(), "foo");
}
}