Skip to content

Commit

Permalink
feat: make it possible to extend type data of schemas after registrat…
Browse files Browse the repository at this point in the history
…ion. (#250)

This replaces the `SchemaTypeMap` with a new `TypeDatas` type that can
have new type datas registered while only requring a read-only reference
allowing the type datas of already-registered schemas to be updated.

This allows for a pattern similar to Rust's traits where a new crate
that can create their own type data type, and register it for foreign
types.
  • Loading branch information
zicklag authored Oct 23, 2023
1 parent 74a53dc commit e126681
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 47 deletions.
18 changes: 13 additions & 5 deletions framework_crates/bones_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ impl UntypedHandle {
#[schema(opaque, no_default)]
pub struct SchemaAssetHandle {
/// The schema of the type pointed to by the handle, if this is not an [`UntypedHandle`].
pub schema: Option<&'static Schema>,
schema: Option<&'static Schema>,
}

impl SchemaAssetHandle {
/// Returns the schema of the type pointed to by the handle, if this is not an [`UntypedHandle`].
pub fn inner_schema(&self) -> Option<&'static Schema> {
self.schema
}
}

// SAFE: We return a valid schema.
Expand Down Expand Up @@ -129,10 +136,11 @@ unsafe impl<T: HasSchema> HasSchema for Handle<T> {
eq_fn: Some(<Self as RawEq>::raw_eq),
hash_fn: Some(<Self as RawHash>::raw_hash),
type_data: {
let mut td = bones_schema::alloc::SchemaTypeMap::default();
let td = bones_schema::alloc::TypeDatas::default();
td.insert(SchemaAssetHandle {
schema: Some(T::schema()),
});
})
.unwrap();
td
},
});
Expand Down Expand Up @@ -177,8 +185,8 @@ unsafe impl HasSchema for UntypedHandle {
eq_fn: Some(<Self as RawEq>::raw_eq),
hash_fn: Some(<Self as RawHash>::raw_hash),
type_data: {
let mut td = bones_schema::alloc::SchemaTypeMap::default();
td.insert(SchemaAssetHandle { schema: None });
let td = bones_schema::alloc::TypeDatas::default();
td.insert(SchemaAssetHandle { schema: None }).unwrap();
td
},
})
Expand Down
2 changes: 1 addition & 1 deletion framework_crates/bones_asset/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl std::fmt::Display for LoaderNotFound {
write!(
f,
"Schema/loader not found for schema/extension: {}\n\
You may need to register the asset with asset_server.register_asset::<AssetType>()",
You may need to register the asset by calling `MyAsset::schema()`",
self.name
)
}
Expand Down
6 changes: 3 additions & 3 deletions framework_crates/bones_ecs/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,10 +514,10 @@ mod tests {
c: Option<ResMut<u32>>,
d: Option<ResMut<u64>>,
) {
assert!(a.as_deref() == None);
assert!(a.as_deref().is_none());
assert!(b.as_deref() == Some(&1));
assert!(c.as_deref() == None);
assert!(d.as_deref() == Some(&mut 2));
assert!(c.as_deref().is_none());
assert!(d.as_deref() == Some(&2));
}

