Skip to content

Commit

Permalink
Add Root::push() and Index::push() to assist with glTF construction.
Browse files Browse the repository at this point in the history
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<Vec<T>>`. 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.
  • Loading branch information
kpreid committed Dec 24, 2023
1 parent 5647436 commit 0a0727a
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 1 deletion.
44 changes: 44 additions & 0 deletions gltf-json/src/extensions/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ impl crate::root::Get<crate::extensions::scene::khr_lights_punctual::Light> 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<Vec<crate::extensions::scene::khr_lights_punctual::Light>> for crate::Root {
fn as_mut(&mut self) -> &mut Vec<crate::extensions::scene::khr_lights_punctual::Light> {
&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 {
Expand All @@ -72,3 +94,25 @@ impl crate::root::Get<crate::extensions::scene::khr_materials_variants::Variant>
.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<Vec<crate::extensions::scene::khr_materials_variants::Variant>> for crate::Root {
fn as_mut(&mut self) -> &mut Vec<crate::extensions::scene::khr_materials_variants::Variant> {
&mut self
.extensions
.get_or_insert_with(Default::default)
.khr_materials_variants
.get_or_insert_with(Default::default)
.variants
}
}
90 changes: 89 additions & 1 deletion gltf-json/src/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,48 @@ use crate::{
};
use validation::Validate;

// TODO: As a breaking change, simplify by replacing uses of `Get<T>` with `AsRef<[T]>`.

/// Helper trait for retrieving top-level objects by a universal identifier.
pub trait Get<T> {
/// Retrieves a single value at the given index.
fn get(&self, id: Index<T>) -> 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<T>(u32, marker::PhantomData<fn() -> T>);

impl<T> Index<T> {
/// 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<T>, value: T) -> Index<T> {
let len = vec.len();
let Ok(index): Result<u32, _> = len.try_into() else {
panic!(
"glTF vector of {ty} has {len} elements, which exceeds the Index limit",
ty = std::any::type_name::<T>(),
);
};

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 {
Expand Down Expand Up @@ -127,6 +160,26 @@ impl Root {
(self as &dyn Get<T>).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<T>(&mut self, value: T) -> Index<T>
where
Self: AsMut<Vec<T>>,
{
Index::push(self.as_mut(), value)
}

/// Deserialize from a JSON string slice.
#[allow(clippy::should_implement_trait)]
pub fn from_str(str_: &str) -> Result<Self, Error> {
Expand Down Expand Up @@ -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<Vec<$ty>> for Root {
fn as_mut(&mut self) -> &mut Vec<$ty> {
&mut self.$field
}
}
};
}

Expand Down Expand Up @@ -345,4 +408,29 @@ mod tests {
Index<Material>: 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));
}
}

0 comments on commit 0a0727a

Please sign in to comment.