Skip to content

Commit

Permalink
feat: add Maybe, fix HasSchema impls for generic types, and impl …
Browse files Browse the repository at this point in the history
…`HasSchema` derive for generic types. (#199)
  • Loading branch information
zicklag committed Sep 18, 2023
1 parent 3a0f12f commit c331cd6
Show file tree
Hide file tree
Showing 8 changed files with 503 additions and 247 deletions.
32 changes: 32 additions & 0 deletions framework_crates/bones_asset/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,38 @@ pub trait AssetLoader: Sync + Send + 'static {
fn load(&self, ctx: AssetLoadCtx, bytes: &[u8]) -> anyhow::Result<SchemaBox>;
}

/// A custom asset loader implementation for a metadata asset.
///
/// This is similar in purpose to implementing [`AssetLoader`], but instead of loading from bytes,
/// it loads from the deserialized [`SchemaRefMut`] of a metadata asset and must be added as a
/// schema type data.
#[derive(HasSchema)]
#[schema(no_clone, no_default)]
pub struct SchemaMetaAssetLoader(
pub fn(
ctx: &mut MetaAssetLoadCtx,
ptr: SchemaRefMut<'_, '_>,
deserialzer: &mut dyn erased_serde::Deserializer,
) -> anyhow::Result<()>,
);

impl SchemaMetaAssetLoader {
/// Load the asset
pub fn load<'a, 'de, D>(
&self,
ctx: &mut MetaAssetLoadCtx,
ptr: SchemaRefMut<'a, 'a>,
deserializer: D,
) -> Result<(), erased_serde::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let mut de = <dyn erased_serde::Deserializer>::erase(deserializer);
(self.0)(ctx, ptr, &mut de).map_err(|e| erased_serde::Error::custom(e.to_string()))
}
}

/// The kind of asset a type represents.
#[derive(HasSchema)]
#[schema(opaque, no_default, no_clone)]
Expand Down
111 changes: 111 additions & 0 deletions framework_crates/bones_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#![cfg_attr(doc, allow(unknown_lints))]
#![deny(rustdoc::all)]

use serde::{de::DeserializeSeed, Deserializer};

/// Helper to export the same types in the crate root and in the prelude.
macro_rules! pub_use {
() => {
Expand All @@ -19,6 +21,7 @@ pub_use!();
/// The prelude.
pub mod prelude {
pub_use!();
pub use super::{Maybe, Maybe::*};
}

mod asset;
Expand All @@ -28,3 +31,111 @@ mod io;
mod parse;
mod path;
mod server;

/// An equivalent to [`Option<T>`] that has a stable memory layout and implements [`HasSchema`].
#[derive(HasSchema, Clone, Default, Debug)]
#[type_data(SchemaMetaAssetLoader(maybe_loader))]
#[repr(C, u8)]
pub enum Maybe<T> {
/// The value is not set.
#[default]
Unset,
/// The value is set.
Set(T),
}

impl<T> Maybe<T> {
/// Convert this [`Maybe`] into an [`Option`].
pub fn option(self) -> Option<T> {
self.into()
}
}

impl<T> From<Maybe<T>> for Option<T> {
fn from(value: Maybe<T>) -> Self {
match value {
Maybe::Set(s) => Some(s),
Maybe::Unset => None,
}
}
}

impl<T> From<Option<T>> for Maybe<T> {
fn from(value: Option<T>) -> Self {
match value {
Some(s) => Maybe::Set(s),
None => Maybe::Unset,
}
}
}

fn maybe_loader(
ctx: &mut MetaAssetLoadCtx,
ptr: SchemaRefMut<'_, '_>,
deserialzer: &mut dyn erased_serde::Deserializer,
) -> anyhow::Result<()> {
deserialzer.deserialize_option(MaybeVisitor { ctx, ptr })?;

Ok(())
}

struct MaybeVisitor<'a, 'srv> {
ctx: &'a mut MetaAssetLoadCtx<'srv>,
ptr: SchemaRefMut<'a, 'a>,
}

impl<'a, 'srv, 'de> serde::de::Visitor<'de> for MaybeVisitor<'a, 'srv> {
type Value = ();

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "an optional value")
}

fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(())
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(())
}

fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
// Write the enum discriminant for the `Set` variant
// SOUND: we know the discriminant due to the `#[repr(C, u8)]` annotation.
unsafe {
self.ptr.as_ptr().write(1);
}

// Get the pointer to the enum value
let value_offset = self.ptr.schema().field_offsets()[0].1;
// NOTE: we take the schema of the first argument of the second enum variant of the
// [`Maybe`] enum because we know that the `Set` variant only has one argument at offset 0
// and we actually want to deserialize the inner type, not a typle of length zero.
let value_schema = self.ptr.schema().kind.as_enum().unwrap().variants[1]
.schema
.kind
.as_struct()
.unwrap()
.fields[0]
.schema;
// SOUND: the schema asserts this is valid.
let value_ref = unsafe {
SchemaRefMut::from_ptr_schema(self.ptr.as_ptr().add(value_offset), value_schema)
};

// Load the enum value
SchemaPtrLoadCtx {
ctx: self.ctx,
ptr: value_ref,
}
.deserialize(deserializer)
}
}
27 changes: 21 additions & 6 deletions framework_crates/bones_asset/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,16 +609,22 @@ fn path_is_metadata(path: &Path) -> bool {
ext == "yaml" || ext == "yml" || ext == "json"
}

use metadata::*;
pub use metadata::*;
mod metadata {
use serde::de::{DeserializeSeed, Error, VariantAccess, Visitor};

use super::*;

/// Context provided while loading a metadata asset.
pub struct MetaAssetLoadCtx<'srv> {
/// The asset server.
pub server: &'srv mut AssetServer,
/// The dependency list of this asset. This should be updated by asset loaders as
/// dependencies are added.
pub dependencies: &'srv mut Vec<Cid>,
/// The location that the asset is being loaded from.
pub loc: AssetLocRef<'srv>,
/// The schema of the asset being loaded.
pub schema: &'static Schema,
}

Expand All @@ -642,9 +648,12 @@ mod metadata {
}
}

struct SchemaPtrLoadCtx<'a, 'srv, 'ptr, 'prnt> {
ctx: &'a mut MetaAssetLoadCtx<'srv>,
ptr: SchemaRefMut<'ptr, 'prnt>,
/// The load context for a [`SchemaRefMut`].
pub struct SchemaPtrLoadCtx<'a, 'srv, 'ptr, 'prnt> {
/// The metadata asset load context.
pub ctx: &'a mut MetaAssetLoadCtx<'srv>,
/// The pointer to load.
pub ptr: SchemaRefMut<'ptr, 'prnt>,
}

impl<'a, 'srv, 'ptr, 'prnt, 'de> DeserializeSeed<'de> for SchemaPtrLoadCtx<'a, 'srv, 'ptr, 'prnt> {
Expand Down Expand Up @@ -679,8 +688,14 @@ mod metadata {
return Ok(());
}

// Use custom deserialize implementation if present.
if let Some(schema_deserialize) = self.ptr.schema().type_data.get::<SchemaDeserialize>()
// Use custom asset load or deserialize implementation if present.
if let Some(custom_loader) = self.ptr.schema().type_data.get::<SchemaMetaAssetLoader>()
{
return custom_loader
.load(self.ctx, self.ptr, deserializer)
.map_err(|e| D::Error::custom(e.to_string()));
} else if let Some(schema_deserialize) =
self.ptr.schema().type_data.get::<SchemaDeserialize>()
{
return schema_deserialize.deserialize(self.ptr, deserializer);
}
Expand Down
Loading

0 comments on commit c331cd6

Please sign in to comment.