let mut world = World::new();
Expand Down
2 changes: 1 addition & 1 deletion framework_crates/bones_framework/src/localization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl<T: HasSchema> SystemParam for Localization<'_, T> {
.enumerate()
{
if let Some(handle_data) = field.schema.type_data.get::<SchemaAssetHandle>() {
if let Some(schema) = handle_data.schema {
if let Some(schema) = handle_data.inner_schema() {
if schema == LocalizationAsset::schema() {
idx = Some(i);
break;
Expand Down
6 changes: 3 additions & 3 deletions framework_crates/bones_schema/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub fn derive_has_schema(input: TokenStream) -> TokenStream {
let add_derive_type_datas = derive_type_data_flags.into_iter().map(|ty| {
let ty = format_ident!("{ty}");
quote! {
tds.insert(<#ty as #schema_mod::FromType<#name>>::from_type());
tds.insert(<#ty as #schema_mod::FromType<#name>>::from_type()).unwrap();
}
});
let add_type_datas = input
Expand All @@ -106,10 +106,10 @@ pub fn derive_has_schema(input: TokenStream) -> TokenStream {

quote! {
{
let mut tds = #schema_mod::alloc::SchemaTypeMap::default();
let tds = #schema_mod::alloc::TypeDatas::default();
#(#add_derive_type_datas),*
#(
tds.insert(#add_type_datas);
tds.insert(#add_type_datas).unwrap();
),*
tds
}
Expand Down
4 changes: 2 additions & 2 deletions framework_crates/bones_schema/src/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ mod vec;
pub use map::*;
mod map;

pub use type_set::*;
mod type_set;
pub use type_datas::*;
mod type_datas;
102 changes: 102 additions & 0 deletions framework_crates/bones_schema/src/alloc/type_datas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use append_only_vec::AppendOnlyVec;

use crate::prelude::*;

/// A `TypeMap`-like structure, that does not allow removing entries or updating exisintg
/// entries.
///
/// This structure doesn't require a mutable reference to insert records
#[derive(Debug)]
pub struct TypeDatas(AppendOnlyVec<SchemaBox>);
impl Default for TypeDatas {
fn default() -> Self {
Self(AppendOnlyVec::new())
}
}
impl Clone for TypeDatas {
fn clone(&self) -> Self {
let clone = TypeDatas::default();
for entry in self.0.iter() {
clone.insert(entry.clone()).unwrap();
}
clone
}
}

impl TypeDatas {
/// Insert data into the store.
pub fn insert<T: HasSchema>(&self, data: T) -> Result<(), TypeDataAlreadyInserted> {
self.insert_box(SchemaBox::new(data))
}

/// Insert boxed data into the store.
pub fn insert_box(&self, data: SchemaBox) -> Result<(), TypeDataAlreadyInserted> {
let schema = data.schema();
for entry in self.0.iter() {
if entry.schema() == schema {
return Err(TypeDataAlreadyInserted(schema));
}
}
self.0.push(data);
Ok(())
}

/// Borrow data from the store, if it exists.
pub fn get<T: HasSchema>(&self) -> Option<&T> {
let id = T::schema().id();
for data in self.0.iter() {
if data.schema().id() == id {
return Some(data.cast_ref());
}
}
None
}

/// Borrow data from the store, if it exists.
pub fn get_ref(&self, id: SchemaId) -> Option<SchemaRef> {
for data in self.0.iter() {
if data.schema().id() == id {
return Some(data.as_ref());
}
}
None
}

/// Iterate over type datas.
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &SchemaBox> {
self.0.iter()
}
}

/// Error type for [`TypeDatas`]
#[derive(Debug)]
pub struct TypeDataAlreadyInserted(&'static Schema);

impl std::fmt::Display for TypeDataAlreadyInserted {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Type data already contains an entry of type: {}",
self.0.full_name,
))
}
}
impl std::error::Error for TypeDataAlreadyInserted {}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn smoke() {
let tds = TypeDatas::default();

tds.insert(String::from("hi")).unwrap();
assert_eq!(Some("hi"), tds.get::<String>().map(|x| x.as_str()));

tds.insert(7u32).unwrap();
assert_eq!(Some(&7), tds.get::<u32>());

let result = tds.insert(String::from("bye"));
assert!(matches!(result, Err(TypeDataAlreadyInserted(_))));
}
}
25 changes: 0 additions & 25 deletions framework_crates/bones_schema/src/alloc/type_set.rs

This file was deleted.

4 changes: 2 additions & 2 deletions framework_crates/bones_schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::{alloc::Layout, any::TypeId, borrow::Cow};

use crate::{alloc::SchemaTypeMap, prelude::*};
use crate::{alloc::TypeDatas, prelude::*};

/// Trait implemented for types that have a [`Schema`].
///
Expand Down Expand Up @@ -165,7 +165,7 @@ pub struct SchemaData {
/// #[derive_type_data(SomeTypeData)]
/// struct MyData;
/// ```
pub type_data: SchemaTypeMap,
pub type_data: TypeDatas,

// NOTE: The fields below could be implemented as type datas, and it would be nicely elegant to
// do so, but for performance reasons, we put them right in the [`Schema`] struct because
Expand Down
12 changes: 7 additions & 5 deletions framework_crates/bones_schema/src/std_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bones_utils::{fxhash::FxHasher, Ustr};
#[cfg(feature = "serde")]
use serde::{de::Error, Deserialize};

use crate::{alloc::SchemaTypeMap, prelude::*, raw_fns::*};
use crate::{alloc::TypeDatas, prelude::*, raw_fns::*};

use std::{alloc::Layout, any::TypeId, hash::Hasher, sync::OnceLock, time::Duration};

Expand Down Expand Up @@ -142,7 +142,7 @@ unsafe impl HasSchema for Ustr {
hash_fn: Some(<Self as RawHash>::raw_hash),
eq_fn: Some(<Self as RawEq>::raw_eq),
type_data: {
let mut td = SchemaTypeMap::default();
let td = TypeDatas::default();
#[cfg(feature = "serde")]
td.insert(SchemaDeserialize {
deserialize_fn: |reference, deserializer| {
Expand All @@ -156,7 +156,8 @@ unsafe impl HasSchema for Ustr {

Ok(())
},
});
})
.unwrap();
td
},
})
Expand All @@ -183,7 +184,7 @@ unsafe impl HasSchema for Duration {
hash_fn: Some(<Self as RawHash>::raw_hash),
eq_fn: Some(<Self as RawEq>::raw_eq),
type_data: {
let mut td = SchemaTypeMap::default();
let td = TypeDatas::default();
#[cfg(feature = "serde")]
td.insert(SchemaDeserialize {
deserialize_fn: |reference, deserializer| {
Expand All @@ -209,7 +210,8 @@ unsafe impl HasSchema for Duration {

Ok(())
},
});
})
.unwrap();
td
},
})
Expand Down

0 comments on commit e126681

Please sign in to comment.