use std::ffi::{CStr, CString};
use crate::mujoco_c::*;
pub(crate) unsafe fn read_mjs_string<'a>(string: *const mjString) -> &'a str {
let ptr = unsafe { mjs_getString(string) };
if ptr.is_null() {
""
} else {
unsafe { CStr::from_ptr(ptr) }.to_str().unwrap()
}
}
pub(crate) unsafe fn write_mjs_string(source: &str, destination: *mut mjString) {
let c_source = CString::new(source).unwrap();
unsafe { mjs_setString(destination, c_source.as_ptr()) };
}
pub(crate) unsafe fn read_mjs_vec_f64<'a>(array: *const mjDoubleVec) -> &'a [f64] {
let mut userdata_length = 0;
let ptr_arr = unsafe { mjs_getDouble(array, &mut userdata_length) };
if ptr_arr.is_null() {
return &[];
}
unsafe { std::slice::from_raw_parts(ptr_arr, userdata_length as usize) }
}
pub(crate) unsafe fn write_mjs_vec_f64(source: &[f64], destination: *mut mjDoubleVec) {
unsafe {
mjs_setDouble(destination, source.as_ptr(), source.len() as i32);
}
}
pub(crate) unsafe fn write_mjs_vec_f32(source: &[f32], destination: *mut mjFloatVec) {
unsafe {
mjs_setFloat(destination, source.as_ptr(), source.len() as i32);
}
}
pub(crate) unsafe fn append_mjs_vec_vec_f32(source: &[f32], destination: *mut mjFloatVecVec) {
unsafe {
mjs_appendFloatVec(destination, source.as_ptr(), source.len() as i32);
}
}
pub(crate) unsafe fn write_mjs_vec_i32(source: &[i32], destination: *mut mjIntVec) {
unsafe {
mjs_setInt(destination, source.as_ptr(), source.len() as i32);
}
}
pub(crate) unsafe fn append_mjs_vec_vec_i32(source: &[i32], destination: *mut mjIntVecVec) {
unsafe {
mjs_appendIntVec(destination, source.as_ptr(), source.len() as i32);
}
}
pub(crate) unsafe fn write_mjs_vec_string(source: &str, destination: *mut mjStringVec) {
let c_source = CString::new(source).unwrap();
unsafe {
mjs_setStringVec(destination, c_source.as_ptr());
}
}
pub(crate) unsafe fn append_mjs_vec_string(source: &str, destination: *mut mjStringVec) {
let c_source = CString::new(source).unwrap();
unsafe {
mjs_appendString(destination, c_source.as_ptr());
}
}
pub(crate) unsafe fn write_mjs_vec_byte<T: bytemuck::NoUninit>(source: &[T], destination: *mut mjByteVec) {
let bytes: &[u8] = bytemuck::cast_slice(source);
unsafe {
mjs_setBuffer(destination, bytes.as_ptr().cast(), bytes.len() as i32);
}
}
macro_rules! add_x_method {
($($name:ident),*) => {paste::paste! {
$(
#[doc = concat!(
"Add and return a child [`", stringify!([<Mjs $name:camel>]), "`].\n\n",
"Delegates to [`Self::try_add_", stringify!($name), "`] and panics if allocation fails.\n",
"# Panics\n",
"Panics if MuJoCo fails to allocate the element."
)]
pub fn [<add_ $name>](&mut self) -> &mut [<Mjs $name:camel>] {
self.[<try_add_ $name>]()
.expect(concat!("mjs_add", stringify!([<$name:camel>]), " returned null; allocation failed"))
}
#[doc = concat!(
"Fallible version of [`Self::add_", stringify!($name), "`].\n\n",
"# Errors\n",
"Returns [`MjEditError::AllocationFailed`] when MuJoCo fails to allocate ",
"the element, instead of panicking."
)]
pub fn [<try_add_ $name>](&mut self) -> Result<&mut [<Mjs $name:camel>], MjEditError> {
let ptr = unsafe { [<mjs_add $name:camel>](self.ffi_mut(), ptr::null()) };
unsafe { ptr.as_mut() }.ok_or(MjEditError::AllocationFailed)
}
)*
}};
}
macro_rules! add_x_method_by_frame {
($($name:ident),*) => {paste::paste! {
$(
#[doc = concat!(
"Add and return a child [`", stringify!([<Mjs $name:camel>]), "`].\n\n",
"Delegates to [`Self::try_add_", stringify!($name), "`] and panics on failure.\n",
"# Panics\n",
"Panics if MuJoCo fails to allocate the element."
)]
pub fn [<add_ $name>](&mut self) -> &mut [<Mjs $name:camel>] {
self.[<try_add_ $name>]()
.expect(concat!("mjs_add", stringify!([<$name:camel>]), " returned null; allocation failed"))
}
#[doc = concat!(
"Fallible version of [`Self::add_", stringify!($name), "`].\n\n",
"# Errors\n",
"Returns [`MjEditError::AllocationFailed`] when MuJoCo fails to allocate the element."
)]
pub fn [<try_add_ $name>](&mut self) -> Result<&mut [<Mjs $name:camel>], MjEditError> {
unsafe {
let ep = self.element_mut_pointer();
let body_ptr = mjs_getParent(ep);
debug_assert!(!body_ptr.is_null(), "mjs_getParent returned null; frame has no parent body");
let ptr = [<mjs_add $name:camel>](body_ptr, ptr::null());
if ptr.is_null() {
return Err(MjEditError::AllocationFailed);
}
let set_result = mjs_setFrame((*ptr).element, self);
debug_assert_eq!(set_result, 0, "mjs_setFrame failed; element or frame is invalid");
Ok(&mut *ptr)
}
}
)*
}};
}
macro_rules! add_x_method_no_default {
($($name:ident),*) => {paste::paste! {
$(
#[doc = concat!(
"Add and return a child [`", stringify!([<Mjs $name:camel>]), "`].\n\n",
"Delegates to [`Self::try_add_", stringify!($name), "`] and panics if allocation fails.\n",
"# Panics\n",
"Panics if MuJoCo fails to allocate the element."
)]
pub fn [<add_ $name>](&mut self) -> &mut [<Mjs $name:camel>] {
self.[<try_add_ $name>]()
.expect(concat!("mjs_add", stringify!([<$name:camel>]), " returned null; allocation failed"))
}
#[doc = concat!(
"Fallible version of [`Self::add_", stringify!($name), "`].\n\n",
"# Errors\n",
"Returns [`MjEditError::AllocationFailed`] when MuJoCo fails to allocate ",
"the element, instead of panicking."
)]
pub fn [<try_add_ $name>](&mut self) -> Result<&mut [<Mjs $name:camel>], MjEditError> {
let ptr = unsafe { [<mjs_add $name:camel>](self.0.as_ptr()) };
unsafe { ptr.as_mut() }.ok_or(MjEditError::AllocationFailed)
}
)*
}};
}
macro_rules! find_x_method {
($($item:ident),*) => {paste::paste! {
$(
#[doc = concat!(
"Obtain an immutable reference to the ", stringify!($item), " with the given `name`.\n",
"# Panics\n",
"When the `name` contains '\\0' characters, a panic occurs."
)]
pub fn $item(&self, name: &str) -> Option<&[<Mjs $item:camel>]> {
let c_name = CString::new(name).unwrap();
unsafe {
let ptr = mjs_findElement(self.0.as_ptr(), MjtObj::[<mjOBJ_ $item:upper>], c_name.as_ptr());
if ptr.is_null() {
None
}
else {
[<mjs_as $item:camel>](ptr).as_ref()
}
}
}
#[doc = concat!(
"Obtain a mutable reference to the ", stringify!($item), " with the given `name`.\n",
"# Panics\n",
"When the `name` contains '\\0' characters, a panic occurs."
)]
pub fn [<$item _mut>](&mut self, name: &str) -> Option<&mut [<Mjs $item:camel>]> {
let c_name = CString::new(name).unwrap();
unsafe {
let ptr = mjs_findElement(self.0.as_ptr(), MjtObj::[<mjOBJ_ $item:upper>], c_name.as_ptr());
if ptr.is_null() {
None
}
else {
[<mjs_as $item:camel>](ptr).as_mut()
}
}
}
)*
}};
}
macro_rules! find_x_method_direct {
($($item:ident),*) => {paste::paste!{
$(
#[doc = concat!(
"Obtain an immutable reference to the ", stringify!($item), " with the given `name`.\n",
"# Panics\n",
"When the `name` contains '\\0' characters, a panic occurs."
)]
pub fn $item(&self, name: &str) -> Option<&[<Mjs $item:camel>]> {
let c_name = CString::new(name).unwrap();
unsafe {
let ptr = [<mjs_find $item:camel>](self.0.as_ptr(), c_name.as_ptr());
if ptr.is_null() {
None
}
else {
ptr.as_ref()
}
}
}
#[doc = concat!(
"Obtain a mutable reference to the ", stringify!($item), " with the given `name`.\n",
"# Panics\n",
"When the `name` contains '\\0' characters, a panic occurs."
)]
pub fn [<$item _mut>](&mut self, name: &str) -> Option<&mut [<Mjs $item:camel>]> {
let c_name = CString::new(name).unwrap();
unsafe {
let ptr = [<mjs_find $item:camel>](self.0.as_ptr(), c_name.as_ptr());
if ptr.is_null() {
None
}
else {
ptr.as_mut()
}
}
}
)*
}};
}
macro_rules! mjs_struct {
($ffi_name:ident $([$SpecObject:ident])? $({ $($extra_trait_methods:tt)* })?) => {paste::paste!{
#[doc = concat!(stringify!($ffi_name), " specification. This is an alias to the FFI type [`", stringify!([<mjs $ffi_name>]), "`].")]
pub type [<Mjs $ffi_name>] = [<mjs $ffi_name>];
impl [<Mjs $ffi_name>] {
pub fn info(&self) -> &str {
unsafe { read_mjs_string(self.info) }
}
pub fn set_info(&mut self, info: &str) {
unsafe { write_mjs_string(info, self.info) };
}
}
impl crate::wrappers::mj_editing::traits::sealed::Sealed for [<Mjs $ffi_name>] {}
impl SpecItem for [<Mjs $ffi_name>] {
fn element_pointer(&self) -> *const mjsElement {
self.element
}
$($(
$extra_trait_methods
)*)?
}
$(
impl $SpecObject for [<Mjs $ffi_name>] {
const OBJ_TYPE: MjtObj = MjtObj::[<mjOBJ_ $ffi_name:upper>];
unsafe fn from_element_as_ptr_mut(element: *mut mjsElement) -> *mut Self {
unsafe { [<mjs_as $ffi_name:camel>](element) }
}
}
)?
}};
}
macro_rules! userdata_method {
($type:ty) => {paste::paste!{
pub fn userdata(&self) -> &[$type] {
unsafe { [<read_mjs_vec_ $type>](self.userdata) }
}
pub fn set_userdata<T: AsRef<[$type]>>(&mut self, value: T) {
unsafe { [<write_mjs_vec_ $type>](value.as_ref(), self.userdata) };
}
pub fn with_userdata<T: AsRef<[$type]>>(&mut self, value: T) -> &mut Self {
unsafe { [<write_mjs_vec_ $type>](value.as_ref(), self.userdata) };
self
}
}};
}
macro_rules! vec_string_set_append {
($($name:ident; $comment:expr);* $(;)?) => {paste::paste!{
$(
#[doc = concat!(
"Splits the `", stringify!($name), "` and put the split text as ", $comment,
"\n",
"# Panics\n",
"When the `value` contains '\\0' characters, a panic occurs."
)]
pub fn [<set_ $name>](&mut self, value: &str) {
unsafe { write_mjs_vec_string(value, self.$name) };
}
#[doc = concat!(
"Splits the `", stringify!($name), "` and append the split text to ", $comment,
"\n",
"# Panics\n",
"When the `value` contains '\\0' characters, a panic occurs."
)]
pub fn [<append_ $name>](&mut self, value: &str) {
unsafe { append_mjs_vec_string(value, self.$name) };
}
)*
}};
($name:ident[$role_ty:ty] => $singular:ident; $comment:expr $(;)?) => {paste::paste!{
#[doc = concat!(
"Sets the entry at index `role` in `", stringify!($name), "` to `name`. ",
$comment,
"\n\n",
"The `", stringify!($name), "` vector is pre-sized by MuJoCo (one slot per ",
"[`", stringify!($role_ty), "`] variant); this method writes directly into ",
"the correct slot.\n",
"\n",
"# Panics\n",
"When `name` contains '\\0' characters, a panic occurs."
)]
pub fn [<set_ $singular>](&mut self, role: $role_ty, name: &str) {
let c_name = CString::new(name).unwrap();
unsafe { mjs_setInStringVec(self.$name, role as std::ffi::c_int, c_name.as_ptr()) };
}
#[doc = concat!(
"Sets the entry at index `role` in `", stringify!($name), "` to `name`, ",
"returning `&mut Self` for chaining. ",
$comment,
"\n\n",
"Equivalent to [`set_", stringify!($singular), "`](Self::set_", stringify!($singular), ")."
)]
pub fn [<with_ $singular>](&mut self, role: $role_ty, name: &str) -> &mut Self {
self.[<set_ $singular>](role, name);
self
}
#[doc = concat!(
"Replaces the entire `", stringify!($name), "` vector with whitespace-split entries from `value`. ",
$comment,
"\n\n",
"<div class=\"warning\">\n\n",
"This replaces the pre-sized vector. Prefer [`set_",
stringify!($singular), "`](Self::set_", stringify!($singular),
"`) to set individual entries by role.\n\n",
"</div>\n\n",
"# Panics\n",
"When the `value` contains '\\0' characters, a panic occurs."
)]
pub fn [<set_ $name>](&mut self, value: &str) {
unsafe { write_mjs_vec_string(value, self.$name) };
}
#[doc = concat!(
"Appends `value` to the end of `", stringify!($name), "`. ",
$comment,
"\n\n",
"<div class=\"warning\">\n\n",
"Appending extends past the pre-sized vector. Prefer [`set_",
stringify!($singular), "`](Self::set_", stringify!($singular),
"`) to set individual entries by role.\n\n",
"</div>\n\n",
"# Panics\n",
"When the `value` contains '\\0' characters, a panic occurs."
)]
pub fn [<append_ $name>](&mut self, value: &str) {
unsafe { append_mjs_vec_string(value, self.$name) };
}
}};
}
macro_rules! string_set_get_with {
(@impl common $([$ffi:ident, $ffi_mut:ident])? $name:ident; $comment:expr;) => {paste::paste!{
#[doc = concat!(
"Return ", $comment,
"\n",
"# Panics\n",
"Panics if the stored string is not valid UTF-8, which can only happen on internal memory corruption \
-- MuJoCo only uses ASCII values."
)]
pub fn $name(&self) -> &str {
unsafe { read_mjs_string(self$(.$ffi())?.$name) }
}
#[allow(unused_unsafe)]
#[doc = concat!(
"Set ", $comment,
"\n",
"# Panics\n",
"When the `value` contains '\\0' characters, a panic occurs."
)]
pub fn [<set_ $name>](&mut self, value: &str) {
unsafe { write_mjs_string(value, unsafe { self$(.$ffi_mut())?.$name }) };
}
}};
( $($([$ffi:ident, $ffi_mut:ident])? $name:ident; $comment:expr;)* ) => {paste::paste!{
$(
string_set_get_with!(@impl common $([$ffi, $ffi_mut])? $name; $comment;);
#[allow(unused_unsafe)]
#[doc = concat!(
"Builder method for setting ", $comment,
"\n",
"# Panics\n",
"When the `value` contains '\\0' characters, a panic occurs."
)]
pub fn [<with_ $name>](mut self, value: &str) -> Self {
unsafe { write_mjs_string(value, unsafe { self$(.$ffi_mut())?.$name }) };
self
}
)*
}};
([&] $($([$ffi:ident, $ffi_mut:ident])? $name:ident; $comment:expr;)* ) => {paste::paste!{
$(
string_set_get_with!(@impl common $([$ffi, $ffi_mut])? $name; $comment;);
#[allow(unused_unsafe)]
#[doc = concat!(
"Builder method for setting ", $comment,
"\n",
"# Panics\n",
"When the `value` contains '\\0' characters, a panic occurs."
)]
pub fn [<with_ $name>](&mut self, value: &str) -> &mut Self {
unsafe { write_mjs_string(value, unsafe { self$(.$ffi_mut())?.$name }) };
self
}
)*
}};
}
macro_rules! vec_set_get {
($($name:ident: $type:ty; $comment:expr);* $(;)?) => {paste::paste!{
$(
#[doc = concat!("Return ", $comment)]
pub fn $name(&self) -> &[$type] {
unsafe { [<read_mjs_vec_ $type>](self.$name) }
}
)*
vec_set!($($name: $type; $comment);*);
}};
}
macro_rules! vec_set {
($($name:ident: $type:ty; $comment:expr);* $(;)?) => {paste::paste!{
$(
#[doc = concat!("Set ", $comment)]
pub fn [<set_ $name>](&mut self, value: &[$type]) {
unsafe { [<write_mjs_vec_ $type>](value, self.$name) };
}
)*
}};
($($([$unsafe_kw:ident : $safety:literal])? $name:ident: $input_type:ty => $type:ty $({$check:expr , $reason:literal} => $err:ty)?; $comment:expr);* $(;)?) => {paste::paste!{
$(
// One cast setter whose shape is driven by the optional check / safety tail:
// - `{ check, "reason" } => ErrType` makes it a safe `-> Result<(), ErrType>` that
#[doc = concat!("Set ", $comment
$(, "\n\n# Errors\nReturns ", $reason, " (in that case nothing is written).")?
$(, "\n\n# Safety\n", $safety)?
)]
pub $($unsafe_kw)? fn [<set_ $name>](&mut self, value: &[$input_type]) $(-> Result<(), $err>)? {
$(for &v in value { ($check)(v)?; })?
$crate::util::assert_ptr_cast_valid::<$input_type, $type>(value.as_ptr());
let raw = unsafe { std::slice::from_raw_parts(value.as_ptr().cast(), value.len()) };
unsafe { [<write_mjs_vec_ $type>](raw, self.$name) };
$(Ok::<(), $err>(()))?
}
)*
}};
}
macro_rules! vec_vec_append {
($($name:ident: $type:ty; $comment:expr);* $(;)?) => {paste::paste!{
$(
#[doc = concat!("Append to ", $comment)]
pub fn [<append_ $name>](&mut self, value: &[$type]) {
unsafe { [<append_mjs_vec_vec_ $type>](value, self.$name) };
}
#[doc = concat!("Set ", $comment, " (deprecated; use ", stringify!([<append_ $name>]), " instead).")]
#[deprecated(note = "use append_ instead of set_ for vector-of-vectors attributes", since = "3.0.0")]
pub fn [<set_ $name>](&mut self, value: &[$type]) {
self.[<append_ $name>](value);
}
)*
}};
}
macro_rules! spec_get_iter {
($($iter_over: ident),*) => {paste::paste!{
$(
#[doc = concat!("Return an iterator over ", stringify!($iter_over)," items that allows modifying each value.")]
pub fn [<$iter_over _iter_mut>](&mut self) -> MjsSpecItemIterMut<'_, [<Mjs $iter_over:camel>]> {
MjsSpecItemIterMut::<[<Mjs $iter_over:camel>]>::new(self)
}
#[doc = concat!("Return an immutable iterator over ", stringify!($iter_over)," items.")]
pub fn [<$iter_over _iter>](&self) -> MjsSpecItemIter<'_, [<Mjs $iter_over:camel>]> {
MjsSpecItemIter::<[<Mjs $iter_over:camel>]>::new(self)
}
)*
}};
}
macro_rules! body_get_iter {
([$($iter_over: ident),*]) => {paste::paste!{
$(
#[doc = concat!("Return an iterator over ", stringify!($iter_over)," items that allows modifying each value.")]
pub fn [<$iter_over _iter_mut>](&mut self, recurse: bool) -> MjsBodyItemIterMut<'_, [<Mjs $iter_over:camel>]> {
MjsBodyItemIterMut::<[<Mjs $iter_over:camel>]>::new(self, recurse)
}
#[doc = concat!("Return an immutable iterator over ", stringify!($iter_over)," items.")]
pub fn [<$iter_over _iter>](&self, recurse: bool) -> MjsBodyItemIter<'_, [<Mjs $iter_over:camel>]> {
MjsBodyItemIter::<[<Mjs $iter_over:camel>]>::new(self, recurse)
}
)*
}};
}