From 0a0727a69a36688a5f345e874b6e9dfa672d5fb3 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Mon, 18 Dec 2023 10:33:20 -0800 Subject: [PATCH] Add `Root::push()` and `Index::push()` to assist with glTF construction. These methods reduce the amount of boilerplate and possible mistakes involved in constructing a glTF asset from scratch. Most use cases are served by `Root::push()`; `Index::push()` is useful for cases like animation samplers where the vector of objects is not in the `Root`, or in algorithmically complex situations such as pushing both within and outside closures, which would produce borrow conflicts if `&mut Root` was required for everything. This introduces a new paradigm for child object access: implementing `AsMut>`. This could also replace the `Get` trait's function via `AsRef<[T]>` implementations, but that would be a breaking change, so if that is to be done, let's do it separately. This commit includes those `AsRef` implementations, though, since they're harmless and parallel to the `AsMut` implementations. --- gltf-json/src/extensions/root.rs | 44 ++++++++++++++++ gltf-json/src/root.rs | 90 +++++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/gltf-json/src/extensions/root.rs b/gltf-json/src/extensions/root.rs index 7ca15073..0883d138 100644 --- a/gltf-json/src/extensions/root.rs +++ b/gltf-json/src/extensions/root.rs @@ -52,6 +52,28 @@ impl crate::root::Get for } } +#[cfg(feature = "KHR_lights_punctual")] +impl AsRef<[crate::extensions::scene::khr_lights_punctual::Light]> for crate::Root { + fn as_ref(&self) -> &[crate::extensions::scene::khr_lights_punctual::Light] { + self.extensions + .as_ref() + .and_then(|extensions| extensions.khr_lights_punctual.as_ref()) + .map(|khr_lights_punctual| khr_lights_punctual.lights.as_slice()) + .unwrap_or(&[]) + } +} +#[cfg(feature = "KHR_lights_punctual")] +impl AsMut> for crate::Root { + fn as_mut(&mut self) -> &mut Vec { + &mut self + .extensions + .get_or_insert_with(Default::default) + .khr_lights_punctual + .get_or_insert_with(Default::default) + .lights + } +} + #[cfg(feature = "KHR_materials_variants")] #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] pub struct KhrMaterialsVariants { @@ -72,3 +94,25 @@ impl crate::root::Get .get(id.value()) } } + +#[cfg(feature = "KHR_materials_variants")] +impl AsRef<[crate::extensions::scene::khr_materials_variants::Variant]> for crate::Root { + fn as_ref(&self) -> &[crate::extensions::scene::khr_materials_variants::Variant] { + self.extensions + .as_ref() + .and_then(|extensions| extensions.khr_materials_variants.as_ref()) + .map(|khr_materials_variants| khr_materials_variants.variants.as_slice()) + .unwrap_or(&[]) + } +} +#[cfg(feature = "KHR_materials_variants")] +impl AsMut> for crate::Root { + fn as_mut(&mut self) -> &mut Vec { + &mut self + .extensions + .get_or_insert_with(Default::default) + .khr_materials_variants + .get_or_insert_with(Default::default) + .variants + } +} diff --git a/gltf-json/src/root.rs b/gltf-json/src/root.rs index b1eb8e50..c436568a 100644 --- a/gltf-json/src/root.rs +++ b/gltf-json/src/root.rs @@ -13,15 +13,48 @@ use crate::{ }; use validation::Validate; +// TODO: As a breaking change, simplify by replacing uses of `Get` with `AsRef<[T]>`. + /// Helper trait for retrieving top-level objects by a universal identifier. pub trait Get { /// Retrieves a single value at the given index. fn get(&self, id: Index) -> Option<&T>; } -/// Represents an offset into an array of type `T` owned by the root glTF object. +/// Represents an offset into a vector of type `T` owned by the root glTF object. +/// +/// This type may be used with the following functions: +/// +/// * [`Root::get()`] to retrieve objects from [`Root`]. +/// * [`Root::push()`] to add new objects to [`Root`]. pub struct Index(u32, marker::PhantomData T>); +impl Index { + /// Given a vector of glTF objects, call [`Vec::push()`] to insert it into the vector, + /// then return an [`Index`] for it. + /// + /// This allows you to easily obtain [`Index`] values with the correct index and type when + /// creating a glTF asset. Note that for [`Root`], you can call [`Root::push()`] without + /// needing to retrieve the correct vector first. + /// + /// # Panics + /// + /// Panics if the vector has [`u32::MAX`] or more elements, in which case an `Index` cannot be + /// created. + pub fn push(vec: &mut Vec, value: T) -> Index { + let len = vec.len(); + let Ok(index): Result = len.try_into() else { + panic!( + "glTF vector of {ty} has {len} elements, which exceeds the Index limit", + ty = std::any::type_name::(), + ); + }; + + vec.push(value); + Index::new(index) + } +} + /// The root object of a glTF 2.0 asset. #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] pub struct Root { @@ -127,6 +160,26 @@ impl Root { (self as &dyn Get).get(index) } + /// Insert the given value into this (as via [`Vec::push()`]), then return the [`Index`] to it. + /// + /// This allows you to easily obtain [`Index`] values with the correct index and type when + /// creating a glTF asset. + /// + /// If you have a mutable borrow conflict when using this method, consider using the more + /// explicit [`Index::push()`] method, passing it only the necessary vector. + /// + /// # Panics + /// + /// Panics if there are already [`u32::MAX`] or more elements of this type, + /// in which case an `Index` cannot be created. + #[track_caller] + pub fn push(&mut self, value: T) -> Index + where + Self: AsMut>, + { + Index::push(self.as_mut(), value) + } + /// Deserialize from a JSON string slice. #[allow(clippy::should_implement_trait)] pub fn from_str(str_: &str) -> Result { @@ -299,6 +352,16 @@ macro_rules! impl_get { self.$field.get(index.value()) } } + impl AsRef<[$ty]> for Root { + fn as_ref(&self) -> &[$ty] { + &self.$field + } + } + impl AsMut> for Root { + fn as_mut(&mut self) -> &mut Vec<$ty> { + &mut self.$field + } + } }; } @@ -345,4 +408,29 @@ mod tests { Index: Send + Sync, { } + + #[test] + fn index_push() { + let some_object = "hello"; + + let mut vec = Vec::new(); + assert_eq!(Index::push(&mut vec, some_object), Index::new(0)); + assert_eq!(Index::push(&mut vec, some_object), Index::new(1)); + } + + #[test] + fn root_push() { + let some_object = Buffer { + byte_length: validation::USize64(1), + #[cfg(feature = "names")] + name: None, + uri: None, + extensions: None, + extras: Default::default(), + }; + + let mut root = Root::default(); + assert_eq!(root.push(some_object.clone()), Index::new(0)); + assert_eq!(root.push(some_object), Index::new(1)); + } }