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);
}
#[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_view_indices {
($id:expr, $addr_map:expr, $njnt:expr, $max_n:expr) => {
{
let start_addr = *$addr_map.add($id) as isize;
if start_addr == -1 {
(0, 0)
}
else
{
let mut next_idx = $id + 1;
let end_addr: usize = loop {
if next_idx >= $njnt as usize {
break $max_n as usize;
}
let next_addr = *$addr_map.add(next_idx) as isize;
if next_addr != -1 {
break next_addr as usize;
}
next_idx += 1;
};
let n = end_addr - start_addr as usize;
(start_addr as usize, n)
}
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! mj_model_nx_to_mapping {
($model_ffi:ident, nq) => {
$model_ffi.jnt_qposadr
};
($model_ffi:ident, nv) => {
$model_ffi.jnt_dofadr
};
($model_ffi:ident, nsensordata) => {
$model_ffi.sensor_adr
};
($model_ffi:ident, ntupledata) => {
$model_ffi.tuple_adr
};
($model_ffi:ident, ntexdata) => {
$model_ffi.tex_adr
};
($model_ffi:ident, nnumericdata) => {
$model_ffi.numeric_adr
};
($model_ffi:ident, nhfielddata) => {
$model_ffi.hfield_adr
};
($model_ffi:ident, na) => {
$model_ffi.actuator_actadr
};
($model_ffi:ident, nJten) => {
$model_ffi.ten_J_rowadr
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! mj_model_nx_to_nitem {
($model_ffi:ident, nq) => {
$model_ffi.njnt
};
($model_ffi:ident, nv) => {
$model_ffi.njnt
};
($model_ffi:ident, nsensordata) => {
$model_ffi.nsensor
};
($model_ffi:ident, ntupledata) => {
$model_ffi.ntuple
};
($model_ffi:ident, ntexdata) => {
$model_ffi.ntex
};
($model_ffi:ident, nnumericdata) => {
$model_ffi.nnumeric
};
($model_ffi:ident, nhfielddata) => {
$model_ffi.nhfield
};
($model_ffi:ident, na) => {
$model_ffi.nu
};
($model_ffi:ident, nJten) => {
$model_ffi.ntendon
};
}
#[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, $ffi:expr, $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 c_name = CString::new(name).unwrap();
let ffi = self.$ffi;
let id = unsafe { mj_name2id(ffi, MjtObj::[<mjOBJ_ $type_:upper>] as i32, c_name.as_ptr()) };
if id == -1 {
return None;
}
let id = id as usize;
$(
let $attr = (id * $len, $len);
)*
$(
let $attr_ffi = (id * ffi.$len_ffi as usize $( * $multiplier)*, ffi.$len_ffi as usize $( * $multiplier)*);
)*
$(
let (dyn_start, dyn_len) = unsafe { mj_view_indices!(
id,
mj_model_nx_to_mapping!(ffi, $ffi_len_dyn),
mj_model_nx_to_nitem!(ffi, $ffi_len_dyn),
ffi.$ffi_len_dyn
) };
let $attr_dyn = (dyn_start $(* $offset_mult)?, dyn_len $(* $offset_mult)?);
)*
let model_signature = 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 {
(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 == 1
}
)*
}};
(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; $comment:expr);* $(;)?]) => {paste::paste!{
$(
#[doc = concat!("Return value of ", $comment)]
pub fn [<$name:camel:snake $($symbol)?>](&self) -> $type {
self$(.$ffi())?.$name.into()
}
)*
}};
(set, [$($([$ffi_mut:ident])? $name:ident: $type:ty; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[doc = concat!("Set ", $comment)]
pub fn [<set_ $name:camel:snake>](&mut self, value: $type) {
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = value.into() };
}
)*
}
};
(force!, get, [$($([$ffi:ident])? $name:ident $(+ $symbol:tt)? : $type:ty; $comment:expr);* $(;)?]) => {paste::paste!{
$(
#[doc = concat!("Return value of ", $comment)]
pub fn [<$name:camel:snake $($symbol)?>](&self) -> $type {
unsafe { $crate::util::force_cast(self$(.$ffi())?.$name) }
}
)*
}};
(force!, set, [$($([$ffi_mut:ident])? $name:ident: $type:ty; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[doc = concat!("Set ", $comment)]
pub fn [<set_ $name:camel:snake>](&mut self, value: $type) {
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = $crate::util::force_cast(value) };
}
)*
}
};
(force!, with, [$($([$ffi_mut:ident])? $name:ident: $type:ty; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[doc = concat!("Builder method for setting ", $comment)]
pub fn [<with_ $name:camel:snake>](mut self, value: $type) -> Self {
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = $crate::util::force_cast(value) };
self
}
)*
}
};
(force!, [&] with, [$($([$ffi_mut:ident])? $name:ident: $type:ty; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[doc = concat!("Builder method for setting ", $comment)]
pub fn [<with_ $name:camel:snake>](&mut self, value: $type) -> &mut Self {
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = $crate::util::force_cast(value) };
self
}
)*
}
};
(with, [$($([$ffi_mut:ident])? $name:ident: $type:ty; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[doc = concat!("Builder method for setting ", $comment)]
pub fn [<with_ $name:camel:snake>](mut self, value: $type) -> Self {
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = value.into() };
self
}
)*
}
};
([&] with, [$($([$ffi_mut:ident])? $name:ident: $type:ty; $comment:expr);* $(;)?]) => {
paste::paste!{
$(
#[doc = concat!("Builder method for setting ", $comment)]
pub fn [<with_ $name:camel:snake>](&mut self, value: $type) -> &mut Self {
#[allow(unused_unsafe)]
unsafe { self$(.$ffi_mut())?.$name = value.into() };
self
}
)*
}
};
(force!, get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : $type:ty ; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(force!, get, [ $($([$ffi])? $name $(+ $symbol)? : $type ; $comment );* ]);
$crate::getter_setter!(force!, set, [ $($([$ffi_mut])? $name : $type ; $comment );* ]);
};
(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 ; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)? : $type ; $comment );* ]);
$crate::getter_setter!(set, [ $($([$ffi_mut])? $name : $type ; $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 ; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)?: $type ; $comment );* ]);
$crate::getter_setter!(set, [ $($([$ffi_mut])? $name : $type ; $comment );* ]);
$crate::getter_setter!($([$token])? with, [ $($([$ffi_mut])? $name : $type ; $comment );* ]);
};
(force!, $([$token:tt])? with, get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : $type:ty ; $comment:expr );* $(;)?]) => {
$crate::getter_setter!(force!, get, [$($([$ffi])? $name $(+ $symbol)? : $type ; $comment );* ]);
$crate::getter_setter!(force!, set, [$($([$ffi_mut])? $name : $type ; $comment );* ]);
$crate::getter_setter!(force!, $([$token])? with, [$($([$ffi_mut])? $name : $type ; $comment );* ]);
};
($([$token:tt])? with, get, [$( $([$ffi: ident, $ffi_mut:ident])? $((allow_mut = $allow_mut:literal))? $name:ident $(+ $symbol:tt)? : & $type:ty ; $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 ; $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 {
($($(($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");
}
#[test]
fn test_getter_setter_dead_arms_11_12_13() {
struct ArmEleven { x: i32, y: i32 }
impl ArmEleven {
crate::getter_setter!(force!, get, set, [x: i32; "x field."; y: i32; "y field.";]);
}
struct ArmTwelve { flag: i32 }
impl ArmTwelve {
crate::getter_setter!(get, set, [flag: bool; "flag field.";]);
}
struct ArmThirteen { count: i32 }
impl ArmThirteen {
crate::getter_setter!(get, set, [count: i32; "count field.";]);
}
let mut arm11 = ArmEleven { x: 3, y: 7 };
assert_eq!(arm11.x(), 3);
assert_eq!(arm11.y(), 7);
arm11.set_x(9);
assert_eq!(arm11.x(), 9);
arm11.set_y(10);
assert_eq!(arm11.y(), 10);
let mut arm12 = ArmTwelve { flag: 1 };
assert!(arm12.flag());
arm12.set_flag(false);
assert!(!arm12.flag());
let mut arm13 = ArmThirteen { count: 5 };
assert_eq!(arm13.count(), 5);
arm13.set_count(10);
assert_eq!(arm13.count(), 10);
}
#[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");
}
}