use std::{borrow::Cow, collections::HashMap, fmt::Debug};
use anyhow::{anyhow, Result};
use bytemuck::{Pod, Zeroable};
use strum::{Display, EnumCount, FromRepr};
mod asset_path;
mod change;
mod data;
pub mod expr;
mod layer;
mod ordering;
mod path;
pub mod schema;
mod spec;
mod value;
pub use asset_path::AssetPath;
pub use change::{ChangeEntry, ChangeFlags, ChangeList};
pub use data::Data;
pub use expr::Expr;
pub use layer::{AuthoringError, Layer, LayerFormat};
pub use ordering::{apply_ordering, element_cmp};
pub use path::{path, Path, PathComponent, PathComponents, PathElement};
pub use schema::{ChildrenKey, FieldKey};
pub use spec::{
AttributeSpec, AttributeSpecMut, PrimSpec, PrimSpecMut, PseudoRootSpec, PseudoRootSpecMut, RelationshipSpec,
RelationshipSpecMut, Spec, SpecError,
};
pub use value::{Value, ValueConversionError};
#[repr(u32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, FromRepr, EnumCount, Display)]
pub enum SpecType {
#[default]
Unknown = 0,
Attribute = 1,
Connection = 2,
Expression = 3,
Mapper = 4,
MapperArg = 5,
Prim = 6,
PseudoRoot = 7,
Relationship = 8,
RelationshipTarget = 9,
Variant = 10,
VariantSet = 11,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Specifier {
Def,
Over,
Class,
}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Permission {
Public,
Private,
}
#[repr(i32)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromRepr)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Variability {
#[default]
Varying,
Uniform,
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct TimeCode(pub f64);
impl TimeCode {
#[inline]
pub fn value(self) -> f64 {
self.0
}
}
impl From<f64> for TimeCode {
fn from(v: f64) -> Self {
TimeCode(v)
}
}
impl From<TimeCode> for f64 {
fn from(t: TimeCode) -> Self {
t.0
}
}
impl From<TimeCode> for Value {
fn from(t: TimeCode) -> Self {
Value::TimeCode(t.0)
}
}
impl TryFrom<Value> for TimeCode {
type Error = ValueConversionError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::TimeCode(v) => Ok(TimeCode(v)),
other => ValueConversionError::err("TimeCode", &other),
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LayerOffset {
pub offset: f64,
pub scale: f64,
}
impl Default for LayerOffset {
fn default() -> Self {
Self {
offset: 0.0,
scale: 1.0,
}
}
}
impl LayerOffset {
pub const IDENTITY: LayerOffset = LayerOffset {
offset: 0.0,
scale: 1.0,
};
#[inline]
pub fn new(offset: f64, scale: f64) -> Self {
Self { offset, scale }
}
#[inline]
pub fn scale_only(scale: f64) -> Self {
Self { offset: 0.0, scale }
}
#[inline]
pub fn is_valid(&self) -> bool {
self.offset.is_finite() && self.scale.is_finite()
}
#[inline]
pub fn is_identity(&self) -> bool {
self.offset == 0.0 && self.scale == 1.0
}
#[inline]
pub fn apply(&self, time: f64) -> f64 {
self.offset + self.scale * time
}
#[inline]
pub fn is_valid_composition(&self) -> bool {
self.offset.is_finite() && self.scale.is_finite() && self.scale > 0.0
}
#[inline]
pub fn sanitized(self) -> Self {
if self.is_valid_composition() {
self
} else {
Self::IDENTITY
}
}
#[inline]
pub fn concatenate(&self, inner: &LayerOffset) -> LayerOffset {
LayerOffset {
offset: self.offset + self.scale * inner.offset,
scale: self.scale * inner.scale,
}
}
}
#[derive(Debug, Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Payload {
#[cfg_attr(feature = "serde", serde(rename = "asset", skip_serializing_if = "String::is_empty"))]
pub asset_path: String,
#[cfg_attr(feature = "serde", serde(rename = "path", skip_serializing_if = "Path::is_empty"))]
pub prim_path: Path,
#[cfg_attr(
feature = "serde",
serde(rename = "layerOffset", skip_serializing_if = "Option::is_none")
)]
pub layer_offset: Option<LayerOffset>,
}
#[derive(Debug, Default, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Reference {
#[cfg_attr(feature = "serde", serde(rename = "asset", skip_serializing_if = "String::is_empty"))]
pub asset_path: String,
#[cfg_attr(feature = "serde", serde(rename = "path", skip_serializing_if = "Path::is_empty"))]
pub prim_path: Path,
#[cfg_attr(feature = "serde", serde(rename = "layerOffset"))]
pub layer_offset: LayerOffset,
#[cfg_attr(
feature = "serde",
serde(rename = "customData", skip_serializing_if = "HashMap::is_empty")
)]
pub custom_data: HashMap<String, Value>,
}
mod list_op;
pub use list_op::ListOp;
pub type IntListOp = ListOp<i32>;
pub type UintListOp = ListOp<u32>;
pub type Int64ListOp = ListOp<i64>;
pub type Uint64ListOp = ListOp<u64>;
pub type StringListOp = ListOp<String>;
pub type TokenListOp = ListOp<String>;
pub type PathListOp = ListOp<Path>;
pub type ReferenceListOp = ListOp<Reference>;
pub type PayloadListOp = ListOp<Payload>;
pub type TimeSampleMap = Vec<(f64, Value)>;
pub type Relocate = (Path, Path);
pub type RelocateList = Vec<Relocate>;
pub trait AbstractData {
fn has_spec(&self, path: &Path) -> bool;
fn has_field(&self, path: &Path, field: &str) -> bool;
fn spec_type(&self, path: &Path) -> Option<SpecType>;
fn try_get(&self, path: &Path, field: &str) -> Result<Option<Cow<'_, Value>>>;
fn get(&self, path: &Path, field: &str) -> Result<Cow<'_, Value>> {
self.try_get(path, field)?
.ok_or_else(|| anyhow!("No field '{field}' at path '{path}'"))
}
fn list(&self, path: &Path) -> Option<Vec<String>>;
fn paths(&self) -> Vec<Path>;
fn as_data(&self) -> Option<&Data> {
None
}
fn as_data_mut(&mut self) -> Option<&mut Data> {
None
}
}
pub type LayerData = Box<dyn AbstractData>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn layer_offset_identity_is_identity() {
assert!(LayerOffset::IDENTITY.is_identity());
assert!(LayerOffset::default().is_identity());
assert!(!LayerOffset::new(0.0, 2.0).is_identity());
assert!(!LayerOffset::new(1.0, 1.0).is_identity());
}
#[test]
fn layer_offset_valid_composition_rejects_non_positive_scale() {
assert!(LayerOffset::new(10.0, 1.0).is_valid_composition());
assert!(!LayerOffset::new(10.0, 0.0).is_valid_composition());
assert!(!LayerOffset::new(10.0, -1.0).is_valid_composition());
assert!(!LayerOffset::new(f64::INFINITY, 1.0).is_valid_composition());
assert!(!LayerOffset::new(0.0, f64::NAN).is_valid_composition());
}
#[test]
fn layer_offset_sanitized_drops_invalid_to_identity() {
assert_eq!(LayerOffset::new(10.0, 2.0).sanitized(), LayerOffset::new(10.0, 2.0));
assert_eq!(LayerOffset::new(5.0, -1.0).sanitized(), LayerOffset::IDENTITY);
assert_eq!(LayerOffset::new(5.0, 0.0).sanitized(), LayerOffset::IDENTITY);
}
#[test]
fn layer_offset_concatenate_matches_spec_formula() {
let outer = LayerOffset::new(10.0, 2.0);
let inner = LayerOffset::new(20.0, 1.0);
assert_eq!(outer.concatenate(&inner), LayerOffset::new(50.0, 2.0));
}
#[test]
fn layer_offset_concatenate_is_associative() {
let a = LayerOffset::new(10.0, 2.0);
let b = LayerOffset::new(20.0, 0.5);
let c = LayerOffset::new(5.0, 3.0);
let ab_c = a.concatenate(&b).concatenate(&c);
let a_bc = a.concatenate(&b.concatenate(&c));
assert!((ab_c.offset - a_bc.offset).abs() < 1e-12);
assert!((ab_c.scale - a_bc.scale).abs() < 1e-12);
}
#[test]
fn layer_offset_identity_is_neutral() {
let a = LayerOffset::new(10.0, 2.0);
assert_eq!(a.concatenate(&LayerOffset::IDENTITY), a);
assert_eq!(LayerOffset::IDENTITY.concatenate(&a), a);
}
}