1use std::{borrow::Cow, collections::HashMap, fmt::Debug};
7
8use anyhow::{anyhow, Result};
9use bytemuck::{Pod, Zeroable};
10use strum::{Display, EnumCount, FromRepr};
11
12mod asset_path;
13mod change;
14mod data;
15pub mod expr;
16mod layer;
17mod ordering;
18mod path;
19pub mod schema;
20mod spec;
21mod value;
22
23pub use asset_path::AssetPath;
24pub use change::{ChangeEntry, ChangeFlags, ChangeList};
25pub use data::Data;
26pub use expr::Expr;
27pub use layer::{AuthoringError, Layer, LayerFormat};
28pub use ordering::{apply_ordering, element_cmp};
29pub use path::{path, Path, PathComponent, PathComponents, PathElement};
30pub use schema::{ChildrenKey, FieldKey};
31pub use spec::{
32 AttributeSpec, AttributeSpecMut, PrimSpec, PrimSpecMut, PseudoRootSpec, PseudoRootSpecMut, RelationshipSpec,
33 RelationshipSpecMut, Spec, SpecError,
34};
35pub use value::{Value, ValueConversionError};
36
37#[repr(u32)]
40#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, FromRepr, EnumCount, Display)]
41pub enum SpecType {
42 #[default]
44 Unknown = 0,
45
46 Attribute = 1,
48 Connection = 2,
49 Expression = 3,
50 Mapper = 4,
51 MapperArg = 5,
52 Prim = 6,
53 PseudoRoot = 7,
54 Relationship = 8,
55 RelationshipTarget = 9,
56 Variant = 10,
57 VariantSet = 11,
58}
59
60#[repr(i32)]
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
64pub enum Specifier {
65 Def,
66 Over,
67 Class,
68}
69
70#[repr(i32)]
77#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize))]
79#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
80pub enum Permission {
81 Public,
82 Private,
83}
84
85#[repr(i32)]
90#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize))]
92#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
93pub enum Variability {
94 #[default]
95 Varying,
96 Uniform,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
109pub struct TimeCode(pub f64);
110
111impl TimeCode {
112 #[inline]
114 pub fn value(self) -> f64 {
115 self.0
116 }
117}
118
119impl From<f64> for TimeCode {
120 fn from(v: f64) -> Self {
121 TimeCode(v)
122 }
123}
124
125impl From<TimeCode> for f64 {
126 fn from(t: TimeCode) -> Self {
127 t.0
128 }
129}
130
131impl From<TimeCode> for Value {
132 fn from(t: TimeCode) -> Self {
133 Value::TimeCode(t.0)
134 }
135}
136
137impl TryFrom<Value> for TimeCode {
138 type Error = ValueConversionError;
139
140 fn try_from(value: Value) -> Result<Self, Self::Error> {
141 match value {
142 Value::TimeCode(v) => Ok(TimeCode(v)),
143 other => ValueConversionError::err("TimeCode", &other),
144 }
145 }
146}
147
148#[repr(C)]
150#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize))]
152pub struct LayerOffset {
153 pub offset: f64,
155 pub scale: f64,
157}
158
159impl Default for LayerOffset {
160 fn default() -> Self {
161 Self {
162 offset: 0.0,
163 scale: 1.0,
164 }
165 }
166}
167
168impl LayerOffset {
169 pub const IDENTITY: LayerOffset = LayerOffset {
171 offset: 0.0,
172 scale: 1.0,
173 };
174
175 #[inline]
176 pub fn new(offset: f64, scale: f64) -> Self {
177 Self { offset, scale }
178 }
179
180 #[inline]
184 pub fn scale_only(scale: f64) -> Self {
185 Self { offset: 0.0, scale }
186 }
187
188 #[inline]
189 pub fn is_valid(&self) -> bool {
190 self.offset.is_finite() && self.scale.is_finite()
191 }
192
193 #[inline]
195 pub fn is_identity(&self) -> bool {
196 self.offset == 0.0 && self.scale == 1.0
197 }
198
199 #[inline]
203 pub fn apply(&self, time: f64) -> f64 {
204 self.offset + self.scale * time
205 }
206
207 #[inline]
213 pub fn is_valid_composition(&self) -> bool {
214 self.offset.is_finite() && self.scale.is_finite() && self.scale > 0.0
215 }
216
217 #[inline]
222 pub fn sanitized(self) -> Self {
223 if self.is_valid_composition() {
224 self
225 } else {
226 Self::IDENTITY
227 }
228 }
229
230 #[inline]
242 pub fn concatenate(&self, inner: &LayerOffset) -> LayerOffset {
243 LayerOffset {
244 offset: self.offset + self.scale * inner.offset,
245 scale: self.scale * inner.scale,
246 }
247 }
248}
249
250#[derive(Debug, Default, Clone, PartialEq)]
260#[cfg_attr(feature = "serde", derive(serde::Serialize))]
261pub struct Payload {
262 #[cfg_attr(feature = "serde", serde(rename = "asset", skip_serializing_if = "String::is_empty"))]
264 pub asset_path: String,
265 #[cfg_attr(feature = "serde", serde(rename = "path", skip_serializing_if = "Path::is_empty"))]
267 pub prim_path: Path,
268 #[cfg_attr(
270 feature = "serde",
271 serde(rename = "layerOffset", skip_serializing_if = "Option::is_none")
272 )]
273 pub layer_offset: Option<LayerOffset>,
274}
275
276#[derive(Debug, Default, Clone, PartialEq)]
283#[cfg_attr(feature = "serde", derive(serde::Serialize))]
284pub struct Reference {
285 #[cfg_attr(feature = "serde", serde(rename = "asset", skip_serializing_if = "String::is_empty"))]
287 pub asset_path: String,
288 #[cfg_attr(feature = "serde", serde(rename = "path", skip_serializing_if = "Path::is_empty"))]
290 pub prim_path: Path,
291 #[cfg_attr(feature = "serde", serde(rename = "layerOffset"))]
293 pub layer_offset: LayerOffset,
294 #[cfg_attr(
296 feature = "serde",
297 serde(rename = "customData", skip_serializing_if = "HashMap::is_empty")
298 )]
299 pub custom_data: HashMap<String, Value>,
300}
301
302mod list_op;
303
304pub use list_op::ListOp;
305
306pub type IntListOp = ListOp<i32>;
307pub type UintListOp = ListOp<u32>;
308
309pub type Int64ListOp = ListOp<i64>;
310pub type Uint64ListOp = ListOp<u64>;
311
312pub type StringListOp = ListOp<String>;
313pub type TokenListOp = ListOp<String>;
314pub type PathListOp = ListOp<Path>;
315pub type ReferenceListOp = ListOp<Reference>;
316pub type PayloadListOp = ListOp<Payload>;
317
318pub type TimeSampleMap = Vec<(f64, Value)>;
319
320pub type Relocate = (Path, Path);
325
326pub type RelocateList = Vec<Relocate>;
329
330pub trait AbstractData {
336 fn has_spec(&self, path: &Path) -> bool;
338
339 fn has_field(&self, path: &Path, field: &str) -> bool;
341
342 fn spec_type(&self, path: &Path) -> Option<SpecType>;
344
345 fn try_get(&self, path: &Path, field: &str) -> Result<Option<Cow<'_, Value>>>;
356
357 fn get(&self, path: &Path, field: &str) -> Result<Cow<'_, Value>> {
361 self.try_get(path, field)?
362 .ok_or_else(|| anyhow!("No field '{field}' at path '{path}'"))
363 }
364
365 fn list(&self, path: &Path) -> Option<Vec<String>>;
367
368 fn paths(&self) -> Vec<Path>;
373
374 fn as_data(&self) -> Option<&Data> {
379 None
380 }
381
382 fn as_data_mut(&mut self) -> Option<&mut Data> {
390 None
391 }
392}
393
394pub type LayerData = Box<dyn AbstractData>;
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn layer_offset_identity_is_identity() {
403 assert!(LayerOffset::IDENTITY.is_identity());
404 assert!(LayerOffset::default().is_identity());
405 assert!(!LayerOffset::new(0.0, 2.0).is_identity());
406 assert!(!LayerOffset::new(1.0, 1.0).is_identity());
407 }
408
409 #[test]
410 fn layer_offset_valid_composition_rejects_non_positive_scale() {
411 assert!(LayerOffset::new(10.0, 1.0).is_valid_composition());
412 assert!(!LayerOffset::new(10.0, 0.0).is_valid_composition());
413 assert!(!LayerOffset::new(10.0, -1.0).is_valid_composition());
414 assert!(!LayerOffset::new(f64::INFINITY, 1.0).is_valid_composition());
415 assert!(!LayerOffset::new(0.0, f64::NAN).is_valid_composition());
416 }
417
418 #[test]
419 fn layer_offset_sanitized_drops_invalid_to_identity() {
420 assert_eq!(LayerOffset::new(10.0, 2.0).sanitized(), LayerOffset::new(10.0, 2.0));
421 assert_eq!(LayerOffset::new(5.0, -1.0).sanitized(), LayerOffset::IDENTITY);
422 assert_eq!(LayerOffset::new(5.0, 0.0).sanitized(), LayerOffset::IDENTITY);
423 }
424
425 #[test]
426 fn layer_offset_concatenate_matches_spec_formula() {
427 let outer = LayerOffset::new(10.0, 2.0);
428 let inner = LayerOffset::new(20.0, 1.0);
429 assert_eq!(outer.concatenate(&inner), LayerOffset::new(50.0, 2.0));
431 }
432
433 #[test]
434 fn layer_offset_concatenate_is_associative() {
435 let a = LayerOffset::new(10.0, 2.0);
436 let b = LayerOffset::new(20.0, 0.5);
437 let c = LayerOffset::new(5.0, 3.0);
438 let ab_c = a.concatenate(&b).concatenate(&c);
439 let a_bc = a.concatenate(&b.concatenate(&c));
440 assert!((ab_c.offset - a_bc.offset).abs() < 1e-12);
441 assert!((ab_c.scale - a_bc.scale).abs() < 1e-12);
442 }
443
444 #[test]
445 fn layer_offset_identity_is_neutral() {
446 let a = LayerOffset::new(10.0, 2.0);
447 assert_eq!(a.concatenate(&LayerOffset::IDENTITY), a);
448 assert_eq!(LayerOffset::IDENTITY.concatenate(&a), a);
449 }
450}