mujoco_rs/error.rs
1//! Error types for MuJoCo-rs operations.
2//!
3//! - [`MjDataError`] - physics data, view-signature, and Jacobian operations.
4//! - [`MjSceneError`] - 3-D scene and visualization operations (`MjvScene`).
5//! - [`MjrContextError`] - GPU rendering-context operations (`MjrContext`).
6//! - [`MjEditError`] - model-specification editing operations (`MjSpec`).
7//! - [`MjModelError`] - model loading, saving, and state operations (`MjModel`).
8//! - [`MjVfsError`] - virtual file system operations (`MjVfs`).
9//! - [`MjPluginError`] - plugin library loading operations.
10//! - [`GlInitError`] - OpenGL / window initialization (feature-gated).
11use std::fmt;
12
13/// Errors that can occur in [`MjData`](crate::wrappers::MjData) physics data
14/// and Jacobian operations.
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum MjDataError {
18 /// A provided object or body index was out of the valid range.
19 IndexOutOfBounds {
20 /// Name of the index parameter, e.g. `"body_id"` or `"geom_id"`.
21 kind: &'static str,
22 /// The index value that was passed.
23 id: usize,
24 /// Exclusive upper bound of the valid range.
25 upper: usize,
26 },
27 /// The provided MuJoCo object type is not supported by this operation.
28 ///
29 /// Contains the raw MuJoCo C object-type code (`mjOBJ_*`) that was not recognized.
30 UnsupportedObjectType(i32),
31 /// MuJoCo failed to allocate the requested structure.
32 AllocationFailed,
33 /// A buffer passed to the operation is too small for the required data.
34 BufferTooSmall {
35 /// Descriptive name of the buffer (e.g. `"destination"`, `"rgb"`).
36 name: &'static str,
37 /// Actual length of the buffer that was provided.
38 got: usize,
39 /// Minimum length the buffer must have.
40 needed: usize,
41 },
42 /// A slice or array parameter has the wrong length for the operation.
43 LengthMismatch {
44 /// Descriptive name of the parameter.
45 name: &'static str,
46 /// Expected length.
47 expected: usize,
48 /// Actual length that was provided.
49 got: usize,
50 },
51 /// Two model-signature-bound objects were created from different models.
52 ///
53 /// This is returned by APIs that require matching model signatures,
54 /// including data-copy operations and info-view accessors.
55 SignatureMismatch {
56 /// Model signature of the source object.
57 source: u64,
58 /// Model signature of the destination object.
59 destination: u64,
60 },
61 /// The specified actuator or sensor has no associated history buffer.
62 NoHistoryBuffer {
63 /// `"actuator"` or `"sensor"`.
64 kind: &'static str,
65 /// The zero-based index of the actuator or sensor.
66 id: usize,
67 },
68 /// The contact buffer is full; no more contacts can be added.
69 ContactBufferFull,
70 /// A filesystem path argument contains invalid UTF-8.
71 InvalidUtf8Path,
72}
73
74impl fmt::Display for MjDataError {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::IndexOutOfBounds { kind, id, upper } => {
78 write!(f, "{kind} {id} is out of bounds [0, {upper})")
79 }
80 Self::UnsupportedObjectType(raw) => {
81 write!(f, "object type {raw} is not supported by this operation")
82 }
83 Self::AllocationFailed => {
84 write!(f, "MuJoCo allocation failed")
85 }
86 Self::BufferTooSmall { name, got, needed } => {
87 write!(
88 f,
89 "{name} buffer is too small: got {got} elements, \
90 but need at least {needed}"
91 )
92 }
93 Self::SignatureMismatch { source, destination } => {
94 write!(
95 f,
96 "model signature mismatch: source {source:#X}, \
97 destination {destination:#X}"
98 )
99 }
100 Self::LengthMismatch { name, expected, got } => {
101 write!(
102 f,
103 "{name} has wrong length: expected {expected}, got {got}"
104 )
105 }
106 Self::NoHistoryBuffer { kind, id } => {
107 write!(f, "{kind} {id} has no history buffer")
108 }
109 Self::ContactBufferFull => {
110 write!(f, "contact buffer is full")
111 }
112 Self::InvalidUtf8Path => {
113 write!(f, "path contains invalid UTF-8")
114 }
115 }
116 }
117}
118
119impl std::error::Error for MjDataError {}
120
121
122/// Errors that can occur in 3-D scene and visualization operations
123/// ([`MjvScene`](crate::wrappers::MjvScene)).
124#[derive(Debug, Clone, PartialEq, Eq)]
125#[non_exhaustive]
126pub enum MjSceneError {
127 /// No more space is available for new geoms in the scene.
128 ///
129 /// Increase the `max_geom` capacity passed to
130 /// [`MjvScene::new`](crate::wrappers::MjvScene::new).
131 SceneFull {
132 /// The current maximum geom capacity of the scene.
133 capacity: i32,
134 },
135 /// A string label exceeds the fixed-size label buffer of an [`MjvGeom`](crate::wrappers::MjvGeom).
136 LabelTooLong {
137 /// Length of the label in bytes.
138 len: usize,
139 /// Maximum number of bytes (excluding the NUL terminator) the buffer can hold.
140 capacity: usize,
141 },
142 /// The figure's line-data buffer for a given plot is full.
143 FigureBufferFull {
144 /// Index of the plot whose buffer is full.
145 plot_index: usize,
146 /// Maximum number of data points the buffer can hold.
147 capacity: usize,
148 },
149 /// A point index is out of range for the current data in a figure plot.
150 FigureIndexOutOfBounds {
151 /// Index of the plot.
152 plot_index: usize,
153 /// The point index that was provided.
154 point_index: usize,
155 /// Current number of data points in the plot.
156 current_len: usize,
157 },
158 /// A plot index is out of the valid range `[0, mjMAXLINE)`.
159 InvalidPlotIndex {
160 /// The out-of-range plot index that was provided.
161 plot_index: usize,
162 /// Maximum number of plots (`mjMAXLINE`).
163 max_plots: usize,
164 },
165 /// A geom label string contains non-ASCII bytes.
166 ///
167 /// MuJoCo's renderer treats the label buffer as ASCII; multi-byte UTF-8
168 /// sequences would be rendered as garbage characters.
169 NonAsciiLabel,
170 /// An integer value does not correspond to any known camera type variant.
171 InvalidCameraType(i32),
172}
173
174impl fmt::Display for MjSceneError {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match self {
177 Self::SceneFull { capacity } => {
178 write!(
179 f,
180 "scene geom buffer is full (capacity = {capacity}); \
181 increase max_geom when constructing MjvScene"
182 )
183 }
184 Self::LabelTooLong { len, capacity } => {
185 write!(
186 f,
187 "label of {len} bytes exceeds the fixed buffer capacity of {capacity} bytes"
188 )
189 }
190 Self::FigureBufferFull { plot_index, capacity } => {
191 write!(
192 f,
193 "figure plot {plot_index} buffer is full \
194 (capacity = {capacity} data points)"
195 )
196 }
197 Self::FigureIndexOutOfBounds { plot_index, point_index, current_len } => {
198 write!(
199 f,
200 "point index {point_index} is out of bounds for plot {plot_index} \
201 (current length = {current_len})"
202 )
203 }
204 Self::InvalidPlotIndex { plot_index, max_plots } => {
205 write!(
206 f,
207 "plot index {plot_index} is out of range [0, {max_plots})"
208 )
209 }
210 Self::NonAsciiLabel => {
211 write!(f, "label contains non-ASCII characters")
212 }
213 Self::InvalidCameraType(raw) => {
214 write!(f, "unknown camera type {raw}")
215 }
216 }
217 }
218}
219
220impl std::error::Error for MjSceneError {}
221
222
223/// Errors that can occur in GPU rendering-context ([`MjrContext`](crate::wrappers::MjrContext)) operations.
224#[derive(Debug, Clone, PartialEq, Eq)]
225#[non_exhaustive]
226pub enum MjrContextError {
227 /// An index passed to a rendering-context operation is out of the valid range.
228 IndexOutOfBounds {
229 /// The index that was supplied.
230 id: usize,
231 /// Exclusive upper bound of the valid range.
232 len: usize,
233 },
234 /// A viewport has invalid (negative) dimensions.
235 InvalidViewport {
236 /// The viewport width that was provided.
237 width: i32,
238 /// The viewport height that was provided.
239 height: i32,
240 },
241 /// A pixel buffer passed to a rendering-context operation is too small.
242 BufferTooSmall {
243 /// Descriptive name of the buffer (e.g. `"rgb"`, `"depth"`).
244 name: &'static str,
245 /// Actual length of the buffer that was provided.
246 got: usize,
247 /// Minimum length the buffer must have.
248 needed: usize,
249 },
250}
251
252impl fmt::Display for MjrContextError {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 match self {
255 Self::IndexOutOfBounds { id, len } => {
256 write!(f, "index {id} is out of range [0, {len})")
257 }
258 Self::InvalidViewport { width, height } => {
259 write!(
260 f,
261 "viewport dimensions must be non-negative, got {width}x{height}"
262 )
263 }
264 Self::BufferTooSmall { name, got, needed } => {
265 write!(
266 f,
267 "{name} buffer is too small: got {got} elements, \
268 but need at least {needed}"
269 )
270 }
271 }
272 }
273}
274
275impl std::error::Error for MjrContextError {}
276
277
278/// Errors that can occur in model-specification editing operations
279/// ([`MjSpec`](crate::wrappers::mj_editing::MjSpec) and related types).
280#[derive(Debug, Clone, PartialEq, Eq)]
281#[non_exhaustive]
282pub enum MjEditError {
283 /// MuJoCo failed to allocate the requested model element.
284 AllocationFailed,
285 /// A filesystem path argument contains invalid UTF-8.
286 InvalidUtf8Path,
287 /// MuJoCo failed to parse the XML (or other format) input.
288 ParseFailed(String),
289 /// MuJoCo failed to compile the spec into a model.
290 CompileFailed(String),
291 /// MuJoCo failed to save the spec to XML.
292 SaveFailed(String),
293 /// A referenced element (e.g. parent default class) was not found.
294 NotFound,
295 /// An element with the same name already exists.
296 AlreadyExists,
297 /// This operation is not supported for the current element.
298 UnsupportedOperation,
299 /// MuJoCo returned an error while attempting to delete the element.
300 DeleteFailed(String),
301 /// The output buffer passed to [`MjSpec::save_xml_string`](crate::wrappers::mj_editing::MjSpec::save_xml_string)
302 /// was too small to hold the XML.
303 ///
304 /// `required_size` follows `snprintf`-style semantics: it is the number of bytes MuJoCo would
305 /// write, **not** counting the NUL terminator. To retry successfully, pass a buffer of at
306 /// least `required_size + 1` bytes.
307 XmlBufferTooSmall {
308 /// Number of bytes MuJoCo would write (excluding the NUL terminator).
309 /// Pass a buffer of at least `required_size + 1` bytes to retry.
310 required_size: usize,
311 },
312 /// Returned when the parameters to the given method/function are invalid.
313 InvalidParameter(String),
314 /// The given index is out of range.
315 IndexOutOfBounds {
316 /// The index that was supplied.
317 id: usize,
318 /// Exclusive upper bound of the valid range.
319 len: usize,
320 },
321}
322
323impl fmt::Display for MjEditError {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 match self {
326 Self::AllocationFailed => write!(f, "MuJoCo failed to allocate the model element"),
327 Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
328 Self::ParseFailed(msg) => write!(f, "parse failed: {msg}"),
329 Self::CompileFailed(msg) => write!(f, "compilation failed: {msg}"),
330 Self::SaveFailed(msg) => write!(f, "save failed: {msg}"),
331 Self::NotFound => write!(f, "referenced element not found"),
332 Self::AlreadyExists => write!(f, "element with the same name already exists"),
333 Self::UnsupportedOperation => write!(f, "this operation is not supported"),
334 Self::DeleteFailed(msg) => write!(f, "delete failed: {msg}"),
335 Self::XmlBufferTooSmall { required_size } => write!(
336 f,
337 "XML output buffer too small; retry with at least {} bytes",
338 required_size + 1
339 ),
340 Self::InvalidParameter(msg) => write!(f, "{msg}"),
341 Self::IndexOutOfBounds { id, len } => write!(
342 f,
343 "index {id} is out of bounds (length is {len})"
344 ),
345 }
346 }
347}
348
349impl std::error::Error for MjEditError {}
350
351
352/// Errors that can occur in [`MjModel`](crate::wrappers::MjModel) operations.
353#[derive(Debug, Clone, PartialEq, Eq)]
354#[non_exhaustive]
355pub enum MjModelError {
356 /// A filesystem path argument contains invalid UTF-8.
357 InvalidUtf8Path,
358 /// MuJoCo failed to load the model from a file, string, or buffer.
359 LoadFailed(String),
360 /// MuJoCo failed to save the model XML.
361 SaveFailed(String),
362 /// MuJoCo failed to allocate the requested structure.
363 AllocationFailed,
364 /// The state source slice has the wrong length for the given spec.
365 StateSliceLengthMismatch {
366 /// Expected length.
367 expected: usize,
368 /// Actual length.
369 got: usize,
370 },
371 /// The destination spec is not a subset of the source spec.
372 SpecNotSubset,
373 /// A destination buffer is too small for the operation.
374 BufferTooSmall {
375 /// Minimum number of elements required.
376 needed: usize,
377 /// Actual number of elements available.
378 available: usize,
379 },
380 /// Two model-signature-bound objects were created from different models.
381 SignatureMismatch {
382 /// Model signature of the source object.
383 source: u64,
384 /// Model signature of the destination object.
385 destination: u64,
386 },
387 /// A virtual-file-system operation failed while loading a model from a string.
388 VfsError(MjVfsError),
389 /// The given index is out of range.
390 IndexOutOfBounds {
391 /// The index that was supplied.
392 id: usize,
393 /// Exclusive upper bound of the valid range.
394 len: usize,
395 },
396}
397
398impl fmt::Display for MjModelError {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 match self {
401 Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
402 Self::LoadFailed(msg) => write!(f, "model load failed: {msg}"),
403 Self::SaveFailed(msg) => write!(f, "model save failed: {msg}"),
404 Self::AllocationFailed => write!(f, "MuJoCo failed to allocate the requested structure"),
405 Self::StateSliceLengthMismatch { expected, got } => {
406 write!(f, "state slice length mismatch: expected {expected}, got {got}")
407 }
408 Self::SpecNotSubset => {
409 write!(f, "dst_spec must be a subset of src_spec")
410 }
411 Self::BufferTooSmall { needed, available } => {
412 write!(
413 f,
414 "buffer is too small: got {available} elements, \
415 but need at least {needed}"
416 )
417 }
418 Self::SignatureMismatch { source, destination } => {
419 write!(
420 f,
421 "model signature mismatch: source {source:#X}, \
422 destination {destination:#X}"
423 )
424 }
425 Self::VfsError(e) => write!(f, "VFS error: {e}"),
426 Self::IndexOutOfBounds { id, len } => write!(
427 f,
428 "index {id} is out of bounds (length is {len})"
429 ),
430 }
431 }
432}
433
434impl std::error::Error for MjModelError {
435 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
436 match self {
437 Self::VfsError(e) => Some(e),
438 _ => None,
439 }
440 }
441}
442
443impl From<MjVfsError> for MjModelError {
444 fn from(e: MjVfsError) -> Self {
445 Self::VfsError(e)
446 }
447}
448
449
450/// Errors that can occur in virtual file system ([`MjVfs`](crate::wrappers::MjVfs)) operations.
451#[derive(Debug, Clone, PartialEq, Eq)]
452#[non_exhaustive]
453pub enum MjVfsError {
454 /// A file or mount with the same name already exists.
455 AlreadyExists,
456 /// MuJoCo failed to load the file or register the buffer.
457 LoadFailed,
458 /// The specified file or directory was not found in the VFS.
459 NotFound,
460 /// The provided path contains invalid UTF-8.
461 InvalidUtf8Path,
462 /// The buffer length exceeds `i32::MAX` bytes.
463 BufferTooLarge,
464 /// An unrecognized MuJoCo return code.
465 Unknown(i32),
466}
467
468impl fmt::Display for MjVfsError {
469 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470 match self {
471 Self::AlreadyExists => write!(f, "file already exists in VFS"),
472 Self::LoadFailed => write!(f, "failed to load file into VFS"),
473 Self::NotFound => write!(f, "file not found in VFS"),
474 Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
475 Self::BufferTooLarge => write!(f, "buffer length exceeds i32::MAX bytes"),
476 Self::Unknown(code) => write!(f, "unknown VFS error (code {code})"),
477 }
478 }
479}
480
481impl std::error::Error for MjVfsError {}
482
483
484/// Errors that can occur when loading MuJoCo plugin libraries.
485#[derive(Debug, Clone, PartialEq, Eq)]
486#[non_exhaustive]
487pub enum MjPluginError {
488 /// The provided path contains invalid UTF-8.
489 InvalidUtf8Path,
490 /// The path string contains an interior null byte.
491 NullBytePath,
492}
493
494impl fmt::Display for MjPluginError {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 match self {
497 Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
498 Self::NullBytePath => write!(f, "path contains an interior null byte"),
499 }
500 }
501}
502
503impl std::error::Error for MjPluginError {}
504
505/// Errors that can occur during OpenGL / window initialization.
506///
507/// Each variant represents a distinct step in the initialization pipeline.
508#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
509#[derive(Debug, Clone)]
510#[non_exhaustive]
511pub enum GlInitError {
512 /// The windowing / display builder failed to initialize.
513 DisplayBuild(String),
514 /// The display builder succeeded but did not produce a window.
515 NoWindow,
516 /// Failed to obtain the native window handle.
517 WindowHandle(String),
518 /// OpenGL context creation failed. Wraps [`glutin::error::Error`].
519 ContextCreation(glutin::error::Error),
520 /// Window surface attributes could not be constructed.
521 SurfaceAttributes(String),
522 /// Rendering surface creation failed. Wraps [`glutin::error::Error`].
523 SurfaceCreation(glutin::error::Error),
524 /// Making the GL context current on the surface failed. Wraps [`glutin::error::Error`].
525 MakeCurrent(glutin::error::Error),
526}
527
528#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
529impl fmt::Display for GlInitError {
530 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
531 match self {
532 Self::DisplayBuild(e) => write!(f, "display build failed: {e}"),
533 Self::NoWindow => write!(f, "display builder did not create a window"),
534 Self::WindowHandle(e) => write!(f, "failed to obtain window handle: {e}"),
535 Self::ContextCreation(e) => write!(f, "GL context creation failed: {e}"),
536 Self::SurfaceAttributes(e) => write!(f, "failed to build surface attributes: {e}"),
537 Self::SurfaceCreation(e) => write!(f, "window surface creation failed: {e}"),
538 Self::MakeCurrent(e) => write!(f, "failed to make GL context current: {e}"),
539 }
540 }
541}
542
543#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
544impl std::error::Error for GlInitError {
545 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
546 match self {
547 Self::ContextCreation(e) |
548 Self::SurfaceCreation(e) |
549 Self::MakeCurrent(e) => Some(e),
550 _ => None,
551 }
552 }
553}