Skip to main content

mujoco_rs/
util.rs

1//! Utility types and macros used throughout the crate.
2use std::{marker::PhantomData, ops::{Deref, DerefMut}};
3use std::sync::{Mutex, MutexGuard};
4use std::ffi::c_char;
5
6use crate::mujoco_c::{mj_version, mjVERSION_HEADER};
7
8/// Standard size of temporary error buffers passed to MuJoCo C functions.
9/// MuJoCo NUL-terminates within this size, so the effective maximum
10/// message length is `ERROR_BUF_LEN - 1` characters.
11pub(crate) const ERROR_BUF_LEN: usize = 100;
12
13/// Copies an ASCII `&str` into a fixed-size `c_char` buffer, NUL-terminating and
14/// zero-filling the remainder.
15///
16/// # Panics
17/// Panics if `value` is not valid ASCII, contains an interior NUL byte,
18/// or if `value` (plus NUL) does not fit in `buf`.
19pub(crate) fn write_ascii_to_buf(buf: &mut [c_char], value: &str) {
20    assert!(value.is_ascii(), "value must be valid ASCII");
21    let c_string = std::ffi::CString::new(value).unwrap();
22    let bytes = c_string.into_bytes_with_nul();
23    let dest: &mut [u8] = bytemuck::cast_slice_mut(buf);
24    dest[..bytes.len()].copy_from_slice(&bytes);
25    dest[bytes.len()..].fill(0);
26}
27
28/// Returns `Some((start, len))` for item `id` inside a packed data array,
29/// or `None` if the item has no data (address entry is negative).
30///
31/// Each entry in `addr_array` is either the item's start offset in the data array
32/// or a negative value (conventionally `-1`) meaning the item has no associated data.
33///
34/// The length is determined by scanning `addr_array[id + 1..]` for the first
35/// non-negative entry (the next item that *does* have data). If no such entry exists,
36/// the region extends to the end of the data array (`data_len`).
37///
38/// # Examples
39/// ```ignore
40/// if let Some((graph_adr, graph_len)) = optional_sparse_addr_range(
41///     model.mesh_graphadr(), mesh_id, model.mesh_graph().len()
42/// ) {
43///     // copy mesh_graph[graph_adr..graph_adr + graph_len]
44/// }
45/// ```
46///
47/// # Panics
48/// Panics if `id >= addr_array.len()`.
49pub(crate) fn optional_sparse_addr_range<T>(addr_array: &[T], id: usize, data_len: usize) -> Option<(usize, usize)>
50where
51    T: Into<i64> + Copy,
52{
53    let adr: i64 = addr_array[id].into();
54    if adr < 0 {
55        return None;
56    }
57    let adr = adr as usize;
58    let len = addr_array
59        .get(id + 1..)
60        .unwrap_or(&[])
61        .iter()
62        .find(|&&next| next.into() != -1)
63        .map(|&next| next.into() as usize)
64        .unwrap_or(data_len)
65        - adr;
66    Some((adr, len))
67}
68
69
70/// Sets or clears a bit flag based on a boolean value.
71///
72/// # Examples
73/// ```ignore
74/// let mut flags = 0i32;
75/// set_flag!(flags, 0x01, true);   // sets bit 0
76/// set_flag!(flags, 0x01, false);  // clears bit 0
77/// ```
78#[doc(hidden)]
79#[macro_export]
80macro_rules! set_flag {
81    ($flags:expr, $mask:expr, $enabled:expr) => {
82        if $enabled {
83            $flags |= $mask;
84        } else {
85            $flags &= !$mask;
86        }
87    };
88}
89
90
91/// Returns the correct address mapping based on the X in nX (nq, nv, nu, ...).
92#[macro_export]
93#[doc(hidden)]
94macro_rules! mj_model_dyn_range {
95    ($model:expr, $id:expr, nq) => {
96        $crate::util::optional_sparse_addr_range($model.jnt_qposadr(), $id, $model.nq() as usize).unwrap_or((0, 0))
97    };
98    ($model:expr, $id:expr, nv) => {
99        $crate::util::optional_sparse_addr_range($model.jnt_dofadr(), $id, $model.nv() as usize).unwrap_or((0, 0))
100    };
101    ($model:expr, $id:expr, nsensordata) => {
102        $crate::util::optional_sparse_addr_range($model.sensor_adr(), $id, $model.nsensordata() as usize).unwrap_or((0, 0))
103    };
104    ($model:expr, $id:expr, ntupledata) => {
105        $crate::util::optional_sparse_addr_range($model.tuple_adr(), $id, $model.ntupledata() as usize).unwrap_or((0, 0))
106    };
107    ($model:expr, $id:expr, ntexdata) => {
108        $crate::util::optional_sparse_addr_range($model.tex_adr(), $id, $model.ntexdata() as usize).unwrap_or((0, 0))
109    };
110    ($model:expr, $id:expr, nnumericdata) => {
111        $crate::util::optional_sparse_addr_range($model.numeric_adr(), $id, $model.nnumericdata() as usize).unwrap_or((0, 0))
112    };
113    ($model:expr, $id:expr, nhfielddata) => {
114        $crate::util::optional_sparse_addr_range($model.hfield_adr(), $id, $model.nhfielddata() as usize).unwrap_or((0, 0))
115    };
116    ($model:expr, $id:expr, na) => {
117        $crate::util::optional_sparse_addr_range($model.actuator_actadr(), $id, $model.na() as usize).unwrap_or((0, 0))
118    };
119    ($model:expr, $id:expr, nJten) => {
120        $crate::util::optional_sparse_addr_range($model.ten_j_rowadr(), $id, $model.n_jten() as usize).unwrap_or((0, 0))
121    };
122}
123
124/// Provides a more direct view to a C array.
125/// # Safety
126/// This does not check if the data is valid. It is assumed
127/// the correct data is given and that it doesn't get dropped before this struct.
128/// This does not break Rust's checks as we create the view each
129/// time from the saved pointers.
130/// This should ONLY be used within a wrapper that fully encapsulates the underlying data.
131#[derive(Debug)]
132pub struct PointerViewMut<'d, T> {
133    ptr: *mut T,
134    len: usize,
135    phantom: PhantomData<&'d mut ()>
136}
137
138impl<'d, T> PointerViewMut<'d, T> {
139    pub(crate) const fn new(ptr: *mut T, len: usize, phantom: PhantomData<&'d mut ()>) -> Self {
140        Self {ptr, len, phantom}
141    }
142}
143
144/// Compares if the two views point to the same data with the same length.
145impl<T> PartialEq for PointerViewMut<'_, T> {
146    fn eq(&self, other: &Self) -> bool {
147        self.ptr == other.ptr && self.len == other.len
148    }
149}
150
151impl<T> Eq for PointerViewMut<'_, T> {}
152
153impl<T> Deref for PointerViewMut<'_, T> {
154    type Target = [T];
155    fn deref(&self) -> &Self::Target {
156        if self.ptr.is_null() {
157            return &[];
158        }
159        // SAFETY: ptr is non-null (checked above), properly aligned, and points to
160        // self.len initialized elements owned by the parent wrapper for lifetime 'd.
161        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
162    }
163}
164
165impl<T> DerefMut for PointerViewMut<'_, T> {
166    fn deref_mut(&mut self) -> &mut Self::Target {
167        if self.ptr.is_null() {
168            return &mut [];
169        }
170        // SAFETY: ptr is non-null (checked above), properly aligned, points to self.len
171        // initialized elements, and &mut self guarantees exclusive access.
172        unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
173    }
174}
175
176/// Provides a read-only view to a C array with explicit unsafe mutable access.
177/// # Safety
178/// This does not check if the data is valid. It is assumed
179/// the correct data is given and that it doesn't get dropped before this struct.
180/// Mutable access is only available via [`PointerViewUnsafeMut::as_mut_slice`],
181/// where the caller must uphold Rust aliasing and validity guarantees.
182/// This should ONLY be used within a wrapper that fully encapsulates the underlying data.
183#[derive(Debug)]
184pub struct PointerViewUnsafeMut<'d, T> {
185    ptr: *mut T,
186    len: usize,
187    phantom: PhantomData<&'d mut ()>
188}
189
190impl<'d, T> PointerViewUnsafeMut<'d, T> {
191    pub(crate) const fn new(ptr: *mut T, len: usize, phantom: PhantomData<&'d mut ()>) -> Self {
192        Self { ptr, len, phantom }
193    }
194
195    /// Returns a mutable slice over the underlying data.
196    ///
197    /// # Safety
198    /// Caller must ensure that:
199    /// - `self.ptr` points to `self.len` properly aligned and initialized `T` values (or is null with `len == 0`);
200    /// - no other references (shared or mutable) to overlapping memory are alive while the returned slice is used;
201    /// - written values preserve Rust type validity and MuJoCo invariants.
202    pub unsafe fn as_mut_slice(&mut self) -> &mut [T] {
203        if self.ptr.is_null() {
204            return &mut [];
205        }
206        // SAFETY: caller upholds aliasing and validity guarantees (documented above).
207        // ptr is non-null (checked above) and points to self.len elements.
208        unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
209    }
210}
211
212/// Compares if the two views point to the same data with the same length.
213impl<T> PartialEq for PointerViewUnsafeMut<'_, T> {
214    fn eq(&self, other: &Self) -> bool {
215        self.ptr == other.ptr && self.len == other.len
216    }
217}
218
219impl<T> Eq for PointerViewUnsafeMut<'_, T> {}
220
221impl<T> Deref for PointerViewUnsafeMut<'_, T> {
222    type Target = [T];
223    fn deref(&self) -> &Self::Target {
224        if self.ptr.is_null() {
225            return &[];
226        }
227        // SAFETY: ptr is non-null (checked above), properly aligned, and points to
228        // self.len initialized elements owned by the parent wrapper for lifetime 'd.
229        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
230    }
231}
232
233/// Provides a more direct view to a C array.
234/// # Safety
235/// This does not check if the data is valid. It is assumed
236/// the correct data is given and that it doesn't get dropped before this struct.
237/// This does not break Rust's checks as we create the view each
238/// time from the saved pointers.
239/// This should ONLY be used within a wrapper that fully encapsulates the underlying data.
240#[derive(Debug)]
241pub struct PointerView<'d, T> {
242    ptr: *const T,
243    len: usize,
244    phantom: PhantomData<&'d ()>
245}
246
247impl<'d, T> PointerView<'d, T> {
248    pub(crate) const fn new(ptr: *const T, len: usize, phantom: PhantomData<&'d ()>) -> Self {
249        Self {ptr, len, phantom}
250    }
251}
252
253/// Compares if the two views point to the same data with the same length.
254impl<T> PartialEq for PointerView<'_, T> {
255    fn eq(&self, other: &Self) -> bool {
256        self.ptr == other.ptr && self.len == other.len
257    }
258}
259
260impl<T> Eq for PointerView<'_, T> {}
261
262impl<T> Deref for PointerView<'_, T> {
263    type Target = [T];
264    fn deref(&self) -> &Self::Target {
265        if self.ptr.is_null() {
266            return &[];
267        }
268        // SAFETY: ptr is non-null (checked above), properly aligned, and points to
269        // self.len initialized elements owned by the parent wrapper for lifetime 'd.
270        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
271    }
272}
273
274/***************************/
275//  Evaluation helper macro
276/***************************/
277/// When @eval is given false, ignore the given contents.
278/// In other cases, expand the given contents.
279#[macro_export]
280#[doc(hidden)]
281macro_rules! eval_or_expand {
282    (@eval $(true)? { $($data:tt)* } ) => { $($data)* };
283    (@eval false { $($data:tt)* } ) => {};
284}
285
286/**************************************************************************************************/
287// View creation for MjData and MjModel
288/**************************************************************************************************/
289
290/// Constructs a view struct by mapping fields to their corresponding locations in `$data`.
291///
292/// - `$field` list uses `$ptr_view` (read-write in `ViewMut`, read-only in `View`).
293/// - `$field_ro` list uses `$ptr_view_ro` (`PointerViewUnsafeMut` in `ViewMut`, `PointerView` in `View`).
294/// - `$opt_field` list uses `$ptr_view`, wrapped in `Option`.
295///
296/// # Safety
297/// Caller must ensure the data pointers remain valid for the lifetime of the view.
298#[macro_export]
299#[doc(hidden)]
300macro_rules! view_creator {
301    (
302        $self:expr, $view:ident, $data:expr,
303        [$($([$prefix_field:ident])? $field:ident : $type_:ty $([$force:ident])?),*],
304        [$($([$prefix_field_ro:ident])? $field_ro:ident : $type_ro:ty $([$force_ro:ident])?),*],
305        [$($([$prefix_opt_field:ident])? $opt_field:ident : $type_opt:ty $([$force_opt:ident])?),*],
306        $ptr_view:expr,
307        $ptr_view_ro:expr
308    ) => {
309        paste::paste! {
310            unsafe {
311                $view {
312                    $(
313                        $field: $ptr_view(
314                            $crate::maybe_force_cast!($data.[<$($prefix_field)? $field>].add($self.$field.0), $type_ $(, $force)?),
315                            $self.$field.1,
316                            std::marker::PhantomData
317                        ),
318                    )*
319                    $(
320                        $field_ro: $ptr_view_ro(
321                            $crate::maybe_force_cast!($data.[<$($prefix_field_ro)? $field_ro>].add($self.$field_ro.0), $type_ro $(, $force_ro)?),
322                            $self.$field_ro.1,
323                            std::marker::PhantomData
324                        ),
325                    )*
326                    $(
327                        $opt_field: if $self.$opt_field.1 > 0 {
328                            Some($ptr_view(
329                                $crate::maybe_force_cast!($data.[<$($prefix_opt_field)? $opt_field>].add($self.$opt_field.0), $type_opt $(, $force_opt)?),
330                                $self.$opt_field.1,
331                                std::marker::PhantomData
332                            ))
333                        } else {
334                            None
335                        },
336                    )*
337                }
338            }
339        }
340    };
341}
342
343
344/// Generates a lookup method `$type_(&self, name: &str) -> Option<Mj{Type}{InfoType}Info>` on
345/// a wrapper.
346///
347/// The returned `Info` struct stores the name, id, and index ranges needed to
348/// create views into the corresponding `MjData` or `MjModel` arrays.
349///
350/// # Entry formats
351///
352/// - **Fixed stride**: `attr: N`: index range is `(id * N, N)`.
353/// - **FFI stride** (with optional multiplier): `attr: ffi_field (* k)`: stride taken from
354///   `model.ffi_field`, optionally scaled by `k`.
355/// - **Dynamic range**: `attr: nXXX (* k)`: start and length resolved via [`mj_model_dyn_range!`],
356///   where `nXXX` is the major-dimension field (e.g. `nhfielddata`, `ntexdata`).
357///   The optional `* k` is a stride multiplier: each logical unit occupies `k` flat elements,
358///   so both the start offset and the length are scaled by `k`. Use this when the target array
359///   stores `k` flat values per logical unit (e.g. `dof_dampingpoly (nv × mjNPOLY)` viewed as
360///   a flat `MjtNum` slice: offset = `dof_start * mjNPOLY`, length = `n_dofs * mjNPOLY`).
361#[doc(hidden)]
362#[macro_export]
363macro_rules! info_method {
364    (
365        $info_type:ident, $([$model:ident],)?
366        $type_:ident,
367        [$($attr:ident: $len:expr),*],
368        [$($attr_ffi:ident: $len_ffi:ident $(* $multiplier:expr)?),*],
369        [$($attr_dyn:ident: $ffi_len_dyn:ident $(* $offset_mult:expr)?),*]
370    ) => {
371        paste::paste! {
372            #[doc = concat!(
373                "Returns a [`", stringify!([<Mj $type_:camel $info_type Info>]), "`] for the named ", stringify!($type_), ", ",
374                "containing the indices required to create views into [`Mj", stringify!($info_type), "`] arrays.\n\n",
375                "Call [`view`](", stringify!([<Mj $type_:camel $info_type Info>]), "::view) or ",
376                "[`try_view`](", stringify!([<Mj $type_:camel $info_type Info>]), "::try_view) on the result to obtain the actual view.\n\n",
377                "# Panics\n",
378                "Panics if `name` contains a `\\0` byte."
379            )]
380            #[allow(non_snake_case)]
381            pub fn $type_(&self, name: &str) -> Option<[<Mj $type_:camel $info_type Info>]> {
382                let model_ref = self$(.$model())?;
383                let id = model_ref.name_to_id(MjtObj::[<mjOBJ_ $type_:upper>], name)?;
384                let model_ffi = model_ref.ffi();
385
386                let id = id as usize;
387                $(
388                    let $attr = (id * $len, $len);
389                )*
390                $(
391                    let $attr_ffi = (
392                        id * model_ffi.$len_ffi as usize $( * $multiplier)*,
393                        model_ffi.$len_ffi as usize $( * $multiplier)*,
394                    );
395                )*
396                $(
397                    let $attr_dyn = {
398                        let (dyn_start, dyn_len) = $crate::mj_model_dyn_range!(model_ref, id, $ffi_len_dyn);
399                        (dyn_start $(* $offset_mult)?, dyn_len $(* $offset_mult)?)
400                    };
401                )*
402
403                let model_signature = model_ffi.signature;
404                Some([<Mj $type_:camel $info_type Info>] { name: name.to_string(), id, model_signature, $($attr,)* $($attr_ffi,)* $($attr_dyn),* })
405            }
406        }
407    }
408}
409
410
411/// Generates `Info`, `ViewMut`, and `View` types for a named MuJoCo object, along with
412/// `view`, `try_view`, `view_mut`, and `try_view_mut` methods on the `Info` type.
413///
414/// # Field lists
415///
416/// - **`[rw fields]`**: read-write: `PointerViewMut` in `ViewMut`, `PointerView` in `View`.
417/// - **`[ro fields]`**: read as `PointerViewUnsafeMut` in `ViewMut` (unsafe to mutate), `PointerView` in `View`.
418/// - **`[opt fields]`**: optional read-write: `Option<PointerViewMut>` / `Option<PointerView>`.
419///
420/// # Field entry syntax
421///
422/// ```text
423/// [prefix_] field_name : ElementType [force]
424/// ```
425///
426/// - `[prefix_]`: optional prefix prepended to the FFI field name (e.g. `[actuator_]`).
427/// - `[force]`: emit a forced pointer cast via [`maybe_force_cast!`] (needed when the Rust
428///   element type differs from the C array element type, e.g. `f64` -> `[f64; 3]`).
429#[doc(hidden)]
430#[macro_export]
431macro_rules! info_with_view {
432    (
433        $info_type:ident, $name:ident,
434        [$($([$prefix_attr:ident])? $attr:ident: $type_:ty $([$force:ident])?),*],
435        [$($([$prefix_attr_ro:ident])? $attr_ro:ident: $type_ro:ty $([$force_ro:ident])?),*],
436        [$($([$prefix_opt_attr:ident])? $opt_attr:ident: $type_opt:ty $([$force_opt:ident])?),*]
437        $(,$generics:ty: $bound:ty)?
438    ) => {
439        paste::paste! {
440            #[doc = "Index ranges required to create views into [`Mj" $info_type "`] arrays for a " $name "."]
441            #[allow(non_snake_case)]
442            #[derive(Debug, Clone)]
443            pub struct [<Mj $name:camel $info_type Info>] {
444                /// Name of the element.
445                pub name: String,
446                /// Index of the element.
447                pub id: usize,
448                model_signature: u64,
449                $(
450                    $attr: (usize, usize),
451                )*
452                $(
453                    $attr_ro: (usize, usize),
454                )*
455                $(
456                    $opt_attr: (usize, usize),
457                )*
458            }
459
460            impl [<Mj $name:camel $info_type Info>] {
461                /// Returns the model signature this `Info` was created from.
462                pub fn model_signature(&self) -> u64 {
463                    self.model_signature
464                }
465
466                #[doc = concat!(
467                    "Returns a mutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
468                    "Fields listed as read-only use [`PointerViewUnsafeMut`](crate::util::PointerViewUnsafeMut): ",
469                    "read is safe, mutation requires [`as_mut_slice`](crate::util::PointerViewUnsafeMut::as_mut_slice) and `unsafe`.\n\n",
470                    "# Errors\n",
471                    "Returns [`SignatureMismatch`](", stringify!([<Mj $info_type Error>]), "::SignatureMismatch) if `",
472                    stringify!($info_type), "` was built from a different model than this `Info`."
473                )]
474                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>]> {
475                    let destination_signature = [<$info_type:lower>].signature();
476                    if self.model_signature != destination_signature {
477                        return Err($crate::error::[<Mj $info_type Error>]::SignatureMismatch {
478                            source: self.model_signature,
479                            destination: destination_signature,
480                        });
481                    }
482                    Ok(view_creator!(self, [<Mj $name:camel $info_type ViewMut>], [<$info_type:lower>].ffi(),
483                        [$($([$prefix_attr])? $attr : $type_ $([$force])?),*],
484                        [$($([$prefix_attr_ro])? $attr_ro : $type_ro $([$force_ro])?),*],
485                        [$($([$prefix_opt_attr])? $opt_attr : $type_opt $([$force_opt])?),*],
486                        $crate::util::PointerViewMut::new,
487                        $crate::util::PointerViewUnsafeMut::new))
488                }
489
490                #[doc = concat!(
491                    "Returns a mutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
492                    "Fields listed as read-only use [`PointerViewUnsafeMut`](crate::util::PointerViewUnsafeMut): ",
493                    "read is safe, mutation requires [`as_mut_slice`](crate::util::PointerViewUnsafeMut::as_mut_slice) and `unsafe`.\n\n",
494                    "# Panics\n",
495                    "Panics if `", stringify!($info_type), "` was built from a different model than this `Info`. ",
496                    "Use [`try_view_mut`](Self::try_view_mut) to handle this as a `Result`."
497                )]
498                pub fn view_mut<'p $(, $generics: $bound)?>(&self, [<$info_type:lower>]: &'p mut [<Mj $info_type>]$(<$generics>)?) -> [<Mj $name:camel $info_type ViewMut>]<'p> {
499                    self.try_view_mut([<$info_type:lower>]).unwrap_or_else(|_| panic!("model signature mismatch"))
500                }
501
502                #[doc = concat!(
503                    "Returns an immutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
504                    "# Errors\n",
505                    "Returns [`SignatureMismatch`](", stringify!([<Mj $info_type Error>]), "::SignatureMismatch) if `",
506                    stringify!($info_type), "` was built from a different model than this `Info`."
507                )]
508                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>]> {
509                    let destination_signature = [<$info_type:lower>].signature();
510                    if self.model_signature != destination_signature {
511                        return Err($crate::error::[<Mj $info_type Error>]::SignatureMismatch {
512                            source: self.model_signature,
513                            destination: destination_signature,
514                        });
515                    }
516                    Ok(view_creator!(self, [<Mj $name:camel $info_type View>], [<$info_type:lower>].ffi(),
517                        [$($([$prefix_attr])? $attr : $type_ $([$force])?),*],
518                        [$($([$prefix_attr_ro])? $attr_ro : $type_ro $([$force_ro])?),*],
519                        [$($([$prefix_opt_attr])? $opt_attr : $type_opt $([$force_opt])?),*],
520                        $crate::util::PointerView::new,
521                        $crate::util::PointerView::new))
522                }
523
524                #[doc = concat!(
525                    "Returns an immutable view into the [`Mj", stringify!($info_type), "`] arrays for this ", stringify!($name), ".\n\n",
526                    "# Panics\n",
527                    "Panics if `", stringify!($info_type), "` was built from a different model than this `Info`. ",
528                    "Use [`try_view`](Self::try_view) to handle this as a `Result`."
529                )]
530                pub fn view<'p $(, $generics: $bound)?>(&self, [<$info_type:lower>]: &'p [<Mj $info_type>]$(<$generics>)?) -> [<Mj $name:camel $info_type View>]<'p> {
531                    self.try_view([<$info_type:lower>]).unwrap_or_else(|_| panic!("model signature mismatch"))
532                }
533            }
534
535            #[doc = "Mutable view into [`Mj" $info_type "`] arrays for a " $name ".\n\n"
536                    "Read-write fields are [`PointerViewMut`](crate::util::PointerViewMut); "
537                    "read-only fields are [`PointerViewUnsafeMut`](crate::util::PointerViewUnsafeMut), "
538                    "which require [`as_mut_slice`](crate::util::PointerViewUnsafeMut::as_mut_slice) and explicit `unsafe` to mutate."]
539            #[allow(non_snake_case)]
540            #[derive(Debug)]
541            pub struct [<Mj $name:camel $info_type ViewMut>]<'d> {
542                $(
543                    #[doc = concat!("Mutable view of `", stringify!($attr), "`.")]
544                    pub $attr: $crate::util::PointerViewMut<'d, $type_>,
545                )*
546                $(
547                    #[doc = concat!("Read-only view of `", stringify!($attr_ro), "`. Requires `unsafe` for mutation.")]
548                    pub $attr_ro: $crate::util::PointerViewUnsafeMut<'d, $type_ro>,
549                )*
550                $(
551                    #[doc = concat!("Optional mutable view of `", stringify!($opt_attr), "`.")]
552                    pub $opt_attr: Option<$crate::util::PointerViewMut<'d, $type_opt>>,
553                )*
554            }
555
556            impl [<Mj $name:camel $info_type ViewMut>]<'_> {
557                /// Zeroes all read-write fields. Read-only fields are left unchanged.
558                pub fn zero(&mut self) {
559                    $(
560                        self.$attr.fill(bytemuck::Zeroable::zeroed());
561                    )*
562                    $(
563                        if let Some(x) = &mut self.$opt_attr {
564                            x.fill(bytemuck::Zeroable::zeroed());
565                        }
566                    )*
567                }
568            }
569
570            #[doc = "Immutable view into [`Mj" $info_type "`] arrays for a " $name "."]
571            #[allow(non_snake_case)]
572            #[derive(Debug)]
573            pub struct [<Mj $name:camel $info_type View>]<'d> {
574                $(
575                    #[doc = concat!("View of `", stringify!($attr), "`.")]
576                    pub $attr: $crate::util::PointerView<'d, $type_>,
577                )*
578                $(
579                    #[doc = concat!("View of `", stringify!($attr_ro), "`.")]
580                    pub $attr_ro: $crate::util::PointerView<'d, $type_ro>,
581                )*
582                $(
583                    #[doc = concat!("Optional view of `", stringify!($opt_attr), "`.")]
584                    pub $opt_attr: Option<$crate::util::PointerView<'d, $type_opt>>,
585                )*
586            }
587        }
588    };
589}
590
591/// Generates getters, setters, and builder methods for struct fields.
592///
593/// ## Optional value constraints
594///
595/// Every arm that writes a value (`set`, `with`, `[&] with`, their `force!` variants, and the
596/// `get, set` / `with, get, set` / `with, get` aggregates) accepts an **optional per-field check**
597/// for fields whose value can be invalid. Bool arms have no check (a bool is always in range).
598///
599/// The check is given as `{ check, "reason" }`, where `check` is the validating function
600/// (`Fn(Type) -> Result<(), ErrType>`) and `"reason"` is a doc fragment, written in the crate's usual
601/// `# Errors` style, that names the error and the condition, e.g.
602/// `"[`MjEditError::InvalidParameter`] when `value` is out of range"`. The macro reuses that one
603/// fragment to generate the per-method docs:
604///
605/// - On a `set`-style field: `name: Type { check, "reason" } => ErrType; "comment"` makes `set_name`
606///   return `Result<(), ErrType>` (the check runs first; on `Err` the field is left unchanged) and
607///   appends `# Errors` reading `Returns <reason>.`.
608/// - On a `with`-style field: `name: Type { check, "reason" }; "comment"` makes the builder
609///   `with_name` call `check` and **panic** (`expect`) on `Err`, since a builder must keep returning
610///   `Self`, and appends `# Panics` reading `Panics with <reason>.`.
611/// - In the combined `with, get, set` aggregate, supply `{ check, "reason" } => ErrType`; the check
612///   and reason are wired into both the fallible `set_name` and the panicking `with_name`.
613///
614/// The `"comment"` describes the field itself and is shared verbatim by the getter, setter, and
615/// builder; the getter never gets an `# Errors`/`# Panics` section, so do **not** hand-write
616/// error/panic notes into `"comment"` — put them in the check `"reason"` instead.
617///
618/// Omitting the check yields the plain infallible setter/builder.
619#[doc(hidden)]
620#[macro_export]
621macro_rules! getter_setter {
622
623    // Helper expansions
624    /*****************************************/
625    (@cast force { $($content:tt)* } ) => {
626        $crate::util::force_cast($($content)*)
627    };
628
629    (@cast  { $($content:tt)* } ) => {
630        $($content)*.into()
631    };
632
633    // Regular arms
634    /*****************************************/
635    (get, [$($([$ffi:ident])? $name:ident $(+ $symbol:tt)?: bool; $comment:expr);* $(;)?]) => {paste::paste!{
636        $(
637            #[doc = concat!("Check ", $comment)]
638            pub fn [<$name:camel:snake $($symbol)?>](&self) -> bool {
639                (self$(.$ffi())?.$name as i32) != 0
640            }
641        )*
642    }};
643
644    (get, [$($([$ffi:ident $(,$ffi_mut:ident)?])? $((allow_mut = $cfg_mut:literal))? $name:ident $(+ $symbol:tt)?: & $type:ty; $comment:expr);* $(;)?]) => {paste::paste!{
645        $(
646            #[doc = concat!("Return an immutable reference to ", $comment)]
647            pub fn [<$name:camel:snake $($symbol)?>](&self) -> &$type {
648                &self$(.$ffi())?.$name
649            }
650
651            $crate::eval_or_expand! {
652                @eval $($cfg_mut)? {
653                    #[doc = concat!("Return a mutable reference to ", $comment)]
654                    pub fn [<$name:camel:snake _mut>](&mut self) -> &mut $type {
655                        #[allow(unused_unsafe)]
656                        unsafe { &mut self$(.$($ffi_mut())?)?.$name }
657                    }
658                }
659            }
660        )*
661    }};
662
663    (get, [$($([$ffi:ident])? $name:ident $(+ $symbol:tt)?: $type:ty $([$cast_type:tt])?; $comment:expr);* $(;)?]) => {paste::paste!{
664        $(
665            #[doc = concat!("Return value of ", $comment)]
666            pub fn [<$name:camel:snake $($symbol)?>](&self) -> $type {
667                #[allow(unused_unsafe)]
668                unsafe { $crate::getter_setter!(@cast $($cast_type)? { self$(.$ffi())?.$name }) }
669            }
670        )*
671    }};
672
673    (set, [$($([$ffi_mut:ident])? $name:ident: $type:ty $([$cast_type:tt])? $({$check:expr , $reason:literal})? $(=> $err:ty)?; $comment:expr);* $(;)?]) => {
674        paste::paste!{
675            $(
676                #[doc = concat!("Set ", $comment $(, "\n\n# Errors\nReturns ", $reason, ".")?)]
677                pub fn [<set_ $name:camel:snake>](&mut self, value: $type) $(-> Result<(), $err>)? {
678                    $(($check)(value)?;)?
679                    #[allow(unused_unsafe)]
680                    unsafe { self$(.$ffi_mut())?.$name = $crate::getter_setter!(@cast $($cast_type)? { value }) };
681                    $(Ok::<(), $err>(()))?
682                }
683            )*
684        }
685    };
686
687    /* Builder pattern */
688    (with, [$($inner:tt)*]) => {
689        $crate::getter_setter!(@with_body [Self], [$($inner)*]);
690    };
691    ([&] with, [$($inner:tt)*]) => {
692        $crate::getter_setter!(@with_body [&mut Self], [$($inner)*]);
693    };
694
695    (@with_body [$ret_ty:ty], [$($([$ffi_mut:ident])? $name:ident: $type:ty $([$cast_type:tt])? $({$check:expr , $reason:literal})?; $comment:expr);* $(;)?]) => {
696        paste::paste!{
697            $(
698                #[allow(unused_mut)]
699                #[doc = concat!("Builder method for setting ", $comment $(, "\n\n# Panics\nPanics with ", $reason, ".")?)]
700                pub fn [<with_ $name:camel:snake>](mut self: $ret_ty, value: $type) -> $ret_ty {
701                    $(($check)(value).expect("invalid builder argument");)?
702                    #[allow(unused_unsafe)]
703                    unsafe { self$(.$ffi_mut())?.$name = $crate::getter_setter!(@cast $($cast_type)? { value }) };
704                    self
705                }
706            )*
707        }
708    };
709
710    /* Handling of optional arguments */
711    (get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : bool ; $comment:expr );* $(;)?]) => {
712        $crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)? : bool ; $comment );* ]);
713        $crate::getter_setter!(set, [ $($([$ffi_mut])? $name : bool ; $comment );* ]);
714    };
715
716    (get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : $type:ty $([$cast_type:tt])? $({$check:expr , $reason:literal})? $(=> $err:ty)?; $comment:expr );* $(;)?]) => {
717        $crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)? : $type $([$cast_type])? ; $comment );* ]);
718        $crate::getter_setter!(set, [ $($([$ffi_mut])? $name : $type $([$cast_type])? $({$check , $reason})? $(=> $err)?; $comment );* ]);
719    };
720
721    /* Builder pattern */
722    ($([$token:tt])? with, get, set, [ $($([$ffi: ident, $ffi_mut:ident])? $name:ident $(+ $symbol:tt)? : bool ; $comment:expr );* $(;)?]) => {
723        $crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)? : bool ; $comment );* ]);
724        $crate::getter_setter!(set, [ $($([$ffi_mut])? $name : bool ; $comment );* ]);
725        $crate::getter_setter!($([$token])? with, [ $($([$ffi_mut])? $name : bool ; $comment );* ]);
726    };
727
728    ($([$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 );* $(;)?]) => {
729        $crate::getter_setter!(get, [ $($([$ffi])? $name $(+ $symbol)?: $type $([$cast_type])? ; $comment );* ]);
730        $crate::getter_setter!(set, [ $($([$ffi_mut])? $name : $type $([$cast_type])? $({$check , $reason})? $(=> $err)?; $comment );* ]);
731        $crate::getter_setter!($([$token])? with, [ $($([$ffi_mut])? $name : $type $([$cast_type])? $({$check , $reason})?; $comment );* ]);
732    };
733
734    ($([$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 );* $(;)?]) => {
735        $crate::getter_setter!(get, [ $($([$ffi, $ffi_mut])? $((allow_mut = $allow_mut))? $name $(+ $symbol)? : & $type ; $comment );* ]);
736        $crate::getter_setter!($([$token])? with, [ $( $([$ffi_mut])? $name : $type $({$check , $reason})? ; $comment );* ]);
737    };
738}
739
740
741#[doc(hidden)]
742#[macro_export]
743/// Constructs builder methods.
744macro_rules! builder_setters {
745    ($($name:ident: $type:ty $(where $generic_type:ident: $generic:path)?; $comment:expr);* $(;)?) => {
746        $(
747            #[doc = concat!("Set ", $comment)]
748            pub fn $name$(<$generic_type: $generic>)?(mut self, value: $type) -> Self {
749                self.$name = value.into();
750                self
751            }
752        )*
753    };
754}
755
756/// Helper macro for conditionally generating `# Safety` docs on mutable array slice methods.
757/// When `unsafe` is passed (method is unsafe), includes the safety section.
758/// When no `unsafe` is passed (method is safe), only generates the basic doc.
759#[doc(hidden)]
760#[macro_export]
761macro_rules! array_mut_doc {
762    (unsafe, $doc:literal) => {
763        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.")
764    };
765    ($doc:literal) => {
766        concat!("Mutable slice of the ", $doc, " array.")
767    };
768}
769
770/// A macro for creating a slice over a raw array of dynamic size (given by some other variable in $len_accessor).
771/// Syntax: attribute: <optional pre-transformations (e.g., `as_ptr as_mut_ptr`)> &[datatype; documentation string;
772/// code to access the length attribute, appearing after `self.`]
773/// Syntax for arrays whose size is a sum from some length array:
774///     summed {
775///         ...
776///         attribute: &[datatype; documentation; [
777///                 size multiplier;
778///                 (code to access the length array, appearing after self);
779///                 (code to access the length array's length, appearing after self)
780///             ]
781///         ],
782///         ...
783///     }
784///
785#[doc(hidden)]
786#[macro_export]
787macro_rules! array_slice_dyn {
788    // Arrays that are of scalar variable size
789    ($($((mut = $unsafe_mut:ident))? $name:ident: $($as_ptr:ident $as_mut_ptr:ident)? &[$type:ty $([$force:ident])?; $doc:literal; $($len_accessor:tt)*]),*) => {
790        paste::paste! {
791            $(
792                #[doc = concat!("Immutable slice of the ", $doc," array.")]
793                pub fn [<$name:camel:snake>](&self) -> &[$type] {
794                    let length = self.$($len_accessor)* as usize;
795                    let ptr = $crate::maybe_force_cast!(self.ffi().$name$(.$as_ptr())?, $type $(, $force)?);
796                    if ptr.is_null() || length == 0 {
797                        return &[];
798                    }
799                    unsafe { std::slice::from_raw_parts(ptr, length) }
800                }
801
802                #[doc = $crate::array_mut_doc!($($unsafe_mut,)? $doc)]
803                pub $($unsafe_mut)? fn [<$name:camel:snake _mut>](&mut self) -> &mut [$type] {
804                    let length = self.$($len_accessor)* as usize;
805                    let ptr = $crate::maybe_force_cast!(unsafe { self.ffi_mut().$name$(.$as_mut_ptr())? }, $type $(, $force)?);
806                    if ptr.is_null() || length == 0 {
807                        return &mut [];
808                    }
809                    unsafe { std::slice::from_raw_parts_mut(ptr, length) }
810                }
811            )*
812        }
813    };
814
815    // Arrays that are of summed variable size
816    (summed { $( $(($unsafe_mut:ident))? $name:ident: &[$type:ty; $doc:literal; [$multiplier:literal ; ($($len_array:tt)*) ; ($($len_array_length:tt)*)]]),* }) => {
817        paste::paste! {
818            $(
819                #[doc = concat!("Immutable slice of the ", $doc," array.")]
820                pub fn [<$name:camel:snake>](&self) -> &[[$type; $multiplier]] {
821                    let length_array_length = self.$($len_array_length)* as usize;
822                    let data_ptr = self.ffi().$name;
823                    let length_ptr = self.$($len_array)*;
824                    if data_ptr.is_null() || length_ptr.is_null() || length_array_length == 0 {
825                        return &[];
826                    }
827
828                    let length = unsafe { std::slice::from_raw_parts(
829                        length_ptr,
830                        length_array_length
831                    ).iter().map(|&x| x as u32).sum::<u32>() as usize };
832
833                    if length == 0 {
834                        return &[];
835                    }
836
837                    unsafe { std::slice::from_raw_parts($crate::maybe_force_cast!(data_ptr, [$type; $multiplier], force), length) }
838                }
839                
840                #[doc = $crate::array_mut_doc!($($unsafe_mut,)? $doc)]
841                pub $($unsafe_mut)? fn [<$name:camel:snake _mut>](&mut self) -> &mut [[$type; $multiplier]] {
842                    let length_array_length = self.$($len_array_length)* as usize;
843                    let data_ptr = unsafe { self.ffi_mut().$name };
844                    let length_ptr = self.$($len_array)*;
845                    if data_ptr.is_null() || length_ptr.is_null() || length_array_length == 0 {
846                        return &mut [];
847                    }
848
849                    let length = unsafe { std::slice::from_raw_parts(
850                        length_ptr,
851                        length_array_length
852                    ).iter().map(|&x| x as u32).sum::<u32>() as usize };
853
854                    if length == 0 {
855                        return &mut [];
856                    }
857
858                    unsafe { std::slice::from_raw_parts_mut($crate::maybe_force_cast!(data_ptr, [$type; $multiplier], force), length) }
859                }
860            )*
861        }
862    };
863
864    // Arrays whose second dimension is dependent on some variable
865    (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)*]),*}) => {
866        paste::paste! {
867            $(
868                #[doc = concat!("Immutable slice of the ", $doc," array.")]
869                pub fn [<$name:camel:snake>](&self) -> &[$type] {
870                    let length = self.$($len_accessor)* as usize * (self.$($inner_len_accessor)*) as usize;
871                    let ptr = $crate::maybe_force_cast!(self.ffi().$name$(.$as_ptr())?, $type $(, $force)?);
872                    if ptr.is_null() || length == 0 {
873                        return &[];
874                    }
875
876                    unsafe { std::slice::from_raw_parts(ptr, length) }
877                }
878
879                #[doc = $crate::array_mut_doc!($($unsafe_mut,)? $doc)]
880                pub $($unsafe_mut)? fn [<$name:camel:snake _mut>](&mut self) -> &mut [$type] {
881                    let length = self.$($len_accessor)* as usize * (self.$($inner_len_accessor)*) as usize;
882                    let ptr = $crate::maybe_force_cast!(unsafe { self.ffi_mut().$name$(.$as_mut_ptr())? }, $type $(, $force)?);
883                    if ptr.is_null() || length == 0 {
884                        return &mut [];
885                    }
886                    unsafe { std::slice::from_raw_parts_mut(ptr, length) }
887                }
888            )*
889        }
890    };
891}
892
893/// Generates getter and setter methods for converting between Rust's &str type and C's char arrays.
894///
895/// # Safety
896/// The generated getters blindly interpret a `char` array as a C string; the
897/// array must be NUL-terminated and contain valid UTF-8. Setters validate
898/// ASCII encoding and will **panic** if the value exceeds the buffer length.
899/// The macro works by first specifying the methods to create (get = getter, set = setter) --- c_str_as_str_method {get, set, {...}} ---
900/// and then providing the rest of the parameters.
901/// 
902/// The rest of the parameters are recursive and are as follows:
903/// - ffi (optional): name of the method that returns some lower-level struct,
904///                   which contains the actual attributes we want to read;
905/// - name: the attribute name;
906/// - sub_index_name: sub_index_type (optional): creates an additional parameter which indexes the `name` array
907///                   in order to get a sub-array
908///                   (e.g., `name` could be `[[i8; 100]; 10]` and we wish to get `[i8; 100]`);
909/// - comment: the documentation comment to insert as the methods documentation.
910/// 
911#[doc(hidden)]
912#[macro_export]
913macro_rules! c_str_as_str_method {
914    (get {$($([$ffi:ident])? $name:ident $([$sub_index_name:ident: $sub_index_type:ty])?; $comment:literal; )*}) => {
915        $(
916            #[doc = concat!("Returns ", $comment, "\n\n# Panics", "\nPanics if the buffer has no NUL terminator or if the resulting string contains invalid UTF-8.")]
917            pub fn $name(&self $(, $sub_index_name: $sub_index_type)? ) -> &str {
918                let bytes: &[u8] = bytemuck::cast_slice(&self$(.$ffi())?.$name$([$sub_index_name])?[..]);
919                std::ffi::CStr::from_bytes_until_nul(bytes)
920                    .expect("no NUL terminator in C string buffer")
921                    .to_str().unwrap()
922            }
923        )*
924    };
925
926    (set {$($([$ffi:ident])? $name:ident $([$sub_index_name:ident: $sub_index_type:ty])?; $comment:literal; )*}) => {paste::paste!{
927        $(
928            #[doc = concat!("Sets ", $comment, "\n\n# Panics", "\nPanics when `", stringify!($name), "` contains invalid ASCII, an interior NUL byte, or is too long.")]
929            pub fn [<set_ $name>](&mut self, $($sub_index_name: $sub_index_type,)? $name: &str) {
930                $crate::util::write_ascii_to_buf(
931                    &mut self$(.$ffi())?.$name$([$sub_index_name])?,
932                    $name,
933                );
934            }
935        )*
936    }};
937
938    (with {$($([$ffi:ident])? $name:ident $([$sub_index_name:ident: $sub_index_type:ty])?; $comment:literal; )*}) => {paste::paste!{
939        $(
940            #[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.")]
941            pub fn [<with_ $name>](mut self, $($sub_index_name: $sub_index_type,)? $name: &str) -> Self {
942                $crate::util::write_ascii_to_buf(
943                    &mut self$(.$ffi())?.$name$([$sub_index_name])?,
944                    $name,
945                );
946                self
947            }
948        )*
949    }};
950
951    // Mixed patterns
952    (with, get, set {$($other:tt)*}) => {
953        $crate::c_str_as_str_method!(get {$($other)*});
954        $crate::c_str_as_str_method!(set {$($other)*});
955        $crate::c_str_as_str_method!(with {$($other)*});
956    };
957
958    (get, set {$($other:tt)*}) => {
959        $crate::c_str_as_str_method!(get {$($other)*});
960        $crate::c_str_as_str_method!(set {$($other)*});
961    };
962
963    (with, set {$($other:tt)*}) => {
964        $crate::c_str_as_str_method!(set {$($other)*});
965        $crate::c_str_as_str_method!(with {$($other)*});
966    };
967
968    (with, get {$($other:tt)*}) => {
969        $crate::c_str_as_str_method!(get {$($other)*});
970        $crate::c_str_as_str_method!(with {$($other)*});
971    };
972}
973
974/// assert_eq!, but with tolerance for floating point rounding.
975#[doc(hidden)]
976#[macro_export]
977macro_rules! assert_relative_eq {
978    ($a:expr, $b:expr, epsilon = $eps:expr) => {{
979        let (a, b, eps) = ($a as f64, $b as f64, $eps as f64);
980        assert!((a - b).abs() <= eps, "left={:?} right={:?} eps={:?}", a, b, eps);
981    }};
982}
983
984
985/// Tries to cast $value into requested type.
986/// # Panics
987/// Panics if the cast fails.
988#[doc(hidden)]
989#[macro_export]
990macro_rules! cast_mut_info {
991    ($value:expr $(, $debug_expr:expr)?) => {
992        {
993            match bytemuck::checked::try_cast_mut($value) {
994                Ok(v) => v,
995                Err(e) => {
996                    let evaluated = format!("{:?}", $value);
997                    #[allow(unused)]
998                    let mut debug_info = String::new();
999                    $(
1000                        debug_info = format!(" (debug info: '{} = {}')", stringify!($debug_expr), $debug_expr);
1001                    )?
1002
1003                    panic!(
1004                        "failed to cast expression '{}', which evaluates to '{}' into requested type (error: {})\
1005                         {debug_info} --- \
1006                         most likely you have a bug in your program.",
1007                        stringify!($value), evaluated, e
1008                    );
1009                }
1010            }
1011        }
1012    };
1013}
1014
1015/// Asserts that the MuJoCo version used matches
1016/// the one MuJoCo-rs was compiled with.
1017///
1018/// # Panics
1019/// Panics if the linked MuJoCo library version does not match
1020/// the version MuJoCo-rs was compiled against.
1021pub fn assert_mujoco_version() {
1022    // SAFETY: mj_version() is a pure query function with no side effects; safe to call at any
1023    // time after the library is loaded.
1024    let linked_version = unsafe { mj_version() as u32 };
1025    let mujoco_rs_version_string = option_env!("CARGO_PKG_VERSION").unwrap_or_else(|| "unknown+mj-unknown");
1026    assert_eq!(
1027        linked_version, mjVERSION_HEADER,
1028        "linked MuJoCo version value ({linked_version}) does not match expected version value ({mjVERSION_HEADER}), \
1029        with which MuJoCo-rs {mujoco_rs_version_string} FFI bindings were generated.",
1030    );
1031}
1032
1033
1034/// Forcefully casts a value of type `T` to type `U`.
1035/// Performs compile-time size and alignment checks, but does **not** guarantee
1036/// that the bit patterns are compatible.
1037///
1038/// # Safety
1039/// The bit pattern of `val` must be a valid representation for type `U`;
1040/// otherwise the behavior is undefined.
1041#[inline(always)]
1042pub unsafe fn force_cast<T, U>(val: T) -> U {
1043    const {
1044        // The underlying type should be the same in representation.
1045        assert!(std::mem::size_of::<T>() == std::mem::size_of::<U>());
1046        assert!(std::mem::align_of::<T>() == std::mem::align_of::<U>());
1047    }
1048    #[repr(C)]
1049    union Transmuter<T, U> {
1050        from: std::mem::ManuallyDrop<T>,
1051        to: std::mem::ManuallyDrop<U>,
1052    }
1053    // SAFETY: size and alignment equality is verified by the const assertions above; the caller
1054    // guarantees bit-pattern validity (see # Safety).
1055    unsafe { std::mem::ManuallyDrop::into_inner(Transmuter { from: std::mem::ManuallyDrop::new(val) }.to) }
1056}
1057
1058
1059/// Asserts at compile time that casting from `Src` to `Dst` is
1060/// size-and-alignment compatible.
1061///
1062/// The target element size must be a multiple of the source element size
1063/// (covers both same-size type conversions and array-grouping casts like
1064/// `*const f64` to `*const [f64; 3]`), and the source and target alignments
1065/// must be equal.
1066///
1067/// The pointer argument is only used for type inference of `Src`; it is
1068/// never dereferenced.
1069#[inline(always)]
1070pub const fn assert_ptr_cast_valid<Src, Dst>(_ptr: *const Src) {
1071    const {
1072        assert!(std::mem::size_of::<Dst>().is_multiple_of(std::mem::size_of::<Src>()),
1073            "ptr cast: target size must be a multiple of source size");
1074
1075        // The underlying type should have the same alignment. This is for converting
1076        // between e.g. *f64 to *[f64; 3].
1077        assert!(std::mem::align_of::<Src>() == std::mem::align_of::<Dst>(),
1078            "ptr cast: source alignment must be == target alignment");
1079    }
1080}
1081
1082/// Conditionally casts a raw pointer to `$type` with compile-time
1083/// size and alignment checks.  When the `force` token is absent the
1084/// pointer is returned as-is.
1085#[doc(hidden)]
1086#[macro_export]
1087macro_rules! maybe_force_cast {
1088    ($ptr:expr, $type:ty) => { $ptr };
1089    ($ptr:expr, $type:ty, force) => {{
1090        let ptr = $ptr;
1091        $crate::util::assert_ptr_cast_valid::<_, $type>(ptr as *const _);
1092        ptr.cast::<$type>()
1093    }};
1094}
1095
1096
1097/* Utility traits */
1098/// Locks a synchronization primitive and resets its poison status.
1099/// This is useful on locations that don't need any special handling
1100/// after a thread panicked while holding a mutex lock.
1101pub trait LockUnpoison<T> {
1102    /// Locks the synchronization primitive, resetting its poison status if necessary.
1103    fn lock_unpoison(&self) -> MutexGuard<'_, T>;
1104}
1105
1106/// Implements automatic unpoisoning on the [`Mutex`].
1107impl<T> LockUnpoison<T> for Mutex<T> {
1108    fn lock_unpoison(&self) -> MutexGuard<'_, T> {
1109        match self.lock() {
1110            Ok(lock) => lock,
1111            Err(e) => {
1112                self.clear_poison();
1113                e.into_inner()
1114            }
1115        }
1116    }
1117}
1118
1119#[cfg(feature = "viewer")]
1120/// Performs a three-way merge of a value.
1121///
1122/// Given three versions of a value (`self`, `other`, `other_prev`), the merge
1123/// updates `self` when `other` has changed relative to `other_prev`, then
1124/// writes the resolved value back into both `other` and `other_prev` so the
1125/// next call starts from a consistent baseline.
1126pub(crate) trait ThreeWayMerge {
1127    /// Merges `other` into `self` using `other_prev` as the baseline.
1128    fn merge(&mut self, other: &mut Self, other_prev: &mut Self);
1129}
1130
1131#[cfg(feature = "viewer")]
1132impl<T: Copy + PartialEq> ThreeWayMerge for T {
1133    #[inline]
1134    fn merge(&mut self, other: &mut Self, other_prev: &mut Self) {
1135        if *other != *other_prev {
1136            *self = *other;
1137        }
1138
1139        *other = *self;
1140        *other_prev = *other;
1141    }
1142}
1143
1144#[cfg(test)]
1145mod tests {
1146    use std::sync::{Arc, Mutex};
1147    use std::ffi::c_char;
1148    use super::LockUnpoison;
1149
1150    /// Verifies that `lock_unpoison` recovers a poisoned mutex and preserves the inner value.
1151    #[test]
1152    fn test_lock_unpoison_recovers_poisoned_mutex() {
1153        let mutex = Arc::new(Mutex::new(42_i32));
1154        let mutex_clone = Arc::clone(&mutex);
1155
1156        // Panic while holding the lock to poison it.
1157        let _ = std::panic::catch_unwind(move || {
1158            let _guard = mutex_clone.lock().unwrap();
1159            panic!("intentional panic to poison mutex");
1160        });
1161
1162        // The mutex must be poisoned now.
1163        assert!(mutex.lock().is_err(), "mutex should be poisoned");
1164
1165        // lock_unpoison must recover the lock and preserve the value.
1166        let value = *mutex.lock_unpoison();
1167        assert_eq!(value, 42, "inner value must be preserved after unpoison");
1168
1169        // After unpoison, regular lock must succeed.
1170        assert!(mutex.lock().is_ok(), "mutex should no longer be poisoned");
1171    }
1172
1173    /// A non-negative-only check used across the comprehensive arm tests below.
1174    fn non_negative(v: i32) -> Result<(), &'static str> {
1175        if v >= 0 { Ok(()) } else { Err("must be non-negative") }
1176    }
1177
1178    /// An array check (first element non-negative) used by the reference-array arm tests below.
1179    fn first_non_negative(a: [f64; 2]) -> Result<(), &'static str> {
1180        if a[0] >= 0.0 { Ok(()) } else { Err("first must be non-negative") }
1181    }
1182
1183    /// Comprehensively exercises **every** arm of `getter_setter!` on minimal dummy structs,
1184    /// verifying both the generated method shapes and the optional `{ check }` constraint on each
1185    /// arm that writes a value. Grouped by arm; a separate struct per arm avoids method-name
1186    /// collisions.
1187    #[test]
1188    #[allow(clippy::bool_assert_comparison)]
1189    // Several arms generate companion methods (e.g. a `set_*` alongside the `with_*`/getter under
1190    // test) that this test intentionally does not all call -- it verifies generation, not every path.
1191    #[allow(dead_code)]
1192    fn test_getter_setter_all_arms() {
1193        use crate::getter_setter as gs;
1194
1195        // --- Getter arms ---------------------------------------------------------------------
1196        // Arm: (get, [bool])
1197        struct GetBool { flag: i32 }
1198        impl GetBool { gs!(get, [flag: bool; "flag.";]); }
1199        assert_eq!(GetBool { flag: 1 }.flag(), true);
1200        assert_eq!(GetBool { flag: 0 }.flag(), false);
1201
1202        // Arm: (get, [&Type]) -- default allow_mut generates `_mut`; `allow_mut = false` omits it.
1203        struct GetRef { arr: [f64; 3] }
1204        impl GetRef { gs!(get, [arr: &[f64; 3]; "array.";]); }
1205        let mut gr = GetRef { arr: [1.0, 2.0, 3.0] };
1206        assert_eq!(gr.arr(), &[1.0, 2.0, 3.0]);
1207        gr.arr_mut()[0] = 9.0;
1208        assert_eq!(gr.arr(), &[9.0, 2.0, 3.0]);
1209
1210        struct GetRefNoMut { arr: [f64; 2] }
1211        impl GetRefNoMut { gs!(get, [(allow_mut = false) arr: &[f64; 2]; "array.";]); }
1212        assert_eq!(GetRefNoMut { arr: [4.0, 5.0] }.arr(), &[4.0, 5.0]);
1213
1214        // Arm: (get, [Type]) -- with a `+ symbol` getter-name suffix.
1215        struct GetVal { val: i32 }
1216        impl GetVal { gs!(get, [val + _renamed: i32; "value.";]); }
1217        assert_eq!(GetVal { val: 7 }.val_renamed(), 7);
1218
1219        // Arm: (get, [Type]) with [force] cast
1220        struct ForceGet { v: i32 }
1221        impl ForceGet { gs!(get, [v: i32 [force]; "v.";]); }
1222        assert_eq!(ForceGet { v: 42 }.v(), 42);
1223
1224        // --- Setter / builder base arms ------------------------------------------------------
1225        // Arm: (set, [...]) -- plain and checked.
1226        struct Set { a: i32, b: i32 }
1227        impl Set {
1228            gs!(set, [
1229                a: i32; "a.";
1230                b: i32 { non_negative, "`Err` when negative" } => &'static str; "b.";
1231            ]);
1232        }
1233        let mut s = Set { a: 0, b: 0 };
1234        s.set_a(5);
1235        assert_eq!(s.a, 5);
1236        assert_eq!(s.set_b(3), Ok(()));
1237        assert_eq!(s.b, 3);
1238        assert_eq!(s.set_b(-1), Err("must be non-negative"));
1239        assert_eq!(s.b, 3); // unchanged on error
1240
1241        // Arm: (set, [...]) with [force] cast -- plain and checked.
1242        struct ForceSet { a: i32, b: i32 }
1243        impl ForceSet {
1244            gs!(set, [
1245                a: i32 [force]; "a.";
1246                b: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "b.";
1247            ]);
1248        }
1249        let mut fs = ForceSet { a: 0, b: 0 };
1250        fs.set_a(8);
1251        assert_eq!(fs.a, 8);
1252        assert_eq!(fs.set_b(2), Ok(()));
1253        assert_eq!(fs.set_b(-1), Err("must be non-negative"));
1254        assert_eq!(fs.b, 2);
1255
1256        // Arm: (with, [...]) -- consuming builder, checked (valid input; panic path tested below).
1257        struct With { v: i32 }
1258        impl With { gs!(with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
1259        assert_eq!(With { v: 0 }.with_v(6).v, 6);
1260
1261        // Arm: ([&] with, [...]) -- &mut builder, checked.
1262        struct RefWith { v: i32 }
1263        impl RefWith { gs!([&] with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
1264        let mut rw = RefWith { v: 0 };
1265        rw.with_v(4);
1266        assert_eq!(rw.v, 4);
1267
1268        // Arm: (with, [...]) and ([&] with, [...]) with [force] cast -- checked.
1269        struct ForceWith { v: i32 }
1270        impl ForceWith { gs!(with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
1271        assert_eq!(ForceWith { v: 0 }.with_v(3).v, 3);
1272
1273        struct ForceRefWith { v: i32 }
1274        impl ForceRefWith { gs!([&] with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
1275        let mut frw = ForceRefWith { v: 0 };
1276        frw.with_v(5);
1277        assert_eq!(frw.v, 5);
1278
1279        // --- Aggregate arms ------------------------------------------------------------------
1280        // Arm: (get, set, [...]) with [force] cast -- plain and checked.
1281        struct ForceGetSet { v: i32 }
1282        impl ForceGetSet { gs!(get, set, [v: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "v.";]); }
1283        let mut fgs = ForceGetSet { v: 0 };
1284        assert_eq!(fgs.set_v(11), Ok(()));
1285        assert_eq!(fgs.v(), 11);
1286        assert_eq!(fgs.set_v(-1), Err("must be non-negative"));
1287
1288        // Arm: (get, set, [bool]) and (get, set, [Type] + check).
1289        struct GetSetBool { flag: i32 }
1290        impl GetSetBool { gs!(get, set, [flag: bool; "flag.";]); }
1291        let mut gsb = GetSetBool { flag: 1 };
1292        assert_eq!(gsb.flag(), true);
1293        gsb.set_flag(false);
1294        assert_eq!(gsb.flag(), false);
1295
1296        struct GetSetVal { count: i32, checked: i32 }
1297        impl GetSetVal {
1298            gs!(get, set, [
1299                count: i32; "count.";
1300                checked: i32 { non_negative, "`Err` when negative" } => &'static str; "checked.";
1301            ]);
1302        }
1303        let mut gsv = GetSetVal { count: 5, checked: 0 };
1304        gsv.set_count(10);
1305        assert_eq!(gsv.count(), 10);
1306        assert_eq!(gsv.set_checked(7), Ok(()));
1307        assert_eq!(gsv.checked(), 7);
1308        assert_eq!(gsv.set_checked(-1), Err("must be non-negative"));
1309        assert_eq!(gsv.checked(), 7);
1310
1311        // Arm: (with, get, set, [bool]) and ([&] with, get, set, [bool]).
1312        struct WithGetSetBool { flag: i32 }
1313        impl WithGetSetBool { gs!(with, get, set, [flag: bool; "flag.";]); }
1314        assert_eq!(WithGetSetBool { flag: 0 }.with_flag(true).flag(), true);
1315
1316        struct RefWithGetSetBool { flag: i32 }
1317        impl RefWithGetSetBool { gs!([&] with, get, set, [flag: bool; "flag.";]); }
1318        let mut rwgsb = RefWithGetSetBool { flag: 0 };
1319        rwgsb.with_flag(true);
1320        assert_eq!(rwgsb.flag(), true);
1321
1322        // Arm: (with, get, set, [Type]) and ([&] with, get, set, [Type] + check).
1323        struct WithGetSetVal { v: i32 }
1324        impl WithGetSetVal { gs!(with, get, set, [v: i32; "v.";]); }
1325        let mut wgsv = WithGetSetVal { v: 0 }.with_v(3);
1326        assert_eq!(wgsv.v(), 3);
1327        wgsv.set_v(4);
1328        assert_eq!(wgsv.v(), 4);
1329
1330        struct RefWithGetSetVal { v: i32, w: i32 }
1331        impl RefWithGetSetVal {
1332            gs!([&] with, get, set, [
1333                v: i32; "v.";
1334                w: i32 { non_negative, "`Err` when negative" } => &'static str; "w.";
1335            ]);
1336        }
1337        let mut rwgsv = RefWithGetSetVal { v: 0, w: 0 };
1338        rwgsv.with_v(1);
1339        assert_eq!(rwgsv.v(), 1);
1340        assert_eq!(rwgsv.set_w(2), Ok(()));
1341        assert_eq!(rwgsv.w(), 2);
1342        assert_eq!(rwgsv.set_w(-1), Err("must be non-negative"));
1343
1344        // Arm: (with, get, set, [...]) and ([&] with, get, set, [...]) with [force] cast.
1345        struct ForceWithGetSet { v: i32 }
1346        impl ForceWithGetSet { gs!(with, get, set, [v: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "v.";]); }
1347        let fwgs = ForceWithGetSet { v: 0 }.with_v(9);
1348        assert_eq!(fwgs.v(), 9);
1349
1350        struct ForceRefWithGetSet { v: i32 }
1351        impl ForceRefWithGetSet { gs!([&] with, get, set, [v: i32 [force]; "v.";]); }
1352        let mut frwgs = ForceRefWithGetSet { v: 0 };
1353        frwgs.with_v(6);
1354        assert_eq!(frwgs.v(), 6);
1355
1356        // Arm: (with, get, [&Type]) and ([&] with, get, [&Type]) -- array fields, plain and checked.
1357        struct WithGetRef { arr: [f64; 3] }
1358        impl WithGetRef { gs!(with, get, [arr: &[f64; 3]; "array.";]); }
1359        let wgr = WithGetRef { arr: [0.0; 3] }.with_arr([1.0, 2.0, 3.0]);
1360        assert_eq!(wgr.arr(), &[1.0, 2.0, 3.0]);
1361
1362        struct RefWithGetRef { arr: [f64; 2] }
1363        impl RefWithGetRef { gs!([&] with, get, [arr: &[f64; 2] { first_non_negative, "`Err` when the first element is negative" }; "array.";]); }
1364        let mut rwgr = RefWithGetRef { arr: [0.0; 2] };
1365        rwgr.with_arr([5.0, 6.0]);
1366        assert_eq!(rwgr.arr(), &[5.0, 6.0]);
1367    }
1368
1369    /* `#[should_panic]` tests: a checked `with_*` builder must panic on invalid input, since a
1370       builder has to keep returning `Self` and cannot surface a `Result`. One per distinct
1371       check-bearing builder code path (the four base `with` arms plus each aggregate that forwards
1372       a check into a builder, which also guards against an aggregate silently dropping the check). */
1373
1374    /// Base `(with, [...])` builder panics on a failing check.
1375    #[test]
1376    #[should_panic(expected = "invalid builder argument")]
1377    fn test_with_check_panics() {
1378        struct S { v: i32 }
1379        impl S { crate::getter_setter!(with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
1380        S { v: 0 }.with_v(-1);
1381    }
1382
1383    /// Base `([&] with, [...])` builder panics on a failing check.
1384    #[test]
1385    #[should_panic(expected = "invalid builder argument")]
1386    fn test_ref_with_check_panics() {
1387        struct S { v: i32 }
1388        impl S { crate::getter_setter!([&] with, [v: i32 { non_negative, "`Err` when negative" }; "v.";]); }
1389        S { v: 0 }.with_v(-1);
1390    }
1391
1392    /// `(with, [...])` builder with `[force]` cast panics on a failing check.
1393    #[test]
1394    #[should_panic(expected = "invalid builder argument")]
1395    fn test_force_with_check_panics() {
1396        struct S { v: i32 }
1397        impl S { crate::getter_setter!(with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
1398        S { v: 0 }.with_v(-1);
1399    }
1400
1401    /// `([&] with, [...])` builder with `[force]` cast panics on a failing check.
1402    #[test]
1403    #[should_panic(expected = "invalid builder argument")]
1404    fn test_force_ref_with_check_panics() {
1405        struct S { v: i32 }
1406        impl S { crate::getter_setter!([&] with, [v: i32 [force] { non_negative, "`Err` when negative" }; "v.";]); }
1407        S { v: 0 }.with_v(-1);
1408    }
1409
1410    /// The `([&] with, get, set, [...])` aggregate forwards the check into `with_*`, which panics.
1411    #[test]
1412    #[should_panic(expected = "invalid builder argument")]
1413    #[allow(dead_code)] // the aggregate also generates `w`/`set_w`, unused in this panic test
1414    fn test_with_get_set_aggregate_check_panics() {
1415        struct S { w: i32 }
1416        impl S {
1417            crate::getter_setter!([&] with, get, set, [
1418                w: i32 { non_negative, "`Err` when negative" } => &'static str; "w.";
1419            ]);
1420        }
1421        S { w: 0 }.with_w(-1);
1422    }
1423
1424    /// The `(with, get, set, [...])` aggregate with `[force]` cast forwards the check into `with_*`, which panics.
1425    #[test]
1426    #[should_panic(expected = "invalid builder argument")]
1427    #[allow(dead_code)] // the aggregate also generates `v`/`set_v`, unused in this panic test
1428    fn test_force_with_get_set_aggregate_check_panics() {
1429        struct S { v: i32 }
1430        impl S {
1431            crate::getter_setter!(with, get, set, [
1432                v: i32 [force] { non_negative, "`Err` when negative" } => &'static str; "v.";
1433            ]);
1434        }
1435        S { v: 0 }.with_v(-1);
1436    }
1437
1438    /// The `([&] with, get, [&Type])` reference-array aggregate forwards the check into `with_*`.
1439    #[test]
1440    #[should_panic(expected = "invalid builder argument")]
1441    #[allow(dead_code)] // the aggregate also generates `arr`/`arr_mut`, unused in this panic test
1442    fn test_with_get_ref_aggregate_check_panics() {
1443        struct S { arr: [f64; 2] }
1444        impl S {
1445            crate::getter_setter!([&] with, get, [arr: &[f64; 2] { first_non_negative, "`Err` when the first element is negative" }; "array.";]);
1446        }
1447        S { arr: [0.0; 2] }.with_arr([-1.0, 0.0]);
1448    }
1449
1450    /// Tests both arms of `cast_mut_info!`: without and with the optional debug expression.
1451    #[test]
1452    fn test_cast_mut_info_both_arms() {
1453        let mut val: [u8; 4] = [7, 0, 0, 0];
1454
1455        // Without debug expression.
1456        let r: &mut [u8; 4] = crate::cast_mut_info!(&mut val);
1457        r[0] = 42;
1458        assert_eq!(val[0], 42);
1459
1460        // With debug expression.
1461        let idx: usize = 0;
1462        let r2: &mut [u8; 4] = crate::cast_mut_info!(&mut val, idx);
1463        r2[0] = 99;
1464        assert_eq!(val[0], 99);
1465    }
1466
1467    /// Tests the three combination arms of `c_str_as_str_method!` that are never
1468    /// used in production code: `(get, set)` (arm 5), `(with, set)` (arm 6),
1469    /// and `(with, get)` (arm 7).
1470    #[test]
1471    fn test_c_str_as_str_method_combination_arms() {
1472        // arm 5: (get, set) -- getter + setter, no builder.
1473        struct GetSet { name: [c_char; 16] }
1474        impl GetSet {
1475            crate::c_str_as_str_method!(get, set { name; "name."; });
1476        }
1477
1478        // arm 6: (with, set) -- setter + builder, no getter.
1479        struct WithSet { label: [c_char; 16] }
1480        impl WithSet {
1481            crate::c_str_as_str_method!(with, set { label; "label."; });
1482        }
1483
1484        // arm 7: (with, get) -- getter + builder, no setter.
1485        struct WithGet { title: [c_char; 16] }
1486        impl WithGet {
1487            crate::c_str_as_str_method!(with, get { title; "title."; });
1488        }
1489
1490        // arm 5: set then get.
1491        let mut gs = GetSet { name: [0; 16] };
1492        gs.set_name("hello");
1493        assert_eq!(gs.name(), "hello");
1494
1495        // arm 6: with_ builder; raw bytes confirm the write.
1496        let ws = WithSet { label: [0; 16] }.with_label("world");
1497        let bytes: &[u8] = bytemuck::cast_slice(&ws.label[..]);
1498        assert!(bytes.starts_with(b"world\0"), "with_label did not write label");
1499        // arm 6 setter path.
1500        let mut ws2 = WithSet { label: [0; 16] };
1501        ws2.set_label("bye");
1502        let bytes2: &[u8] = bytemuck::cast_slice(&ws2.label[..]);
1503        assert!(bytes2.starts_with(b"bye\0"), "set_label did not write label");
1504
1505        // arm 7: with_ builder then getter.
1506        let wg = WithGet { title: [0; 16] }.with_title("foo");
1507        assert_eq!(wg.title(), "foo");
1508    }
1509}