Attribute views¶
The MuJoCo library stores data about joints, bodies, and other elements in contiguous arrays. These can be challenging to work with, particularly when the array’s length varies between elements. For example, different types of joints may have a different number of degrees of freedom. MuJoCo’s Python bindings solve this issue by providing views to specific ranges in the corresponding arrays.
Like MuJoCo’s Python bindings, MuJoCo-rs also provides views. Specifically, we provide views for attributes of MjData and MjModel.
Views borrow data and cannot be preserved across simulation steps, as that would violate Rust’s borrow checker rules. Re-looking up names each step would also be expensive. To overcome this, “info” structs exist, which cache the resolved index ranges and the model signature for fast view creation after each step.
Warning
Cached info structs are tied to the model signature they were created from.
Calling view() / view_mut() with data from an incompatible model
will panic with a model-signature mismatch.
Use try_view() / try_view_mut() if you want to handle mismatches
as Result values instead of panicking.
Reading¶
For example, let’s say we want to read the position of a free joint. We will first create an info struct by calling MjData::joint like so:
use mujoco_rs::prelude::*;
fn main() {
let model = MjModel::from_xml("model.xml").expect("could not load the model");
let mut data = MjData::new(&model);
let joint_info = data.joint("football-ball").expect("name not found");
loop {
data.step();
// ...
}
}
To actually view the data, we will now call MjJointDataInfo::view and pass it a reference to MjData, like so:
use mujoco_rs::prelude::*;
fn main() {
let model = MjModel::from_xml("model.xml").expect("could not load the model");
let mut data = MjData::new(&model);
let joint_info = data.joint("football-ball").expect("name not found");
loop {
data.step();
println!("{:?}", &joint_info.view(&data).qpos[..3]); // print x, y and z coordinates.
}
}
If a signature mismatch is possible in your workflow, use
MjJointDataInfo::try_view
and handle the returned Result instead of panicking.
View attributes like MjJointDataView::qpos
use mujoco_rs::util::PointerView (or Option<PointerView> for optional
fields). PointerView implements the
Deref trait and on deref
acts like a slice. While some fields might be scalars, we still treat those as arrays
for implementation simplicity reasons.
The same view / view_mut pattern applies to every named element type — bodies,
geoms, sites, actuators, sensors, and so on — each looked up through its corresponding
finder (e.g. MjData::body or MjData::geom) and info struct.
Writing¶
The above example shows a read-only view. For mutability, MjJointDataInfo::view_mut must be called and passed a mutable reference to MjData, like so:
use mujoco_rs::prelude::*;
fn main() {
let model = MjModel::from_xml("model.xml").expect("could not load the model");
let mut data = MjData::new(&model);
let joint_info = data.joint("football-ball").expect("name not found");
loop {
data.step();
joint_info.view_mut(&mut data).qpos[0] = 0.5;
}
}
For fallible mutable access, use
MjJointDataInfo::try_view_mut
to get a Result instead of panicking on signature mismatch.
In mutable views, regular writable fields use
mujoco_rs::util::PointerViewMut. Fields marked as read-only in the
generated view still support mutation, but only through
mujoco_rs::util::PointerViewUnsafeMut and explicit unsafe:
use mujoco_rs::prelude::*;
fn main() {
let mut model = MjModel::from_xml("model.xml").expect("could not load the model");
let actuator_info = model.actuator("slider").unwrap();
let mut view = actuator_info.view_mut(&mut model);
unsafe {
view.dyntype.as_mut_slice()[0] = MjtDyn::mjDYN_NONE;
view.actadr.as_mut_slice()[0] = -1;
}
}