viewport_lib/error.rs
1//! Error types for the viewport library.
2
3/// Errors that can occur during mesh upload and manipulation.
4#[derive(Debug, Clone, thiserror::Error)]
5#[non_exhaustive]
6pub enum ViewportError {
7 /// Mesh data has empty positions or indices.
8 #[error("empty mesh: {positions} positions, {indices} indices")]
9 EmptyMesh {
10 /// Number of positions provided.
11 positions: usize,
12 /// Number of indices provided.
13 indices: usize,
14 },
15
16 /// Positions and normals arrays have different lengths.
17 #[error("mesh length mismatch: {positions} positions vs {normals} normals")]
18 MeshLengthMismatch {
19 /// Number of positions provided.
20 positions: usize,
21 /// Number of normals provided.
22 normals: usize,
23 },
24
25 /// Mesh index is out of bounds for the mesh storage.
26 #[error("mesh index {index} out of bounds (mesh count: {count})")]
27 MeshIndexOutOfBounds {
28 /// The requested mesh index.
29 index: usize,
30 /// The number of meshes currently stored.
31 count: usize,
32 },
33
34 /// An index buffer entry references a vertex that does not exist.
35 #[error("invalid vertex index {vertex_index} (vertex count: {vertex_count})")]
36 InvalidVertexIndex {
37 /// The offending index value.
38 vertex_index: u32,
39 /// Total number of vertices.
40 vertex_count: usize,
41 },
42
43 /// Volume scalar data length does not match the declared grid dimensions.
44 #[error("volume data length {actual} does not match dims {dims:?} (expected {expected})")]
45 VolumeDataLengthMismatch {
46 /// Number of scalar values provided.
47 actual: usize,
48 /// Expected length, equal to `dims[0] * dims[1] * dims[2]`.
49 expected: usize,
50 /// Grid dimensions `[nx, ny, nz]`.
51 dims: [u32; 3],
52 },
53
54 /// Texture RGBA data has an unexpected size.
55 #[error("invalid texture data: expected {expected} bytes, got {actual}")]
56 InvalidTextureData {
57 /// Expected byte count (width * height * 4).
58 expected: usize,
59 /// Actual byte count provided.
60 actual: usize,
61 },
62
63 /// Attempted to access or replace a mesh slot that is empty (previously removed).
64 #[error("mesh slot {index} is empty")]
65 MeshSlotEmpty {
66 /// The slot index that was accessed.
67 index: usize,
68 },
69
70 /// Named scalar attribute not found on the given mesh.
71 #[error("attribute '{name}' not found on mesh {mesh_id}")]
72 AttributeNotFound {
73 /// The mesh index that was accessed.
74 mesh_id: usize,
75 /// The attribute name that was requested.
76 name: String,
77 },
78
79 /// Attribute data length does not match the existing buffer.
80 ///
81 /// `replace_attribute` requires the same vertex count as the original upload.
82 #[error("attribute length mismatch: expected {expected} f32 elements, got {got}")]
83 AttributeLengthMismatch {
84 /// Expected number of f32 elements (original attribute length).
85 expected: usize,
86 /// Actual number of f32 elements provided.
87 got: usize,
88 },
89
90 /// A buffer required for GPU marching cubes upload exceeds the device's
91 /// `max_buffer_size` limit. The caller should fall back to CPU marching cubes.
92 #[error("GPU MC buffer '{buffer}' needs {needed} bytes but device limit is {limit}")]
93 McBufferTooLarge {
94 /// Short name identifying which buffer exceeded the limit (e.g. `"vertex_buf"`).
95 buffer: &'static str,
96 /// Bytes required for the allocation.
97 needed: u64,
98 /// The device's `max_buffer_size` limit.
99 limit: u64,
100 },
101
102 /// Gaussian splat data is malformed or empty.
103 #[error("invalid gaussian splat data: {reason}")]
104 InvalidGaussianSplatData {
105 /// Short description of what is wrong.
106 reason: &'static str,
107 },
108
109 /// A background upload worker panicked or dropped its channel before
110 /// sending a result.
111 #[error("upload worker did not complete: {reason}")]
112 JobWorkerLost {
113 /// Short description of how the worker was lost.
114 reason: &'static str,
115 },
116
117 /// `upload_result_*` was called before the corresponding job reached the
118 /// `Ready` state. Poll `upload_status` and retry on the next frame.
119 #[error("upload job is not yet ready to take its result")]
120 JobNotReady,
121
122 /// `upload_result_*` was called with a `JobId` that does not belong to
123 /// the matching upload type, has already been taken, or was never
124 /// issued.
125 #[error("upload job has no result available: {reason}")]
126 JobResultMissing {
127 /// Short description of why the result is unavailable.
128 reason: &'static str,
129 },
130
131 /// No deformer slots remain. The registry has a fixed budget set by the
132 /// vertex-stage storage-buffer limit; once all slots are taken, callers
133 /// must release one with `unregister_deformer` (when implemented) before
134 /// registering another.
135 #[error("deformer registry exhausted: {max} slots in use")]
136 DeformSlotsExhausted {
137 /// Total number of slots the registry exposes.
138 max: usize,
139 },
140
141 /// A deformer with this name is already registered.
142 #[error("deformer name '{name}' already registered")]
143 DeformNameTaken {
144 /// The duplicate name that was rejected.
145 name: String,
146 },
147
148 /// The composed mesh-family shader failed validation, either because the
149 /// supplied `wgsl_body` is malformed or because composition produced an
150 /// invalid module. The reason carries the underlying validator message
151 /// and identifies which shader failed.
152 #[error("deformer shader invalid: {reason}")]
153 DeformShaderInvalid {
154 /// Validator message prefixed by the shader name that failed.
155 reason: String,
156 },
157
158 /// A LOD group was registered with no levels.
159 #[error("LOD group has no levels")]
160 LodGroupEmpty,
161
162 /// The mesh list and threshold list passed to `register_lod_group` differ
163 /// in length. Each level needs exactly one threshold.
164 #[error("LOD level count mismatch: {meshes} meshes vs {thresholds} thresholds")]
165 LodLevelCountMismatch {
166 /// Number of meshes provided.
167 meshes: usize,
168 /// Number of thresholds provided.
169 thresholds: usize,
170 },
171
172 /// LOD thresholds must strictly decrease from the full level to the crudest,
173 /// since each level applies to a smaller on-screen size than the one before.
174 #[error("LOD thresholds must strictly decrease (level {level} is not smaller than the previous)")]
175 LodThresholdsNotDescending {
176 /// The level whose threshold is not smaller than its predecessor's.
177 level: usize,
178 },
179
180 /// A level's mesh is not compatible with the other levels in the group: a
181 /// switch to it would change rendering. Today this means its named
182 /// attribute set or its deformer attachment differs from level 0.
183 #[error("LOD level {level} is incompatible with level 0: {reason}")]
184 LodLevelIncompatible {
185 /// The level that does not match level 0.
186 level: usize,
187 /// What differs (a missing attribute name, or a deform mismatch).
188 reason: String,
189 },
190
191 /// A LOD group id does not refer to a registered group.
192 #[error("LOD group {index} is not registered")]
193 LodGroupNotFound {
194 /// The group index that was looked up.
195 index: usize,
196 },
197}
198
199/// Convenience alias for `Result<T, ViewportError>`.
200pub type ViewportResult<T> = Result<T, ViewportError>;