diff --git a/Cargo.toml b/Cargo.toml index 304824a..dd24ed6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,7 @@ serde_yaml = { version = "0.9", optional = true, default-features = false } [dev-dependencies] ron = "0.8" +serde_yaml = "0.9" bevy = "0.10.1" bevy_prototype_lyon = "0.8.0" trybuild = "1.0.71" @@ -119,6 +120,16 @@ name = "basic_schematic" path = "examples/basic_schematic.rs" required-features = ["ron", "auto_name", "custom_schematics", "bevy_sprite"] +[[example]] +name = "custom_config" +path = "examples/custom_config.rs" +required-features = ["ron", "auto_name"] + +[[example]] +name = "custom_loader" +path = "examples/custom_loader.rs" +required-features = ["ron", "auto_name"] + [[example]] name = "custom_schematic" path = "examples/custom_schematic.rs" diff --git a/assets/examples/custom_config/Player.prototype.ron b/assets/examples/custom_config/Player.prototype.ron new file mode 100644 index 0000000..6c0d91e --- /dev/null +++ b/assets/examples/custom_config/Player.prototype.ron @@ -0,0 +1,3 @@ +( + name: "Player" +) \ No newline at end of file diff --git a/assets/examples/custom_loader/Player.custom.yaml b/assets/examples/custom_loader/Player.custom.yaml new file mode 100644 index 0000000..7a09149 --- /dev/null +++ b/assets/examples/custom_loader/Player.custom.yaml @@ -0,0 +1,11 @@ +name: Player +schematics: + custom_loader::Player: + # Comment out the `Health` component below to have this prototype + # be rejected by our custom loader. + custom_loader::Health: + - 100 + # Comment out the `Mana` component below to have a default instance + # of it be inserted by our custom loader. + custom_loader::Mana: + - 100 diff --git a/bevy_proto_backend/src/children/builder.rs b/bevy_proto_backend/src/children/builder.rs index d1091b1..9855c6f 100644 --- a/bevy_proto_backend/src/children/builder.rs +++ b/bevy_proto_backend/src/children/builder.rs @@ -2,63 +2,61 @@ use std::path::Path; use bevy::asset::{AssetIo, Handle, LoadedAsset}; -use crate::load::ProtoLoadContext; +use crate::load::{Loader, ProtoLoadContext}; use crate::path::{ProtoPath, ProtoPathContext}; use crate::proto::Prototypical; /// A helper struct for properly building out a [prototype's] children. /// /// [prototype's]: Prototypical -pub struct ProtoChildBuilder<'ctx, 'load_ctx, T: Prototypical> { - pub(crate) context: ProtoLoadContext<'ctx, 'load_ctx, T>, - child_count: usize, +pub struct ProtoChildBuilder<'ctx, 'load_ctx, T: Prototypical, L: Loader> { + pub(crate) context: ProtoLoadContext<'ctx, 'load_ctx, T, L>, } -impl<'ctx, 'load_ctx, T: Prototypical> ProtoChildBuilder<'ctx, 'load_ctx, T> { - pub(crate) fn new(context: ProtoLoadContext<'ctx, 'load_ctx, T>) -> Self { - Self { - context, - child_count: 0, - } +impl<'ctx, 'load_ctx, T: Prototypical, L: Loader> ProtoChildBuilder<'ctx, 'load_ctx, T, L> { + pub(crate) fn new(context: ProtoLoadContext<'ctx, 'load_ctx, T, L>) -> Self { + Self { context } } /// Add the given child to the parent. - pub fn add_child(&mut self, mut child: T) -> Result, T::Error> { - let deps = self.context.preprocess_proto(&mut child)?; + pub fn add_child(&mut self, child: T) -> Result, L::Error> { + let (child, meta, deps) = self.context.preprocess_proto(child)?; let child_handle = self.context.set_labeled_asset( - &format!("{:0>3}--{:0>3}", self.context.depth(), self.child_count), + meta.path.label().expect("child should have an asset label"), LoadedAsset::new(child).with_dependencies(deps), ); - self.child_count += 1; + self.context.increment_index(); Ok(child_handle) } /// Add the child with the given path to the parent. - pub fn add_child_path(&mut self, child_path: ProtoPath) -> Result, T::Error> { + pub fn add_child_path(&mut self, child_path: ProtoPath) -> Result, L::Error> { self.context .child_paths_mut() .push(child_path.asset_path().to_owned()); - self.child_count += 1; + self.context.increment_index(); Ok(self.context.get_handle(child_path)) } /// Access the current [`ProtoLoadContext`]. - pub fn context(&self) -> &ProtoLoadContext<'ctx, 'load_ctx, T> { + pub fn context(&self) -> &ProtoLoadContext<'ctx, 'load_ctx, T, L> { &self.context } /// Access the current [`ProtoLoadContext`] mutably. - pub fn context_mut(&mut self) -> &mut ProtoLoadContext<'ctx, 'load_ctx, T> { + pub fn context_mut(&mut self) -> &mut ProtoLoadContext<'ctx, 'load_ctx, T, L> { &mut self.context } } -impl<'ctx, 'load_ctx, T: Prototypical> ProtoPathContext for ProtoChildBuilder<'ctx, 'load_ctx, T> { +impl<'ctx, 'load_ctx, T: Prototypical, L: Loader> ProtoPathContext + for ProtoChildBuilder<'ctx, 'load_ctx, T, L> +{ fn base_path(&self) -> &Path { self.context.base_path() } diff --git a/bevy_proto_backend/src/load/asset_loader.rs b/bevy_proto_backend/src/load/asset_loader.rs new file mode 100644 index 0000000..691cc01 --- /dev/null +++ b/bevy_proto_backend/src/load/asset_loader.rs @@ -0,0 +1,67 @@ +use std::marker::PhantomData; +use std::sync::Arc; + +use bevy::app::AppTypeRegistry; +use bevy::asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset}; +use bevy::prelude::{Handle, World}; +use parking_lot::RwLock; + +use crate::load::{Loader, ProtoLoadContext}; +use crate::proto::{Config, Prototypical}; +use crate::registration::{LoadQueue, ProtoRegistry}; + +pub(crate) struct ProtoAssetLoader, C: Config> { + registry: AppTypeRegistry, + proto_registry: Arc>>, + loader: L, + _phantom: PhantomData, +} + +impl, C: Config> ProtoAssetLoader { + pub fn new(loader: L, world: &mut World) -> Self { + world.init_resource::(); + world.init_resource::>(); + + Self { + registry: world.resource::().clone(), + proto_registry: world.resource::>().load_queue().clone(), + loader, + _phantom: Default::default(), + } + } +} + +impl, C: Config> AssetLoader for ProtoAssetLoader { + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, anyhow::Result<(), anyhow::Error>> { + Box::pin(async { + let registry = self.registry.read(); + let mut ctx = ProtoLoadContext::::new(®istry, &self.loader, load_context); + + // 1. Deserialize the prototype + let prototype = L::deserialize(bytes, &mut ctx)?; + let (prototype, _, mut dependency_paths) = ctx.preprocess_proto(prototype)?; + dependency_paths.append(ctx.child_paths_mut()); + + // 2. Register + let asset_handle: Handle = + load_context.get_handle(AssetPath::new_ref(load_context.path(), None)); + self.proto_registry + .write() + .queue(prototype.id().clone(), &asset_handle); + + // 3. Finish! + let asset = LoadedAsset::new(prototype).with_dependencies(dependency_paths); + load_context.set_default_asset(asset); + + Ok(()) + }) + } + + fn extensions(&self) -> &[&str] { + self.loader.extensions() + } +} diff --git a/bevy_proto_backend/src/load/load_context.rs b/bevy_proto_backend/src/load/load_context.rs index 8831da1..021323a 100644 --- a/bevy_proto_backend/src/load/load_context.rs +++ b/bevy_proto_backend/src/load/load_context.rs @@ -1,4 +1,5 @@ use std::error::Error; +use std::fmt::{Display, Formatter}; use std::marker::PhantomData; use std::path::Path; @@ -8,33 +9,34 @@ use bevy::reflect::TypeRegistryInternal; use crate::children::ProtoChildBuilder; use crate::deps::DependenciesBuilder; +use crate::load::{Loader, ProtoLoadMeta}; use crate::path::ProtoPathContext; use crate::proto::Prototypical; /// The context when loading a [prototype]. /// /// [prototype]: Prototypical -pub struct ProtoLoadContext<'a, 'ctx, T: Prototypical> { +pub struct ProtoLoadContext<'a, 'ctx, T: Prototypical, L: Loader> { registry: &'a TypeRegistryInternal, + loader: &'a L, load_context: Option<&'a mut LoadContext<'ctx>>, - extensions: &'a [&'static str], child_paths: Vec>, - depth: usize, + index_path: IndexPath, _phantom: PhantomData, } -impl<'a, 'ctx, T: Prototypical> ProtoLoadContext<'a, 'ctx, T> { +impl<'a, 'ctx, T: Prototypical, L: Loader> ProtoLoadContext<'a, 'ctx, T, L> { pub(crate) fn new( registry: &'a TypeRegistryInternal, + loader: &'a L, load_context: &'a mut LoadContext<'ctx>, - extensions: &'a [&'static str], ) -> Self { Self { registry, + loader, load_context: Some(load_context), - extensions, child_paths: Vec::new(), - depth: 0, + index_path: IndexPath::default(), _phantom: Default::default(), } } @@ -51,36 +53,49 @@ impl<'a, 'ctx, T: Prototypical> ProtoLoadContext<'a, 'ctx, T> { /// The current hierarchical depth of the prototype being processed. pub fn depth(&self) -> usize { - self.depth + self.index_path.depth() } /// Creates a [`ProtoChildBuilder`] to allow for proper child processing. - pub fn with_children) -> Result<(), E>>( + pub fn with_children) -> Result<(), E>>( &mut self, f: F, ) -> Result<(), E> { + self.index_path.push(); + let mut ctx = Self { registry: self.registry, + loader: self.loader, load_context: self.load_context.take(), - extensions: self.extensions, child_paths: Vec::new(), - depth: self.depth + 1, + index_path: IndexPath::default(), _phantom: Default::default(), }; std::mem::swap(&mut ctx.child_paths, &mut self.child_paths); - self.depth += 1; + std::mem::swap(&mut ctx.index_path, &mut self.index_path); let mut builder = ProtoChildBuilder::new(ctx); let result = f(&mut builder); + self.load_context = builder.context.load_context; self.child_paths = builder.context.child_paths; + self.index_path = builder.context.index_path; - self.depth -= 1; + self.index_path.pop(); result } + /// The loader used to load the prototype. + pub fn loader(&self) -> &'a L { + self.loader + } + + pub(crate) fn increment_index(&mut self) { + self.index_path.increment(); + } + pub(crate) fn set_labeled_asset( &mut self, label: &str, @@ -100,10 +115,32 @@ impl<'a, 'ctx, T: Prototypical> ProtoLoadContext<'a, 'ctx, T> { &mut self.child_paths } + pub(crate) fn meta(&self) -> ProtoLoadMeta { + let label = if self.index_path.is_root() { + // Root prototype + None + } else { + // Descendant prototype + Some(self.index_path.to_string()) + }; + + let path = AssetPath::new(self.base_path().to_owned(), label); + let handle = self.get_handle(path.get_id()); + + ProtoLoadMeta { + path, + handle, + depth: self.depth(), + } + } + pub(crate) fn preprocess_proto( &mut self, - prototype: &mut T, - ) -> Result>, T::Error> { + prototype: T, + ) -> Result<(T, ProtoLoadMeta, Vec>), L::Error> { + let meta = self.meta(); + let mut prototype = self.loader.on_load_prototype(prototype, &meta)?; + let mut deps = DependenciesBuilder::new(self.load_context.as_mut().unwrap()); // 1. Track schematic dependencies @@ -126,11 +163,13 @@ impl<'a, 'ctx, T: Prototypical> ProtoLoadContext<'a, 'ctx, T> { dependency_paths.extend(templates.iter().map(|(path, _)| path.into())); } - Ok(dependency_paths) + Ok((prototype, meta, dependency_paths)) } } -impl<'a, 'ctx, T: Prototypical> ProtoPathContext for ProtoLoadContext<'a, 'ctx, T> { +impl<'a, 'ctx, T: Prototypical, L: Loader> ProtoPathContext + for ProtoLoadContext<'a, 'ctx, T, L> +{ fn base_path(&self) -> &Path { self.load_context.as_ref().unwrap().path() } @@ -140,6 +179,55 @@ impl<'a, 'ctx, T: Prototypical> ProtoPathContext for ProtoLoadContext<'a, 'ctx, } fn extensions(&self) -> &[&'static str] { - self.extensions + self.loader.extensions() + } +} + +/// Helper struct for tracking the depth and index of a prototype being processed. +/// +/// Each entry in the index path represents the index of the prototype, +/// and the index of that entry represents the depth of the prototype. +/// +/// The root prototype is represented as an empty index path. +#[derive(Default, Debug)] +struct IndexPath(Vec); + +impl IndexPath { + pub fn push(&mut self) { + self.0.push(0); + } + + pub fn pop(&mut self) { + self.0.pop(); + } + + pub fn increment(&mut self) { + *self.0.last_mut().expect("index path should not be empty") += 1; + } + + pub fn depth(&self) -> usize { + self.0.len() + } + + pub fn is_root(&self) -> bool { + self.0.is_empty() + } +} + +impl Display for IndexPath { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut is_first = true; + for index in self.0.iter() { + if is_first { + write!(f, "Child: ")?; + is_first = false; + } else { + write!(f, "--")?; + } + + write!(f, "{:0>4}", index)?; + } + + Ok(()) } } diff --git a/bevy_proto_backend/src/load/loader.rs b/bevy_proto_backend/src/load/loader.rs index 6884ba5..810f440 100644 --- a/bevy_proto_backend/src/load/loader.rs +++ b/bevy_proto_backend/src/load/loader.rs @@ -1,72 +1,123 @@ -use std::cmp::Reverse; -use std::marker::PhantomData; -use std::sync::Arc; - -use anyhow::Error; -use bevy::app::AppTypeRegistry; -use bevy::asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset}; -use bevy::prelude::{FromWorld, Handle, World}; -use parking_lot::RwLock; - use crate::load::ProtoLoadContext; -use crate::proto::Config; use crate::proto::Prototypical; -use crate::registration::{LoadQueue, ProtoRegistry}; +use crate::schematics::SchematicError; +use bevy::asset::{AssetPath, Handle}; +use bevy::prelude::FromWorld; +use std::error::Error; +use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; -pub(crate) struct ProtoLoader { - registry: AppTypeRegistry, - proto_registry: Arc>>, - extensions: Box<[&'static str]>, - _phantom: PhantomData, -} +/// Configures how a [prototype] should be loaded. +/// +/// [prototype]: Prototypical +pub trait Loader: FromWorld + Clone + Send + Sync + 'static { + /// Error type returned by this loader during deserialization and loading. + type Error: Error + From + Send + Sync; -impl FromWorld for ProtoLoader { - fn from_world(world: &mut World) -> Self { - let mut extensions = world.resource::().extensions(); + /// Deserialize the given slice of bytes into an instance of `T`. + fn deserialize(bytes: &[u8], ctx: &mut ProtoLoadContext) -> Result; - // Ensure the extensions are in order of longest to shortest - extensions.sort_by_key(|ext| Reverse(ext.len())); + /// A list of supported extensions. + /// + /// Extensions should be in order of most-specific to least specific, + /// and should not be prepended by a dot (`.`). + /// Generally, this means it should be in order of longest to shortest. + /// + /// For example, this could return: + /// + /// ``` + /// # use bevy::prelude::Resource; + /// # use bevy_proto_backend::proto::{Config, Prototypical}; + /// # use bevy_proto_backend::load::{Loader, ProtoLoadContext}; + /// # use bevy_proto_backend::schematics::SchematicError; + /// # #[derive(Default, Clone)] + /// struct MyPrototypeLoader; + /// impl Loader for MyPrototypeLoader { + /// # type Error = SchematicError; + /// fn extensions(&self) -> &[&'static str] { + /// &[ + /// // Most Specific (Longest) // + /// "prototype.yaml", + /// "prototype.ron", + /// "yaml", + /// "ron", + /// // Least Specific (Shortest) // + /// ] + /// } + /// # fn deserialize(bytes: &[u8], ctx: &mut ProtoLoadContext) -> Result { + /// # todo!() + /// # } + /// } + /// ``` + fn extensions(&self) -> &[&'static str]; - Self { - registry: world.resource::().clone(), - proto_registry: world.resource::>().load_queue().clone(), - extensions, - _phantom: Default::default(), - } + /// Callback for when a [prototype] is loaded. + /// + /// This is called right after deserialization, but before any preprocessing. + /// + /// This can be used to modify the prototype before it is processed, + /// handle side effects, or even reject the prototype with an error. + /// + /// By default, this will do nothing and return the prototype as-is. + /// + /// Note: Currently, this logic can also be implemented in [`deserialize`], however, + /// this may change in the future so it's best to put this kind of logic here. + /// + /// [prototype]: Prototypical + /// [`deserialize`]: Loader::deserialize + fn on_load_prototype(&self, prototype: T, meta: &ProtoLoadMeta) -> Result { + let _ = meta; + Ok(prototype) } } -impl AssetLoader for ProtoLoader { - fn load<'a>( - &'a self, - bytes: &'a [u8], - load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, anyhow::Result<(), Error>> { - Box::pin(async { - let registry = self.registry.read(); - let mut ctx = ProtoLoadContext::::new(®istry, load_context, &self.extensions); +/// Metadata about a [prototype] that is being loaded. +/// +/// [prototype]: Prototypical +pub struct ProtoLoadMeta { + /// The path to the prototype. + pub path: AssetPath<'static>, + /// A strong handle to the prototype. + pub handle: Handle, + /// The depth of this prototype in the raw prototype tree. + /// + /// Children loaded by path are not included in this depth + /// since they are loaded separately. + pub depth: usize, +} - // 1. Deserialize the prototype - let mut prototype = T::deserialize(bytes, &mut ctx)?; - let mut dependency_paths = ctx.preprocess_proto(&mut prototype)?; - dependency_paths.append(ctx.child_paths_mut()); +impl Debug for ProtoLoadMeta { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ProtoLoadMeta") + .field("path", &self.path) + .field("depth", &self.depth) + .field("handle", &self.handle) + .finish() + } +} - // 2. Register - let asset_handle: Handle = - load_context.get_handle(AssetPath::new_ref(load_context.path(), None)); - self.proto_registry - .write() - .queue(prototype.id().clone(), &asset_handle); +impl Clone for ProtoLoadMeta { + fn clone(&self) -> Self { + Self { + path: self.path.clone(), + handle: self.handle.clone(), + depth: self.depth, + } + } +} - // 3. Finish! - let asset = LoadedAsset::new(prototype).with_dependencies(dependency_paths); - load_context.set_default_asset(asset); +impl Eq for ProtoLoadMeta {} - Ok(()) - }) +impl PartialEq for ProtoLoadMeta { + fn eq(&self, other: &Self) -> bool { + self.depth == other.depth && self.handle == other.handle && self.path == other.path } +} - fn extensions(&self) -> &[&str] { - &self.extensions +impl Hash for ProtoLoadMeta { + fn hash(&self, state: &mut H) { + self.path.hash(state); + self.handle.hash(state); + self.depth.hash(state); } } diff --git a/bevy_proto_backend/src/load/mod.rs b/bevy_proto_backend/src/load/mod.rs index 0bc29a2..a171b27 100644 --- a/bevy_proto_backend/src/load/mod.rs +++ b/bevy_proto_backend/src/load/mod.rs @@ -1,5 +1,7 @@ +pub(crate) use asset_loader::*; pub use load_context::*; -pub(crate) use loader::*; +pub use loader::*; +mod asset_loader; mod load_context; mod loader; diff --git a/bevy_proto_backend/src/path/list.rs b/bevy_proto_backend/src/path/list.rs index 769eb57..5bed164 100644 --- a/bevy_proto_backend/src/path/list.rs +++ b/bevy_proto_backend/src/path/list.rs @@ -3,23 +3,25 @@ use std::fmt::Formatter; use serde::de::{DeserializeSeed, SeqAccess, Visitor}; use serde::Deserializer; -use crate::load::ProtoLoadContext; +use crate::load::{Loader, ProtoLoadContext}; use crate::path::{ProtoPath, ProtoPathDeserializer}; use crate::proto::Prototypical; /// Deserializer for a sequence of [`ProtoPath`]s. -pub struct ProtoPathListDeserializer<'a, 'ctx, 'load_ctx, T: Prototypical> { - context: &'a ProtoLoadContext<'ctx, 'load_ctx, T>, +pub struct ProtoPathListDeserializer<'a, 'ctx, 'load_ctx, T: Prototypical, L: Loader> { + context: &'a ProtoLoadContext<'ctx, 'load_ctx, T, L>, } -impl<'a, 'ctx, 'load_ctx, T: Prototypical> ProtoPathListDeserializer<'a, 'ctx, 'load_ctx, T> { - pub fn new(context: &'a ProtoLoadContext<'ctx, 'load_ctx, T>) -> Self { +impl<'a, 'ctx, 'load_ctx, T: Prototypical, L: Loader> + ProtoPathListDeserializer<'a, 'ctx, 'load_ctx, T, L> +{ + pub fn new(context: &'a ProtoLoadContext<'ctx, 'load_ctx, T, L>) -> Self { Self { context } } } -impl<'a, 'ctx, 'load_ctx, 'de, T: Prototypical> DeserializeSeed<'de> - for ProtoPathListDeserializer<'a, 'ctx, 'load_ctx, T> +impl<'a, 'ctx, 'load_ctx, 'de, T: Prototypical, L: Loader> DeserializeSeed<'de> + for ProtoPathListDeserializer<'a, 'ctx, 'load_ctx, T, L> { type Value = Vec; @@ -27,12 +29,12 @@ impl<'a, 'ctx, 'load_ctx, 'de, T: Prototypical> DeserializeSeed<'de> where D: Deserializer<'de>, { - struct PathListVisitor<'a, 'ctx, 'load_ctx, T: Prototypical> { - context: &'a ProtoLoadContext<'ctx, 'load_ctx, T>, + struct PathListVisitor<'a, 'ctx, 'load_ctx, T: Prototypical, L: Loader> { + context: &'a ProtoLoadContext<'ctx, 'load_ctx, T, L>, } - impl<'a, 'ctx, 'load_ctx, 'de, T: Prototypical> Visitor<'de> - for PathListVisitor<'a, 'ctx, 'load_ctx, T> + impl<'a, 'ctx, 'load_ctx, 'de, T: Prototypical, L: Loader> Visitor<'de> + for PathListVisitor<'a, 'ctx, 'load_ctx, T, L> { type Value = Vec; diff --git a/bevy_proto_backend/src/plugin.rs b/bevy_proto_backend/src/plugin.rs index 454b1b8..2772d1d 100644 --- a/bevy_proto_backend/src/plugin.rs +++ b/bevy_proto_backend/src/plugin.rs @@ -2,46 +2,49 @@ use std::marker::PhantomData; use bevy::app::{App, Plugin}; use bevy::asset::AddAsset; +use bevy::prelude::FromWorld; use parking_lot::Mutex; use crate::impls; -use crate::load::ProtoLoader; -use crate::proto::{ProtoAsset, ProtoAssetEvent, ProtoStorage, Prototypical}; +use crate::load::{Loader, ProtoAssetLoader}; +use crate::proto::{Config, ProtoAsset, ProtoAssetEvent, ProtoStorage, Prototypical}; use crate::registration::{on_proto_asset_event, ProtoRegistry}; use crate::tree::{AccessOp, ChildAccess, EntityAccess, ProtoEntity}; -/// Plugin to add support for the given [prototype] `T`. +/// Plugin to add support for the given [prototype] `P`. /// /// [prototype]: Prototypical -pub struct ProtoBackendPlugin { - config: Mutex>, +pub struct ProtoBackendPlugin, C: Config> { + config: Mutex>, + loader: Mutex>, _phantom: PhantomData, } -impl ProtoBackendPlugin { - pub fn with_config(mut self, config: T::Config) -> Self { - self.config = Mutex::new(Some(config)); - self - } -} - -impl Default for ProtoBackendPlugin { - fn default() -> Self { +impl, C: Config> ProtoBackendPlugin { + pub fn new() -> Self { Self { config: Mutex::new(None), + loader: Mutex::new(None), _phantom: Default::default(), } } + + /// Add a custom [`Config`] to the plugin. + pub fn with_config(mut self, config: C) -> Self { + self.config = Mutex::new(Some(config)); + self + } + + /// Add a custom [`Loader`] to the plugin. + pub fn with_loader(mut self, loader: L) -> Self { + self.loader = Mutex::new(Some(loader)); + self + } } -impl Plugin for ProtoBackendPlugin { +impl, C: Config> Plugin for ProtoBackendPlugin { fn build(&self, app: &mut App) { - if let Some(config) = self.config.try_lock().and_then(|mut config| config.take()) { - app.insert_resource(config); - } else { - app.init_resource::(); - } - + // === Types === // #[cfg(feature = "bevy_render")] app.register_type::(); @@ -49,14 +52,39 @@ impl Plugin for ProtoBackendPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() - .init_resource::>() - .init_resource::>() - .init_asset_loader::>() - .add_asset::() - .add_event::>() - .add_system(on_proto_asset_event::); - + .register_type::(); impls::register_impls(app); + + // === Resources === // + if let Some(config) = self.config.lock().take() { + app.insert_resource(config); + } else { + app.init_resource::(); + } + + app.init_resource::>() + .init_resource::>(); + + // === Assets === // + let loader = self + .loader + .lock() + .take() + .unwrap_or_else(|| ::from_world(&mut app.world)); + let asset_loader = ProtoAssetLoader::::new(loader, &mut app.world); + + app.add_asset_loader(asset_loader).add_asset::(); + + // === Events === // + app.add_event::>(); + + // === Systems === // + app.add_system(on_proto_asset_event::); + } +} + +impl, C: Config> Default for ProtoBackendPlugin { + fn default() -> Self { + Self::new() } } diff --git a/bevy_proto_backend/src/proto/commands.rs b/bevy_proto_backend/src/proto/commands.rs index ea58941..94243d4 100644 --- a/bevy_proto_backend/src/proto/commands.rs +++ b/bevy_proto_backend/src/proto/commands.rs @@ -13,19 +13,19 @@ use crate::tree::EntityTreeNode; /// /// [prototypes]: Prototypical #[derive(SystemParam)] -pub struct ProtoCommands<'w, 's, T: Prototypical> { +pub struct ProtoCommands<'w, 's, T: Prototypical, C: Config> { commands: Commands<'w, 's>, #[system_param(ignore)] - _phantom: PhantomData, + _phantom: PhantomData<(T, C)>, } -impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { +impl<'w, 's, T: Prototypical, C: Config> ProtoCommands<'w, 's, T, C> { /// Spawn the prototype with the given [ID]. /// /// This internally calls [`Commands::spawn`]. /// /// [ID]: Prototypical::id - pub fn spawn>(&mut self, id: I) -> ProtoEntityCommands<'w, 's, '_, T> { + pub fn spawn>(&mut self, id: I) -> ProtoEntityCommands<'w, 's, '_, T, C> { let mut entity = ProtoEntityCommands::new(self.commands.spawn_empty().id(), self); entity.insert(id); entity @@ -34,7 +34,7 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { /// Spawn an empty entity. /// /// This internally calls [`Commands::spawn_empty`]. - pub fn spawn_empty(&mut self) -> ProtoEntityCommands<'w, 's, '_, T> { + pub fn spawn_empty(&mut self) -> ProtoEntityCommands<'w, 's, '_, T, C> { ProtoEntityCommands::new(self.commands.spawn_empty().id(), self) } @@ -44,10 +44,10 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { /// To spawn this prototype as a new entity, use [`spawn`] instead. /// /// [ID]: Prototypical::id - /// [require an entity]: Prototypical::require_entity + /// [require an entity]: Prototypical::requires_entity /// [`spawn`]: Self::spawn pub fn apply>(&mut self, id: I) { - self.add(ProtoInsertCommand::::new(id.into(), None)); + self.add(ProtoInsertCommand::::new(id.into(), None)); } /// Remove the prototype with the given [ID] from the world. @@ -56,9 +56,9 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { /// To remove this prototype from an entity, use [`ProtoEntityCommands::remove`] instead. /// /// [ID]: Prototypical::id - /// [require an entity]: Prototypical::require_entity + /// [require an entity]: Prototypical::requires_entity pub fn remove>(&mut self, id: I) { - self.add(ProtoInsertCommand::::new(id.into(), None)); + self.add(ProtoInsertCommand::::new(id.into(), None)); } /// Get the [`ProtoEntityCommands`] for the given entity. @@ -74,7 +74,7 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { /// [`get_entity`] for the non-panicking version. /// /// [`get_entity`]: Self::get_entity - pub fn entity(&mut self, entity: Entity) -> ProtoEntityCommands<'w, 's, '_, T> { + pub fn entity(&mut self, entity: Entity) -> ProtoEntityCommands<'w, 's, '_, T, C> { ProtoEntityCommands::new(self.commands.entity(entity).id(), self) } @@ -87,7 +87,7 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { /// [`entity`] for the panicking version. /// /// [`entity`]: Self::entity - pub fn get_entity(&mut self, entity: Entity) -> Option> { + pub fn get_entity(&mut self, entity: Entity) -> Option> { self.commands .get_entity(entity) .as_ref() @@ -99,7 +99,7 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { /// spawning a new one if it doesn't exist. /// /// This internally calls [`Commands::get_or_spawn`]. - pub fn get_or_spawn(&mut self, entity: Entity) -> ProtoEntityCommands<'w, 's, '_, T> { + pub fn get_or_spawn(&mut self, entity: Entity) -> ProtoEntityCommands<'w, 's, '_, T, C> { ProtoEntityCommands::new(self.commands.get_or_spawn(entity).id(), self) } @@ -108,7 +108,7 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { &mut self.commands } - fn add(&mut self, command: C) { + fn add(&mut self, command: Cmd) { self.commands.add(command); } } @@ -116,13 +116,13 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> { /// A struct similar to [`EntityCommands`], but catered towards [prototypes]. /// /// [prototypes]: Prototypical -pub struct ProtoEntityCommands<'w, 's, 'a, T: Prototypical> { +pub struct ProtoEntityCommands<'w, 's, 'a, T: Prototypical, C: Config> { entity: Entity, - proto_commands: &'a mut ProtoCommands<'w, 's, T>, + proto_commands: &'a mut ProtoCommands<'w, 's, T, C>, } -impl<'w, 's, 'a, T: Prototypical> ProtoEntityCommands<'w, 's, 'a, T> { - fn new(entity: Entity, proto_commands: &'a mut ProtoCommands<'w, 's, T>) -> Self { +impl<'w, 's, 'a, T: Prototypical, C: Config> ProtoEntityCommands<'w, 's, 'a, T, C> { + fn new(entity: Entity, proto_commands: &'a mut ProtoCommands<'w, 's, T, C>) -> Self { Self { entity, proto_commands, @@ -140,7 +140,7 @@ impl<'w, 's, 'a, T: Prototypical> ProtoEntityCommands<'w, 's, 'a, T> { pub fn insert>(&mut self, id: I) -> &mut Self { let id = id.into(); self.proto_commands - .add(ProtoInsertCommand::::new(id, Some(self.entity))); + .add(ProtoInsertCommand::::new(id, Some(self.entity))); self } @@ -150,12 +150,12 @@ impl<'w, 's, 'a, T: Prototypical> ProtoEntityCommands<'w, 's, 'a, T> { pub fn remove>(&mut self, id: I) -> &mut Self { let id = id.into(); self.proto_commands - .add(ProtoRemoveCommand::::new(id, Some(self.entity))); + .add(ProtoRemoveCommand::::new(id, Some(self.entity))); self } /// Returns the underlying [`ProtoCommands`]. - pub fn commands(&mut self) -> &mut ProtoCommands<'w, 's, T> { + pub fn commands(&mut self) -> &mut ProtoCommands<'w, 's, T, C> { self.proto_commands } @@ -169,19 +169,23 @@ impl<'w, 's, 'a, T: Prototypical> ProtoEntityCommands<'w, 's, 'a, T> { /// /// [command]: Command /// [prototype]: Prototypical -pub struct ProtoInsertCommand { - data: ProtoCommandData, +pub struct ProtoInsertCommand> { + data: ProtoCommandData, } -impl ProtoInsertCommand { +impl> ProtoInsertCommand { pub fn new(id: T::Id, entity: Option) -> Self { Self { - data: ProtoCommandData { id, entity }, + data: ProtoCommandData { + id, + entity, + _phantom: PhantomData, + }, } } } -impl Command for ProtoInsertCommand { +impl> Command for ProtoInsertCommand { fn write(self, world: &mut World) { self.data.assert_is_registered(world); @@ -196,19 +200,23 @@ impl Command for ProtoInsertCommand { /// /// [command]: Command /// [prototype]: Prototypical -pub struct ProtoRemoveCommand { - data: ProtoCommandData, +pub struct ProtoRemoveCommand> { + data: ProtoCommandData, } -impl ProtoRemoveCommand { +impl> ProtoRemoveCommand { pub fn new(id: T::Id, entity: Option) -> Self { Self { - data: ProtoCommandData { id, entity }, + data: ProtoCommandData { + id, + entity, + _phantom: PhantomData, + }, } } } -impl Command for ProtoRemoveCommand { +impl> Command for ProtoRemoveCommand { fn write(self, world: &mut World) { self.data.assert_is_registered(world); @@ -219,15 +227,16 @@ impl Command for ProtoRemoveCommand { } } -struct ProtoCommandData { +struct ProtoCommandData> { id: T::Id, entity: Option, + _phantom: PhantomData, } -impl ProtoCommandData { +impl> ProtoCommandData { /// Asserts that the given prototype is registered, panicking if it isn't. fn assert_is_registered(&self, world: &World) { - let registry = world.resource::>(); + let registry = world.resource::>(); let is_registered = registry.contains(&self.id); if !is_registered { @@ -247,10 +256,10 @@ impl ProtoCommandData { fn for_each_entity(&self, world: &mut World, is_apply: bool, callback: F) where - F: Fn(&EntityTreeNode, &mut SchematicContext, &Assets, &mut T::Config), + F: Fn(&EntityTreeNode, &mut SchematicContext, &Assets, &mut C), { - world.resource_scope(|world: &mut World, registry: Mut>| { - world.resource_scope(|world: &mut World, mut config: Mut| { + world.resource_scope(|world: &mut World, registry: Mut>| { + world.resource_scope(|world: &mut World, mut config: Mut| { world.resource_scope(|world, prototypes: Mut>| { let entity_tree = registry .get_tree_by_id(&self.id) diff --git a/bevy_proto_backend/src/proto/config.rs b/bevy_proto_backend/src/proto/config.rs index 67ce732..81128ef 100644 --- a/bevy_proto_backend/src/proto/config.rs +++ b/bevy_proto_backend/src/proto/config.rs @@ -14,35 +14,6 @@ use crate::schematics::{DynamicSchematic, SchematicContext}; /// [`ProtoBackendPlugin`]: crate::ProtoBackendPlugin #[allow(unused_variables)] pub trait Config: Resource + FromWorld { - /// A list of supported extensions. - /// - /// Extensions should be in order of most-specific to least specific, - /// and should not be prepended by a dot (`.`). - /// Generally, this means it should be in order of longest to shortest. - /// - /// For example, this could return: - /// - /// ``` - /// # use bevy::prelude::Resource; - /// # use bevy_proto_backend::proto::{Config, Prototypical}; - /// # #[derive(Default)] - /// struct Foo; - /// # impl Resource for Foo {} - /// impl Config for Foo { - /// fn extensions(&self) -> Box<[&'static str]> { - /// vec![ - /// // Most Specific (Longest) // - /// "prototype.yaml", - /// "prototype.ron", - /// "yaml" - /// "ron", - /// // Least Specific (Shortest) // - /// ].into_boxed_slice() - /// } - /// } - /// ``` - fn extensions(&self) -> Box<[&'static str]>; - /// Callback method that's triggered when a [prototype] is registered. /// /// Prototypes are registered when they are first loaded. diff --git a/bevy_proto_backend/src/proto/prototypes.rs b/bevy_proto_backend/src/proto/prototypes.rs index 0a62c39..5e344e2 100644 --- a/bevy_proto_backend/src/proto/prototypes.rs +++ b/bevy_proto_backend/src/proto/prototypes.rs @@ -7,7 +7,7 @@ use std::hash::Hash; use std::path::{Path, PathBuf}; use thiserror::Error; -use crate::proto::{ProtoStorage, Prototypical}; +use crate::proto::{Config, ProtoStorage, Prototypical}; use crate::registration::ProtoRegistry; #[derive(Debug, Error)] @@ -23,9 +23,9 @@ pub enum ProtoLoadError { /// /// [prototypes]: Prototypical #[derive(SystemParam)] -pub struct Prototypes<'w, T: Prototypical> { - registry: Res<'w, ProtoRegistry>, - config: Res<'w, ::Config>, +pub struct Prototypes<'w, T: Prototypical, C: Config> { + registry: Res<'w, ProtoRegistry>, + config: Res<'w, C>, asset_server: Res<'w, AssetServer>, storage: Res<'w, ProtoStorage>, } @@ -36,14 +36,14 @@ pub struct Prototypes<'w, T: Prototypical> { /// /// [prototypes]: Prototypical #[derive(SystemParam)] -pub struct PrototypesMut<'w, T: Prototypical> { - registry: Res<'w, ProtoRegistry>, - config: ResMut<'w, ::Config>, +pub struct PrototypesMut<'w, T: Prototypical, C: Config> { + registry: Res<'w, ProtoRegistry>, + config: ResMut<'w, C>, asset_server: Res<'w, AssetServer>, storage: ResMut<'w, ProtoStorage>, } -impl<'w, T: Prototypical> PrototypesMut<'w, T> { +impl<'w, T: Prototypical, C: Config> PrototypesMut<'w, T, C> { /// Load the prototype at the given path. /// /// This will also store a strong handle to the prototype in order to keep it loaded. @@ -100,15 +100,15 @@ impl<'w, T: Prototypical> PrototypesMut<'w, T> { /// Returns a mutable reference to the [`Config`] resource. /// - /// [`Config`]: crate::proto::Config - pub fn config_mut(&mut self) -> &mut T::Config { + /// [`Config`]: Config + pub fn config_mut(&mut self) -> &mut C { &mut self.config } } macro_rules! impl_prototypes { ($ident: ident) => { - impl<'w, T: Prototypical> $ident<'w, T> { + impl<'w, T: Prototypical, C: Config> $ident<'w, T, C> { /// Returns the [`LoadState`] for the prototype with the given [`HandleId`]. /// /// This method is preferred over [`AssetServer::get_load_state`] as it better @@ -171,8 +171,8 @@ macro_rules! impl_prototypes { /// Returns a reference to the [`Config`] resource. /// - /// [`Config`]: crate::proto::Config - pub fn config(&self) -> &T::Config { + /// [`Config`]: Config + pub fn config(&self) -> &C { &self.config } } diff --git a/bevy_proto_backend/src/proto/prototypical.rs b/bevy_proto_backend/src/proto/prototypical.rs index 9025f29..6486a2f 100644 --- a/bevy_proto_backend/src/proto/prototypical.rs +++ b/bevy_proto_backend/src/proto/prototypical.rs @@ -1,4 +1,3 @@ -use std::error::Error; use std::fmt::Debug; use std::hash::Hash; @@ -6,10 +5,8 @@ use bevy::asset::Asset; use crate::children::{Children, PrototypicalChild}; use crate::deps::Dependencies; -use crate::load::ProtoLoadContext; use crate::path::ProtoPath; -use crate::proto::config::Config; -use crate::schematics::{SchematicError, Schematics}; +use crate::schematics::Schematics; use crate::templates::Templates; /// The trait used to define a prototype. @@ -44,10 +41,6 @@ pub trait Prototypical: Asset + Sized { /// /// This is used to configure how a child is processed. type Child: PrototypicalChild; - /// The configuration type used for managing this prototype. - type Config: Config; - /// The error type used for deserializing. - type Error: Error + From + Send + Sync; /// The ID of this prototype. fn id(&self) -> &Self::Id; @@ -75,6 +68,4 @@ pub trait Prototypical: Asset + Sized { fn children(&self) -> Option<&Children>; /// A mutable reference to the collection of [`Children`] contained in this prototype, if any. fn children_mut(&mut self) -> Option<&mut Children>; - /// Deserialize the given slice of bytes into an instance of [`Self`]. - fn deserialize(bytes: &[u8], ctx: &mut ProtoLoadContext) -> Result; } diff --git a/bevy_proto_backend/src/registration/manager.rs b/bevy_proto_backend/src/registration/manager.rs index ee21f21..aa64dff 100644 --- a/bevy_proto_backend/src/registration/manager.rs +++ b/bevy_proto_backend/src/registration/manager.rs @@ -2,7 +2,7 @@ use bevy::asset::Handle; use bevy::ecs::system::SystemParam; use bevy::prelude::ResMut; -use crate::proto::{ProtoError, Prototypical}; +use crate::proto::{Config, ProtoError, Prototypical}; use crate::registration::params::RegistryParams; use crate::registration::ProtoRegistry; @@ -12,12 +12,12 @@ use crate::registration::ProtoRegistry; /// /// [prototypes]: Prototypical #[derive(SystemParam)] -pub(crate) struct ProtoManager<'w, T: Prototypical> { - registry: ResMut<'w, ProtoRegistry>, - registry_params: RegistryParams<'w, T>, +pub(crate) struct ProtoManager<'w, T: Prototypical, C: Config> { + registry: ResMut<'w, ProtoRegistry>, + registry_params: RegistryParams<'w, T, C>, } -impl<'w, T: Prototypical> ProtoManager<'w, T> { +impl<'w, T: Prototypical, C: Config> ProtoManager<'w, T, C> { pub fn register(&mut self, handle: &Handle) -> Result<&'w T, ProtoError> { self.registry.register(handle, &mut self.registry_params) } diff --git a/bevy_proto_backend/src/registration/params.rs b/bevy_proto_backend/src/registration/params.rs index 986ef28..6382178 100644 --- a/bevy_proto_backend/src/registration/params.rs +++ b/bevy_proto_backend/src/registration/params.rs @@ -1,25 +1,25 @@ -use crate::proto::{ProtoAssetEvent, ProtoError, Prototypical}; +use crate::proto::{Config, ProtoAssetEvent, ProtoError, Prototypical}; use bevy::asset::{Assets, Handle, HandleId}; use bevy::ecs::system::SystemParam; use bevy::prelude::{EventWriter, Res, ResMut}; #[derive(SystemParam)] -pub(super) struct RegistryParams<'w, T: Prototypical> { +pub(super) struct RegistryParams<'w, T: Prototypical, C: Config> { prototypes: Res<'w, Assets>, - config: ResMut<'w, ::Config>, + config: ResMut<'w, C>, proto_events: EventWriter<'w, ProtoAssetEvent>, } -impl<'w, T: Prototypical> RegistryParams<'w, T> { +impl<'w, T: Prototypical, C: Config> RegistryParams<'w, T, C> { pub fn prototypes(&self) -> &'w Assets { Res::clone(&self.prototypes).into_inner() } - pub fn config(&self) -> &T::Config { + pub fn config(&self) -> &C { &self.config } - pub fn config_mut(&mut self) -> &mut T::Config { + pub fn config_mut(&mut self) -> &mut C { &mut self.config } diff --git a/bevy_proto_backend/src/registration/registry.rs b/bevy_proto_backend/src/registration/registry.rs index 50eb6fa..b1d2024 100644 --- a/bevy_proto_backend/src/registration/registry.rs +++ b/bevy_proto_backend/src/registration/registry.rs @@ -1,5 +1,6 @@ use std::borrow::Borrow; use std::hash::Hash; +use std::marker::PhantomData; use std::sync::Arc; use crate::registration::params::RegistryParams; @@ -14,7 +15,7 @@ use crate::tree::{ProtoTree, ProtoTreeBuilder}; /// Resource used to track load states, store mappings, and generate cached data. #[derive(Resource)] -pub(crate) struct ProtoRegistry { +pub(crate) struct ProtoRegistry> { ids: HashMap, handles: HashMap>, trees: HashMap>, @@ -28,16 +29,17 @@ pub(crate) struct ProtoRegistry { load_queue: Arc>>, /// Set of prototypes that failed to be registered. failed: HashSet, + _phantom: PhantomData, } -impl ProtoRegistry { +impl> ProtoRegistry { /// Registers a prototype. /// /// This will return an error if the prototype is already registered. pub(super) fn register<'w>( &mut self, handle: &Handle, - params: &mut RegistryParams<'w, T>, + params: &mut RegistryParams<'w, T, C>, ) -> Result<&'w T, ProtoError> { let prototype = self.register_internal(handle, params, false)?; @@ -59,7 +61,7 @@ impl ProtoRegistry { pub(super) fn unregister( &mut self, handle: &Handle, - params: &mut RegistryParams, + params: &mut RegistryParams, ) -> Option { let id = self.unregister_internal(handle, params)?; @@ -82,7 +84,7 @@ impl ProtoRegistry { pub(super) fn reload<'w>( &mut self, handle: &Handle, - params: &mut RegistryParams<'w, T>, + params: &mut RegistryParams<'w, T, C>, ) -> Result<&'w T, ProtoError> { if self.unregister_internal(handle, params).is_some() { let prototype = self.register_internal(handle, params, true)?; @@ -145,7 +147,7 @@ impl ProtoRegistry { fn register_internal<'w>( &mut self, handle: &Handle, - params: &mut RegistryParams<'w, T>, + params: &mut RegistryParams<'w, T, C>, is_reload: bool, ) -> Result<&'w T, ProtoError> { let handle = params.get_strong_handle(handle); @@ -192,7 +194,7 @@ impl ProtoRegistry { fn unregister_internal( &mut self, handle: &Handle, - params: &mut RegistryParams, + params: &mut RegistryParams, ) -> Option { let handle_id = handle.id(); @@ -216,7 +218,7 @@ impl ProtoRegistry { } } -impl Default for ProtoRegistry { +impl> Default for ProtoRegistry { fn default() -> Self { Self { ids: HashMap::new(), @@ -225,6 +227,7 @@ impl Default for ProtoRegistry { dependents: HashMap::new(), load_queue: Default::default(), failed: HashSet::new(), + _phantom: PhantomData, } } } diff --git a/bevy_proto_backend/src/registration/systems.rs b/bevy_proto_backend/src/registration/systems.rs index ff50106..dd365e9 100644 --- a/bevy_proto_backend/src/registration/systems.rs +++ b/bevy_proto_backend/src/registration/systems.rs @@ -1,13 +1,13 @@ use bevy::asset::AssetEvent; use bevy::prelude::{error, EventReader}; -use crate::proto::Prototypical; +use crate::proto::{Config, Prototypical}; use crate::registration::ProtoManager; /// Handles the registration of loaded, modified, and removed prototypes. -pub(crate) fn on_proto_asset_event( +pub(crate) fn on_proto_asset_event>( mut events: EventReader>, - mut manager: ProtoManager, + mut manager: ProtoManager, ) { for event in events.iter() { match event { diff --git a/bevy_proto_backend/src/schematics/collection.rs b/bevy_proto_backend/src/schematics/collection.rs index 6ba9a34..bf26dfb 100644 --- a/bevy_proto_backend/src/schematics/collection.rs +++ b/bevy_proto_backend/src/schematics/collection.rs @@ -1,10 +1,10 @@ use std::borrow::Cow; use std::fmt::{Debug, Formatter}; -use bevy::utils::hashbrown::hash_map::{Iter, IterMut}; +use bevy::utils::hashbrown::hash_map::{IntoIter, Iter, IterMut}; use bevy::utils::HashMap; -use crate::schematics::DynamicSchematic; +use crate::schematics::{DynamicSchematic, Schematic}; /// A collection of [schematics] for a [prototype]. /// @@ -31,37 +31,64 @@ impl Schematics { Self(HashMap::with_capacity(capacity)) } + /// Returns true if the given schematic is contained. + pub fn contains(&self) -> bool { + self.0.contains_key(std::any::type_name::()) + } + /// Returns true if the given [type name] of a schematic is contained. /// /// [type name]: std::any::type_name - pub fn contains(&self, key: &str) -> bool { + pub fn contains_by_name(&self, key: &str) -> bool { self.0.contains_key(key) } + /// Get a reference to the given schematic. + pub fn get(&self) -> Option<&DynamicSchematic> { + self.0.get(std::any::type_name::()) + } + /// Get a reference to the schematic with the given [type name]. /// /// [type name]: std::any::type_name - pub fn get(&self, key: &str) -> Option<&DynamicSchematic> { + pub fn get_by_name(&self, key: &str) -> Option<&DynamicSchematic> { self.0.get(key) } + /// Get a mutable reference to the given schematic. + pub fn get_mut(&mut self) -> Option<&mut DynamicSchematic> { + self.0.get_mut(std::any::type_name::()) + } + /// Get a mutable reference to the schematic with the given [type name]. /// /// [type name]: std::any::type_name - pub fn get_mut(&mut self, key: &str) -> Option<&mut DynamicSchematic> { + pub fn get_mut_by_name(&mut self, key: &str) -> Option<&mut DynamicSchematic> { self.0.get_mut(key) } /// Insert a new schematic. - pub fn insert(&mut self, schematic: DynamicSchematic) -> Option { + pub fn insert(&mut self, input: T::Input) -> Option { + let schematic = DynamicSchematic::new::(input); + let key = Cow::Borrowed(std::any::type_name::()); + self.0.insert(key, schematic) + } + + /// Insert a new schematic dynamically. + pub fn insert_dynamic(&mut self, schematic: DynamicSchematic) -> Option { let key = Cow::Borrowed(schematic.type_info().type_name()); self.0.insert(key, schematic) } + /// Remove the given schematic. + pub fn remove(&mut self) -> Option { + self.0.remove(std::any::type_name::()) + } + /// Remove the schematic with the given [type name]. /// /// [type name]: std::any::type_name - pub fn remove(&mut self, key: &str) -> Option { + pub fn remove_by_name(&mut self, key: &str) -> Option { self.0.remove(key) } @@ -93,3 +120,18 @@ impl Debug for Schematics { write!(f, ")") } } + +impl FromIterator<(Cow<'static, str>, DynamicSchematic)> for Schematics { + fn from_iter, DynamicSchematic)>>(iter: T) -> Self { + Self(HashMap::from_iter(iter)) + } +} + +impl IntoIterator for Schematics { + type Item = (Cow<'static, str>, DynamicSchematic); + type IntoIter = IntoIter, DynamicSchematic>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/bevy_proto_backend/src/schematics/dynamic.rs b/bevy_proto_backend/src/schematics/dynamic.rs index 862638b..c5f0e40 100644 --- a/bevy_proto_backend/src/schematics/dynamic.rs +++ b/bevy_proto_backend/src/schematics/dynamic.rs @@ -22,13 +22,27 @@ pub struct DynamicSchematic { } impl DynamicSchematic { - /// Get the reflected [schematic input] data. + pub fn new(input: T::Input) -> Self { + Self { + input: Box::new(input), + reflect_schematic: >::from_type(), + } + } + + /// Get a reference to the reflected [schematic input] data. /// /// [schematic input]: Schematic::Input pub fn input(&self) -> &dyn Reflect { &*self.input } + /// Get a mutable reference to the reflected [schematic input] data. + /// + /// [schematic input]: Schematic::Input + pub fn input_mut(&mut self) -> &mut dyn Reflect { + &mut *self.input + } + /// Dynamically call the corresponding [`Schematic::apply`] method. pub fn apply(&self, context: &mut SchematicContext) -> Result<(), SchematicError> { (self.reflect_schematic.apply)(&*self.input, context) @@ -121,7 +135,7 @@ impl ReflectSchematic { } } -impl FromType for ReflectSchematic { +impl FromType for ReflectSchematic { fn from_type() -> Self { Self { type_info: ::type_info(), diff --git a/bevy_proto_backend/src/schematics/schematic.rs b/bevy_proto_backend/src/schematics/schematic.rs index 9c5426b..8d93327 100644 --- a/bevy_proto_backend/src/schematics/schematic.rs +++ b/bevy_proto_backend/src/schematics/schematic.rs @@ -1,5 +1,5 @@ use bevy::prelude::{FromReflect, Reflect}; -use bevy::reflect::GetTypeRegistration; +use bevy::reflect::{GetTypeRegistration, Typed}; use crate::deps::DependenciesBuilder; use crate::schematics::SchematicContext; @@ -37,7 +37,7 @@ use crate::schematics::SchematicContext; /// [world]: bevy::ecs::world::World /// [derived]: bevy_proto_derive::Schematic /// [module-level documentation]: crate::schematics -pub trait Schematic: Reflect { +pub trait Schematic: Reflect + Typed { /// The input type to this schematic. /// /// This acts as an intermediate between serialized schematic information diff --git a/bevy_proto_backend/src/tree/builder.rs b/bevy_proto_backend/src/tree/builder.rs index 1ed1212..320e871 100644 --- a/bevy_proto_backend/src/tree/builder.rs +++ b/bevy_proto_backend/src/tree/builder.rs @@ -10,17 +10,17 @@ use crate::templates::Templates; use crate::tree::ProtoTree; /// Cache object used to create [`ProtoTree`] objects. -pub(crate) struct ProtoTreeBuilder<'a, T: Prototypical> { - registry: &'a mut ProtoRegistry, +pub(crate) struct ProtoTreeBuilder<'a, T: Prototypical, C: Config> { + registry: &'a mut ProtoRegistry, prototypes: &'a Assets, - config: &'a T::Config, + config: &'a C, } -impl<'a, T: Prototypical> ProtoTreeBuilder<'a, T> { +impl<'a, T: Prototypical, C: Config> ProtoTreeBuilder<'a, T, C> { pub fn new( - registry: &'a mut ProtoRegistry, + registry: &'a mut ProtoRegistry, prototypes: &'a Assets, - config: &'a T::Config, + config: &'a C, ) -> Self { Self { registry, diff --git a/examples/README.md b/examples/README.md index ec3f4ec..6ba6aa6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -15,6 +15,8 @@ The recommended order is: 6. [custom_schematic.rs](./custom_schematic.rs) - Creating a custom `Schematic`implementation 7. [hierarchy.rs](./hierarchy.rs) - Defining complex prototype hierarchies with children 8. [cycles.rs](./cycles.rs) - How prototype cycles are handled +9. [custom_loader.rs](./custom_loader.rs) - How to create a custom loader +10. [custom_config.rs](./custom_config.rs) - How to create a custom config ### Bevy Examples diff --git a/examples/basic_schematic.rs b/examples/basic_schematic.rs index 1b3568a..f78834f 100644 --- a/examples/basic_schematic.rs +++ b/examples/basic_schematic.rs @@ -17,7 +17,7 @@ fn main() { .register_type::() .register_type_data::() // =============== // - .add_plugin(ProtoPlugin::default()) + .add_plugin(ProtoPlugin::new()) .add_startup_systems((setup, load)) .add_systems(( spawn.run_if( diff --git a/examples/bevy/ui.rs b/examples/bevy/ui.rs index ec6b185..0cb5d48 100644 --- a/examples/bevy/ui.rs +++ b/examples/bevy/ui.rs @@ -21,7 +21,7 @@ fn main() { watch_for_changes: true, ..default() })) - .add_plugin(ProtoPlugin::default()) + .add_plugin(ProtoPlugin::new()) // The original example sets the update mode to `desktop_app`, // but this doesn't play nice with hot-reloading. // .insert_resource(bevy::winit::WinitSettings::desktop_app()) diff --git a/examples/custom_config.rs b/examples/custom_config.rs new file mode 100644 index 0000000..14de21b --- /dev/null +++ b/examples/custom_config.rs @@ -0,0 +1,100 @@ +//! This example demonstrates how to implement a custom config. +//! +//! Configs are used to define certain aspects of prototype registration +//! as well as handle various callbacks during the life cycle of a prototype. +//! +//! Note that you don't need to create a custom config to do most things. +//! Instead, you can just define your logic on the default [`ProtoConfig`]. +//! +//! Custom configs are mainly useful if you want to store or access data +//! during one of the callbacks. + +use bevy::prelude::*; + +use bevy_proto::backend::proto::Config; +use bevy_proto::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + // =============== // + // Be sure to add `ProtoPlugin` with the custom config: + .add_plugin(ProtoPlugin::new_with_config(MyConfig::default())) + // Note: If you don't need to define your own type/resource, + // you can also just add some callbacks to the default `ProtoConfig`: + // + // .add_plugin( + // ProtoPlugin::new().with_config(ProtoConfig::default().on_register_prototype(Box::new( + // |prototype, _handle| { + // println!("Registered: {:?}", prototype.id()); + // }, + // ))), + // ) + // + // =============== // + .add_startup_system(load) + .add_systems(( + // =============== // + // For custom configs we also need to use `ProtoCondition::::prototype_ready` + // instead of the default `prototype_ready` for our run condition: + spawn + .run_if(ProtoCondition::::prototype_ready("Player").and_then(run_once())), + // =============== // + on_config_change, + inspect, + )) + .run(); +} + +/// Our own custom config. +#[derive(Resource, Default)] +struct MyConfig { + is_changed: bool, + last_registered: Option>, +} + +impl Config for MyConfig { + // ============================= // + // Define custom callbacks here. + // ============================= // + + fn on_register_prototype(&mut self, prototype: &Prototype, handle: Handle) { + // For example, we can print the ID of every prototype that's registered: + println!("Registered: {:?}", prototype.id()); + + // And since this is a resource, we can store whatever we want on it: + self.last_registered = Some(handle.clone_weak()); + self.is_changed = true; + } +} + +// By default all bevy_proto system parameters use the standard `ProtoConfig`. +// However, if we want to use our custom config, we need to pass it as a type parameter. +// In other words, we need to use `PrototypesMut` instead of `PrototypesMut`. +fn load(mut prototypes: PrototypesMut) { + prototypes.load("examples/custom_config/Player.prototype.ron"); +} + +// The same applies here: we need to use `ProtoCommands` instead of `ProtoCommands`. +fn spawn(mut commands: ProtoCommands) { + commands.spawn("Player"); +} + +// Our custom config is like any resource and is inserted into the world +// when we add the `ProtoPlugin`. +fn on_config_change(mut config: ResMut) { + if !config.is_changed { + return; + } + + println!("Last Registered: {:?}", config.last_registered); + + config.is_changed = false; +} + +// This relies on the `auto_name` feature to be useful +fn inspect(query: Query>) { + for name in &query { + println!("Spawned: {:?}", name); + } +} diff --git a/examples/custom_loader.rs b/examples/custom_loader.rs new file mode 100644 index 0000000..ba856bc --- /dev/null +++ b/examples/custom_loader.rs @@ -0,0 +1,123 @@ +//! This example demonstrates how to implement a custom loader. +//! +//! Loaders are what actually controls how an asset is loaded and what +//! formats are supported. +//! +//! The example below shows how to implement a custom loader for +//! files ending with the `.custom.yaml` extension. +//! It also shows how we can modify a prototype before it's finalized. +//! +//! It should be noted that bevy_proto already supports YAML files, +//! which can be enabled using the `yaml` feature. + +use bevy::prelude::*; +use serde::de::DeserializeSeed; + +use bevy_proto::backend::load::{Loader, ProtoLoadContext, ProtoLoadMeta}; +use bevy_proto::de::PrototypeDeserializer; +use bevy_proto::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + // =============== // + // Be sure to add `ProtoPlugin` with the custom loader: + .add_plugin(ProtoPlugin::new_with_loader(MyLoader::default())) + // =============== // + .register_type::() + .register_type::() + .register_type::() + .add_startup_system(load) + .add_systems(( + spawn.run_if(prototype_ready("Player").and_then(run_once())), + inspect, + )) + .run(); +} + +/// A custom loader for `.custom.yaml` files. +#[derive(Clone, Default)] +struct MyLoader; + +impl Loader for MyLoader { + /// For simplicity, we'll just reuse bevy_proto's error type. + type Error = PrototypeError; + + fn deserialize( + bytes: &[u8], + ctx: &mut ProtoLoadContext, + ) -> Result { + // To deserialize the default `Prototype`, we can use the `PrototypeDeserializer`: + let deserializer = PrototypeDeserializer::new(ctx); + + // And for our format, we'll use YAML: + deserializer + .deserialize(serde_yaml::Deserializer::from_slice(bytes)) + .map_err(|err| PrototypeError::custom(format_args!("YAML error: {}", err))) + } + + fn extensions(&self) -> &[&'static str] { + // It's important we arrange these from most specific to least specific + // (from shortest to longest), and that they do not start with a dot (`.`). + &["custom.yaml", "yaml"] + } + + fn on_load_prototype( + &self, + mut prototype: Prototype, + meta: &ProtoLoadMeta, + ) -> Result { + // This is where we can process a prototype before it is finalized by the asset loader. + + // For example, we can log the prototype's name and path: + println!("Loaded prototype: {:?} ({:?})", prototype.id(), meta.path); + + // We can modify it in some way: + if prototype.schematics().contains::() { + if let Some(mana) = prototype.schematics_mut().get_mut::() { + mana.input_mut().downcast_mut::().unwrap().0 *= 2; + } else { + prototype.schematics_mut().insert::(Mana(50)); + } + } + + // Or we can reject it if it doesn't meet our requirements: + if prototype.schematics().contains::() + && !prototype.schematics().contains::() + { + return Err(PrototypeError::custom(format_args!( + "Prototype with ID {:?} contains a `Player` component but not a `Health` component!", + prototype.id() + ))); + } + + // Finally, we return the prototype. + Ok(prototype) + } +} + +#[derive(Component, Schematic, Reflect, FromReflect)] +#[reflect(Schematic)] +struct Player; + +#[derive(Component, Schematic, Reflect, FromReflect)] +#[reflect(Schematic)] +struct Health(i32); + +#[derive(Component, Schematic, Reflect, FromReflect)] +#[reflect(Schematic)] +struct Mana(i32); + +fn load(mut prototypes: PrototypesMut) { + prototypes.load("examples/custom_loader/Player.custom.yaml"); +} + +fn spawn(mut commands: ProtoCommands) { + commands.spawn("Player"); +} + +fn inspect(query: Query<(&Health, &Mana), Added>) { + for (health, mana) in &query { + println!("Spawned player with {:?} HP and {:?} MP", health.0, mana.0); + } +} diff --git a/examples/custom_schematic.rs b/examples/custom_schematic.rs index 6071ccb..b254e4f 100644 --- a/examples/custom_schematic.rs +++ b/examples/custom_schematic.rs @@ -8,7 +8,7 @@ use bevy_proto::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(ProtoPlugin::default()) + .add_plugin(ProtoPlugin::new()) // =============== // // Make sure to register your types! .register_type::() diff --git a/examples/cycles.rs b/examples/cycles.rs index 06aa82d..3555707 100644 --- a/examples/cycles.rs +++ b/examples/cycles.rs @@ -27,14 +27,14 @@ use bevy_proto_backend::cycles::CycleResponse; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(ProtoPlugin::new(ProtoConfig::default().on_cycle(Box::new( - |cycle| { + .add_plugin( + ProtoPlugin::new().with_config(ProtoConfig::default().on_cycle(Box::new(|cycle| { println!("Handling cycle: {:?}", cycle); // Here we can configure what CycleResponse is returned. // For debug builds, the default behavior is to panic. CycleResponse::Panic - }, - )))) + }))), + ) .add_startup_system(load) .add_systems(( spawn.run_if(prototype_ready("CycleA").and_then(run_once())), diff --git a/examples/hierarchy.rs b/examples/hierarchy.rs index a6d5d1e..ba292fd 100644 --- a/examples/hierarchy.rs +++ b/examples/hierarchy.rs @@ -33,7 +33,7 @@ use bevy_proto_backend::tree::EntityAccess; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(ProtoPlugin::new( + .add_plugin(ProtoPlugin::new().with_config( ProtoConfig::default().on_before_apply_prototype(Box::new(|prototype, context| { // To see the end result of our hierarchy, let's inspect the generated // `EntityTree` for the `Parent` prototype. diff --git a/examples/hot_reload.rs b/examples/hot_reload.rs index 4572143..330c3bd 100644 --- a/examples/hot_reload.rs +++ b/examples/hot_reload.rs @@ -24,7 +24,7 @@ fn main() { watch_for_changes: true, ..default() })) - .add_plugin(ProtoPlugin::default()) + .add_plugin(ProtoPlugin::new()) .add_startup_systems((setup, load)) .add_systems((spawn.run_if(prototype_ready("ReloadableSprite")), inspect)) .run(); diff --git a/examples/loading.rs b/examples/loading.rs index 942b1cf..0945701 100644 --- a/examples/loading.rs +++ b/examples/loading.rs @@ -9,7 +9,7 @@ fn main() { .add_plugins(DefaultPlugins) // The `ProtoPlugin` adds all the necessary systems and resources // to load and spawn prototype assets. - .add_plugin(ProtoPlugin::default()) + .add_plugin(ProtoPlugin::new()) .add_startup_systems((setup, load)) .add_systems(( spawn.run_if(prototype_ready("Player").and_then(run_once())), diff --git a/examples/templates.rs b/examples/templates.rs index d735275..2836eaa 100644 --- a/examples/templates.rs +++ b/examples/templates.rs @@ -29,7 +29,7 @@ use bevy_proto::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(ProtoPlugin::default()) + .add_plugin(ProtoPlugin::new()) .register_type::() .register_type::() .add_startup_systems((load, setup)) diff --git a/src/conditions.rs b/src/conditions.rs index b062988..91774eb 100644 --- a/src/conditions.rs +++ b/src/conditions.rs @@ -1,10 +1,27 @@ +use crate::config::ProtoConfig; use crate::prelude::Prototypes; +use crate::proto::Prototype; +use bevy_proto_backend::proto::Config; +use std::marker::PhantomData; /// Run condition that returns true if the [prototype] with the given /// ID is loaded and ready to be used. /// -/// [prototype]: crate::prelude::Prototype +/// [prototype]: Prototype pub fn prototype_ready(id: I) -> impl Fn(Prototypes<'_>) -> bool { - let id = id.to_string(); - move |prototypes: Prototypes| prototypes.is_ready(&id) + ProtoCondition::prototype_ready(id) +} + +/// A collection of common run conditions for use with custom [`Config`] types. +pub struct ProtoCondition = ProtoConfig>(PhantomData); + +impl> ProtoCondition { + /// Run condition that returns true if the [prototype] with the given + /// ID is loaded and ready to be used. + /// + /// [prototype]: Prototype + pub fn prototype_ready(id: I) -> impl Fn(Prototypes<'_, C>) -> bool { + let id = id.to_string(); + move |prototypes: Prototypes| prototypes.is_ready(&id) + } } diff --git a/src/config.rs b/src/config.rs index 5ee85c5..aae65de 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,9 +18,8 @@ use crate::hooks::{ use crate::proto::Prototype; /// The config resource for [`Prototype`]. -#[derive(Resource)] +#[derive(Resource, Default)] pub struct ProtoConfig { - extensions: Vec<&'static str>, on_register_prototype: Option, on_reload_prototype: Option, on_unregister_prototype: Option, @@ -109,41 +108,7 @@ impl ProtoConfig { } } -impl Default for ProtoConfig { - fn default() -> Self { - let mut extensions = Vec::new(); - - if cfg!(feature = "yaml") { - extensions.push("prototype.yaml"); - } - - if cfg!(feature = "ron") { - extensions.push("prototype.ron"); - } - - Self { - extensions, - on_register_prototype: None, - on_reload_prototype: None, - on_unregister_prototype: None, - on_before_apply_prototype: None, - on_after_apply_prototype: None, - on_before_remove_prototype: None, - on_after_remove_prototype: None, - on_before_apply_schematic: None, - on_after_apply_schematic: None, - on_before_remove_schematic: None, - on_after_remove_schematic: None, - on_cycle: None, - } - } -} - impl Config for ProtoConfig { - fn extensions(&self) -> Box<[&'static str]> { - self.extensions.clone().into_boxed_slice() - } - fn on_register_prototype(&mut self, prototype: &Prototype, handle: Handle) { if let Some(on_register_prototype) = &mut self.on_register_prototype { on_register_prototype(prototype, handle); diff --git a/src/proto/child/de/child.rs b/src/de/child.rs similarity index 79% rename from src/proto/child/de/child.rs rename to src/de/child.rs index 9449c2a..dd59c6a 100644 --- a/src/proto/child/de/child.rs +++ b/src/de/child.rs @@ -4,14 +4,19 @@ use bevy::asset::Handle; use serde::de::{DeserializeSeed, Error, MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; +use crate::de::ProtoChildValueDeserializer; +use crate::loader::ProtoLoader; use bevy_proto_backend::children::ProtoChildBuilder; +use bevy_proto_backend::load::Loader; use bevy_proto_backend::path::ProtoPath; use crate::prelude::Prototype; -use crate::proto::child::de::value::ProtoChildValueDeserializer; -use crate::proto::child::de::{PROTO_CHILD, PROTO_CHILD_MERGE_KEY, PROTO_CHILD_VALUE}; use crate::proto::{ProtoChild, ProtoChildValue}; +pub(super) const PROTO_CHILD: &str = "ProtoChild"; +const PROTO_CHILD_MERGE_KEY: &str = "merge_key"; +const PROTO_CHILD_VALUE: &str = "value"; + #[derive(Deserialize, Debug)] #[serde(field_identifier, rename_all = "snake_case")] enum ProtoChildField { @@ -19,18 +24,18 @@ enum ProtoChildField { Value, } -pub struct ProtoChildDeserializer<'a, 'ctx, 'load_ctx> { - builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>, +pub struct ProtoChildDeserializer<'a, 'ctx, 'load_ctx, L: Loader = ProtoLoader> { + builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>, } -impl<'a, 'ctx, 'load_ctx> ProtoChildDeserializer<'a, 'ctx, 'load_ctx> { - pub fn new(builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>) -> Self { +impl<'a, 'ctx, 'load_ctx, L: Loader> ProtoChildDeserializer<'a, 'ctx, 'load_ctx, L> { + pub fn new(builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>) -> Self { Self { builder } } } -impl<'a, 'ctx, 'load_ctx, 'de> DeserializeSeed<'de> - for ProtoChildDeserializer<'a, 'ctx, 'load_ctx> +impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> DeserializeSeed<'de> + for ProtoChildDeserializer<'a, 'ctx, 'load_ctx, L> { type Value = ProtoChild; @@ -38,10 +43,12 @@ impl<'a, 'ctx, 'load_ctx, 'de> DeserializeSeed<'de> where D: Deserializer<'de>, { - struct ProtoChildVisitor<'a, 'ctx, 'load_ctx> { - pub(crate) builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>, + struct ProtoChildVisitor<'a, 'ctx, 'load_ctx, L: Loader = ProtoLoader> { + pub(crate) builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>, } - impl<'a, 'ctx, 'load_ctx, 'de> Visitor<'de> for ProtoChildVisitor<'a, 'ctx, 'load_ctx> { + impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> Visitor<'de> + for ProtoChildVisitor<'a, 'ctx, 'load_ctx, L> + { type Value = ProtoChild; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { diff --git a/src/proto/child/de/value.rs b/src/de/child_value.rs similarity index 75% rename from src/proto/child/de/value.rs rename to src/de/child_value.rs index 152bd65..98c0007 100644 --- a/src/proto/child/de/value.rs +++ b/src/de/child_value.rs @@ -3,11 +3,14 @@ use std::fmt::Formatter; use serde::de::{DeserializeSeed, EnumAccess, VariantAccess, Visitor}; use serde::{Deserialize, Deserializer}; +use crate::de::PrototypeDeserializer; +use crate::loader::ProtoLoader; use bevy_proto_backend::children::ProtoChildBuilder; +use bevy_proto_backend::load::Loader; use bevy_proto_backend::path::ProtoPathDeserializer; use crate::prelude::Prototype; -use crate::proto::{ProtoChildValue, PrototypeDeserializer}; +use crate::proto::ProtoChildValue; const PROTO_CHILD_VALUE: &str = "ProtoChildValue"; const PROTO_CHILD_VALUE_PATH: &str = "Path"; @@ -20,18 +23,20 @@ enum ProtoChildValueVariant { Inline, } -pub(crate) struct ProtoChildValueDeserializer<'a, 'ctx, 'load_ctx> { - builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>, +pub struct ProtoChildValueDeserializer<'a, 'ctx, 'load_ctx, L: Loader = ProtoLoader> { + builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>, } -impl<'a, 'ctx, 'load_ctx> ProtoChildValueDeserializer<'a, 'ctx, 'load_ctx> { - pub fn new(builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>) -> Self { +impl<'a, 'ctx, 'load_ctx, L: Loader> + ProtoChildValueDeserializer<'a, 'ctx, 'load_ctx, L> +{ + pub fn new(builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>) -> Self { Self { builder } } } -impl<'a, 'ctx, 'load_ctx, 'de> DeserializeSeed<'de> - for ProtoChildValueDeserializer<'a, 'ctx, 'load_ctx> +impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> DeserializeSeed<'de> + for ProtoChildValueDeserializer<'a, 'ctx, 'load_ctx, L> { type Value = ProtoChildValue; @@ -39,10 +44,12 @@ impl<'a, 'ctx, 'load_ctx, 'de> DeserializeSeed<'de> where D: Deserializer<'de>, { - struct ProtoChildValueVisitor<'a, 'ctx, 'load_ctx> { - builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>, + struct ProtoChildValueVisitor<'a, 'ctx, 'load_ctx, L: Loader> { + builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>, } - impl<'a, 'ctx, 'load_ctx, 'de> Visitor<'de> for ProtoChildValueVisitor<'a, 'ctx, 'load_ctx> { + impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> Visitor<'de> + for ProtoChildValueVisitor<'a, 'ctx, 'load_ctx, L> + { type Value = ProtoChildValue; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { diff --git a/src/proto/child/de/sequence.rs b/src/de/children.rs similarity index 65% rename from src/proto/child/de/sequence.rs rename to src/de/children.rs index 5fecbfa..dfbe2e0 100644 --- a/src/proto/child/de/sequence.rs +++ b/src/de/children.rs @@ -1,26 +1,27 @@ use std::fmt::Formatter; +use crate::loader::ProtoLoader; +use bevy_proto_backend::children::ProtoChildBuilder; +use bevy_proto_backend::load::Loader; use serde::de::{DeserializeSeed, SeqAccess, Visitor}; use serde::Deserializer; -use bevy_proto_backend::children::ProtoChildBuilder; - -use crate::proto::child::de::child::ProtoChildDeserializer; -use crate::proto::child::de::PROTO_CHILD; +use crate::de::child::PROTO_CHILD; +use crate::de::ProtoChildDeserializer; use crate::proto::{ProtoChild, Prototype}; -pub(crate) struct ProtoChildrenDeserializer<'a, 'ctx, 'load_ctx> { - builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>, +pub struct ProtoChildrenDeserializer<'a, 'ctx, 'load_ctx, L: Loader = ProtoLoader> { + builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>, } -impl<'a, 'ctx, 'load_ctx> ProtoChildrenDeserializer<'a, 'ctx, 'load_ctx> { - pub fn new(builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>) -> Self { +impl<'a, 'ctx, 'load_ctx, L: Loader> ProtoChildrenDeserializer<'a, 'ctx, 'load_ctx, L> { + pub fn new(builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>) -> Self { Self { builder } } } -impl<'a, 'ctx, 'load_ctx, 'de> DeserializeSeed<'de> - for ProtoChildrenDeserializer<'a, 'ctx, 'load_ctx> +impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> DeserializeSeed<'de> + for ProtoChildrenDeserializer<'a, 'ctx, 'load_ctx, L> { type Value = Vec; @@ -28,10 +29,12 @@ impl<'a, 'ctx, 'load_ctx, 'de> DeserializeSeed<'de> where D: Deserializer<'de>, { - struct ProtoChildrenVisitor<'a, 'ctx, 'load_ctx> { - pub(crate) builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype>, + struct ProtoChildrenVisitor<'a, 'ctx, 'load_ctx, L: Loader> { + pub(crate) builder: &'a mut ProtoChildBuilder<'ctx, 'load_ctx, Prototype, L>, } - impl<'a, 'ctx, 'load_ctx, 'de> Visitor<'de> for ProtoChildrenVisitor<'a, 'ctx, 'load_ctx> { + impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> Visitor<'de> + for ProtoChildrenVisitor<'a, 'ctx, 'load_ctx, L> + { type Value = Vec; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { diff --git a/src/de/mod.rs b/src/de/mod.rs new file mode 100644 index 0000000..659039c --- /dev/null +++ b/src/de/mod.rs @@ -0,0 +1,9 @@ +pub use child::*; +pub use child_value::*; +pub use children::*; +pub use proto::*; + +mod child; +mod child_value; +mod children; +mod proto; diff --git a/src/proto/de.rs b/src/de/proto.rs similarity index 88% rename from src/proto/de.rs rename to src/de/proto.rs index d4bd952..6a40286 100644 --- a/src/proto/de.rs +++ b/src/de/proto.rs @@ -5,13 +5,13 @@ use serde::de::{DeserializeSeed, Error, MapAccess, Visitor}; use serde::{Deserialize, Deserializer}; use bevy_proto_backend::children::Children; -use bevy_proto_backend::load::ProtoLoadContext; +use bevy_proto_backend::load::{Loader, ProtoLoadContext}; use bevy_proto_backend::path::{ProtoPathContext, ProtoPathListDeserializer}; use bevy_proto_backend::schematics::Schematics; use bevy_proto_backend::templates::Templates; +use crate::de::ProtoChildrenDeserializer; use crate::prelude::Prototype; -use crate::proto::ProtoChildrenDeserializer; use crate::schematics::SchematicsDeserializer; const NAME: &str = "name"; @@ -30,28 +30,32 @@ enum PrototypeField { Entity, } -pub(crate) struct PrototypeDeserializer<'a, 'ctx, 'load_ctx> { - pub(crate) context: &'a mut ProtoLoadContext<'ctx, 'load_ctx, Prototype>, +pub struct PrototypeDeserializer<'a, 'ctx, 'load_ctx, L: Loader> { + pub(crate) context: &'a mut ProtoLoadContext<'ctx, 'load_ctx, Prototype, L>, } -impl<'a, 'ctx, 'load_ctx> PrototypeDeserializer<'a, 'ctx, 'load_ctx> { - pub fn new(context: &'a mut ProtoLoadContext<'ctx, 'load_ctx, Prototype>) -> Self { +impl<'a, 'ctx, 'load_ctx, L: Loader> PrototypeDeserializer<'a, 'ctx, 'load_ctx, L> { + pub fn new(context: &'a mut ProtoLoadContext<'ctx, 'load_ctx, Prototype, L>) -> Self { Self { context } } } -impl<'a, 'ctx, 'load_ctx, 'de> DeserializeSeed<'de> for PrototypeDeserializer<'a, 'ctx, 'load_ctx> { +impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> DeserializeSeed<'de> + for PrototypeDeserializer<'a, 'ctx, 'load_ctx, L> +{ type Value = Prototype; fn deserialize(self, deserializer: D) -> Result where D: Deserializer<'de>, { - struct PrototypeVisitor<'a, 'ctx, 'load_ctx> { - context: &'a mut ProtoLoadContext<'ctx, 'load_ctx, Prototype>, + struct PrototypeVisitor<'a, 'ctx, 'load_ctx, L: Loader> { + context: &'a mut ProtoLoadContext<'ctx, 'load_ctx, Prototype, L>, } - impl<'a, 'ctx, 'load_ctx, 'de> Visitor<'de> for PrototypeVisitor<'a, 'ctx, 'load_ctx> { + impl<'a, 'ctx, 'load_ctx, 'de, L: Loader> Visitor<'de> + for PrototypeVisitor<'a, 'ctx, 'load_ctx, L> + { type Value = Prototype; fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { diff --git a/src/lib.rs b/src/lib.rs index 0379d6d..5391c77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,9 @@ mod conditions; pub mod config; #[cfg(feature = "custom_schematics")] pub mod custom; +pub mod de; pub mod hooks; +pub mod loader; mod plugin; pub mod proto; mod schematics; @@ -78,40 +80,44 @@ mod schematics; /// use bevy_proto::prelude::*; /// ``` pub mod prelude { + pub use crate::config::ProtoConfig; pub use bevy_proto_backend::deps::DependenciesBuilder; pub use bevy_proto_backend::proto::Prototypical; pub use bevy_proto_backend::schematics::{ReflectSchematic, Schematic, SchematicContext}; pub use super::conditions::*; pub use super::plugin::ProtoPlugin; - pub use super::proto::Prototype; + pub use super::proto::{Prototype, PrototypeError}; /// A helper SystemParam for managing [prototypes]. /// /// For the mutable version, see [`PrototypesMut`]. /// /// [prototypes]: Prototype - pub type Prototypes<'w> = bevy_proto_backend::proto::Prototypes<'w, Prototype>; + pub type Prototypes<'w, C = ProtoConfig> = + bevy_proto_backend::proto::Prototypes<'w, Prototype, C>; /// A helper SystemParam for managing [prototypes]. /// /// For the immutable version, see [`Prototypes`]. /// /// [prototypes]: Prototype - pub type PrototypesMut<'w> = bevy_proto_backend::proto::PrototypesMut<'w, Prototype>; + pub type PrototypesMut<'w, C = ProtoConfig> = + bevy_proto_backend::proto::PrototypesMut<'w, Prototype, C>; /// A system parameter similar to [`Commands`], but catered towards [prototypes]. /// /// [`Commands`]: bevy::prelude::Commands /// [prototypes]: Prototype - pub type ProtoCommands<'w, 's> = bevy_proto_backend::proto::ProtoCommands<'w, 's, Prototype>; + pub type ProtoCommands<'w, 's, C = ProtoConfig> = + bevy_proto_backend::proto::ProtoCommands<'w, 's, Prototype, C>; /// A struct similar to [`EntityCommands`], but catered towards [prototypes]. /// /// [`EntityCommands`]: bevy::ecs::system::EntityCommands /// [prototypes]: Prototype - pub type ProtoEntityCommands<'w, 's, 'a> = - bevy_proto_backend::proto::ProtoEntityCommands<'w, 's, 'a, Prototype>; + pub type ProtoEntityCommands<'w, 's, 'a, C = ProtoConfig> = + bevy_proto_backend::proto::ProtoEntityCommands<'w, 's, 'a, Prototype, C>; /// Asset lifecycle events for [prototype] assets. /// diff --git a/src/loader.rs b/src/loader.rs new file mode 100644 index 0000000..1b4ddee --- /dev/null +++ b/src/loader.rs @@ -0,0 +1,82 @@ +use crate::de::PrototypeDeserializer; +use crate::proto::{Prototype, PrototypeError}; +use bevy_proto_backend::load::{Loader, ProtoLoadContext}; +use bevy_proto_backend::path::ProtoPathContext; +use serde::de::DeserializeSeed; + +const RON_FORMATS: &[&str] = &["prototype.ron", "proto.ron"]; +const YAML_FORMATS: &[&str] = &["prototype.yaml", "proto.yaml"]; + +/// The default prototype loader. +/// +/// # Supported Formats +/// +/// | Format | Feature | Extensions | +/// | ------ | ------- | ---------- | +/// | [RON] | `ron` | `.prototype.ron`, `.proto.ron` | +/// | [YAML] | `yaml` | `.prototype.yaml`, `.proto.yaml` | +/// +/// [RON]: https://github.com/ron-rs/ron +/// [YAML]: https://github.com/dtolnay/serde-yaml +#[derive(Clone)] +pub struct ProtoLoader { + extensions: Vec<&'static str>, +} + +impl Default for ProtoLoader { + fn default() -> Self { + let mut extensions = Vec::new(); + + if cfg!(feature = "yaml") { + extensions.extend(YAML_FORMATS); + } + + if cfg!(feature = "ron") { + extensions.extend(RON_FORMATS); + } + + Self { extensions } + } +} + +impl Loader for ProtoLoader { + type Error = PrototypeError; + + fn deserialize( + bytes: &[u8], + ctx: &mut ProtoLoadContext, + ) -> Result { + let path = ctx.base_path().to_path_buf(); + + let ext = path + .extension() + .ok_or_else(|| PrototypeError::MissingExtension(path.clone()))?; + + let ext = ext + .to_str() + .ok_or_else(|| PrototypeError::UnsupportedExtension(ext.to_string_lossy().to_string()))? + .to_lowercase(); + + let deserializer = PrototypeDeserializer::new(ctx); + + match ext.as_str() { + #[cfg(feature = "ron")] + "ron" => { + let mut ron_de = ron::Deserializer::from_bytes(bytes) + .map_err(|err| PrototypeError::SpannedRonError(path.clone(), err))?; + deserializer.deserialize(&mut ron_de).map_err(|err| { + PrototypeError::SpannedRonError(path.clone(), ron_de.span_error(err)) + }) + } + #[cfg(feature = "yaml")] + "yaml" => deserializer + .deserialize(serde_yaml::Deserializer::from_slice(bytes)) + .map_err(PrototypeError::from), + other => Err(PrototypeError::UnsupportedExtension(other.to_string())), + } + } + + fn extensions(&self) -> &[&'static str] { + &self.extensions + } +} diff --git a/src/plugin.rs b/src/plugin.rs index 7e74232..fa81ca9 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,37 +1,114 @@ use std::sync::Mutex; use bevy::app::{App, Plugin}; +use bevy_proto_backend::load::Loader; +use bevy_proto_backend::proto::Config; use bevy_proto_backend::ProtoBackendPlugin; use crate::config::ProtoConfig; +use crate::loader::ProtoLoader; use crate::proto::Prototype; /// Adds support for [`Prototype`] assets. -#[derive(Default)] -pub struct ProtoPlugin { - config: Mutex>, +/// +/// By default this sets up the plugin with the default [`ProtoLoader`] and [`ProtoConfig`]. +/// However, this may be configured with user-defined [`Loaders`] and [`Configs`]. +/// +/// Note: If using a custom `Config` type, be sure to use that type as the generic +/// parameter for any system parameter from this crate that requires it +/// (e.g. [`Prototypes`], [`ProtoCommands`], etc.). +/// +/// [`Loaders`]: Loader +/// [`Configs`]: Config +/// [`Prototypes`]: crate::prelude::Prototypes +/// [`ProtoCommands`]: crate::prelude::ProtoCommands +pub struct ProtoPlugin = ProtoLoader, C: Config = ProtoConfig> { + loader: Mutex>, + config: Mutex>, } impl ProtoPlugin { - pub fn new(config: ProtoConfig) -> Self { + /// Create a new default plugin instance. + pub fn new() -> Self { Self { + loader: Mutex::new(None), + config: Mutex::new(None), + } + } +} + +impl> ProtoPlugin { + /// Create a new plugin instance with the given [`Loader`]. + pub fn new_with_loader(loader: L) -> Self { + Self { + loader: Mutex::new(Some(loader)), + config: Mutex::new(None), + } + } + + /// Pass the given [`ProtoConfig`] instance to the plugin. + pub fn with_config(mut self, config: ProtoConfig) -> Self { + self.config = Mutex::new(Some(config)); + self + } +} + +impl> ProtoPlugin { + /// Create a new plugin instance with the given [`Config`]. + /// + /// Note: If using a custom `Config` type, be sure to use that type as the generic + /// parameter for any system parameter from this crate that requires it + /// (e.g. [`Prototypes`], [`ProtoCommands`], etc.). + /// + /// [`Prototypes`]: crate::prelude::Prototypes + /// [`ProtoCommands`]: crate::prelude::ProtoCommands + pub fn new_with_config(config: C) -> Self { + Self { + loader: Mutex::new(None), config: Mutex::new(Some(config)), } } } -impl Plugin for ProtoPlugin { +impl, C: Config> ProtoPlugin { + /// Create a new plugin instance with the given [`Loader`] and [`Config`]. + /// + /// Note: If using a custom `Config` type, be sure to use that type as the generic + /// parameter for any system parameter from this crate that requires it + /// (e.g. [`Prototypes`], [`ProtoCommands`], etc.). + /// + /// [`Prototypes`]: crate::prelude::Prototypes + /// [`ProtoCommands`]: crate::prelude::ProtoCommands + pub fn new_with_loader_and_config(loader: L, config: C) -> Self { + Self { + loader: Mutex::new(Some(loader)), + config: Mutex::new(Some(config)), + } + } +} + +impl, C: Config> Plugin for ProtoPlugin { fn build(&self, app: &mut App) { - let mut plugin = ProtoBackendPlugin::::default(); + let mut plugin = ProtoBackendPlugin::::new(); if let Ok(Some(config)) = self.config.lock().map(|mut config| config.take()) { plugin = plugin.with_config(config); } + if let Ok(Some(loader)) = self.loader.lock().map(|mut loader| loader.take()) { + plugin = plugin.with_loader(loader); + } + app.add_plugin(plugin); #[cfg(feature = "custom_schematics")] crate::custom::register_custom_schematics(app); } } + +impl Default for ProtoPlugin { + fn default() -> Self { + Self::new() + } +} diff --git a/src/proto/child/proto_child.rs b/src/proto/child.rs similarity index 100% rename from src/proto/child/proto_child.rs rename to src/proto/child.rs diff --git a/src/proto/child/de/mod.rs b/src/proto/child/de/mod.rs deleted file mode 100644 index 336b20b..0000000 --- a/src/proto/child/de/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub(crate) use sequence::*; - -mod child; -mod sequence; -mod value; - -const PROTO_CHILD: &str = "ProtoChild"; -const PROTO_CHILD_MERGE_KEY: &str = "merge_key"; -const PROTO_CHILD_VALUE: &str = "value"; diff --git a/src/proto/child/mod.rs b/src/proto/child/mod.rs deleted file mode 100644 index b1eb90e..0000000 --- a/src/proto/child/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub(crate) use de::*; -pub use proto_child::*; - -mod de; -mod proto_child; diff --git a/src/proto/error.rs b/src/proto/error.rs index 43f1fe6..6fb298c 100644 --- a/src/proto/error.rs +++ b/src/proto/error.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::path::PathBuf; use thiserror::Error; @@ -9,6 +10,8 @@ use bevy_proto_backend::schematics::SchematicError; /// [`Prototype`]: crate::prelude::Prototype #[derive(Debug, Error)] pub enum PrototypeError { + #[error("{0}")] + Custom(String), /// The path of the prototype being loaded is missing an extension. #[error("expected extension")] MissingExtension(PathBuf), @@ -26,3 +29,9 @@ pub enum PrototypeError { #[error(transparent)] SchematicError(#[from] SchematicError), } + +impl PrototypeError { + pub fn custom(msg: impl Display) -> Self { + Self::Custom(format!("{}", msg)) + } +} diff --git a/src/proto/mod.rs b/src/proto/mod.rs index ef45693..eff4292 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -1,11 +1,9 @@ //! Items relating to the main [`Prototype`] struct. pub use child::*; -pub(crate) use de::*; pub use error::*; pub use prototype::*; -mod child; -mod de; +pub mod child; mod error; mod prototype; diff --git a/src/proto/prototype.rs b/src/proto/prototype.rs index 1537667..2c6acc8 100644 --- a/src/proto/prototype.rs +++ b/src/proto/prototype.rs @@ -1,17 +1,12 @@ +use crate::proto::ProtoChild; use bevy::reflect::TypeUuid; -use serde::de::DeserializeSeed; - use bevy_proto_backend::children::Children; use bevy_proto_backend::deps::Dependencies; -use bevy_proto_backend::load::ProtoLoadContext; -use bevy_proto_backend::path::{ProtoPath, ProtoPathContext}; +use bevy_proto_backend::path::ProtoPath; use bevy_proto_backend::proto::Prototypical; use bevy_proto_backend::schematics::Schematics; use bevy_proto_backend::templates::Templates; -use crate::config::ProtoConfig; -use crate::proto::{ProtoChild, PrototypeDeserializer, PrototypeError}; - /// The core asset type used to create easily-configurable entity trees. #[derive(Debug, TypeUuid)] #[uuid = "cbc85a87-723a-4e61-83c7-26e96e54fe9f"] @@ -28,8 +23,6 @@ pub struct Prototype { impl Prototypical for Prototype { type Id = String; type Child = ProtoChild; - type Config = ProtoConfig; - type Error = PrototypeError; fn id(&self) -> &Self::Id { &self.id @@ -74,35 +67,4 @@ impl Prototypical for Prototype { fn children_mut(&mut self) -> Option<&mut Children> { self.children.as_mut() } - - fn deserialize(bytes: &[u8], ctx: &mut ProtoLoadContext) -> Result { - let path = ctx.base_path().to_path_buf(); - - let ext = path - .extension() - .ok_or_else(|| PrototypeError::MissingExtension(path.clone()))?; - - let ext = ext - .to_str() - .ok_or_else(|| PrototypeError::UnsupportedExtension(ext.to_string_lossy().to_string()))? - .to_lowercase(); - - let deserializer = PrototypeDeserializer::new(ctx); - - match ext.as_str() { - #[cfg(feature = "ron")] - "ron" => { - let mut ron_de = ron::Deserializer::from_bytes(bytes) - .map_err(|err| PrototypeError::SpannedRonError(path.clone(), err))?; - deserializer.deserialize(&mut ron_de).map_err(|err| { - PrototypeError::SpannedRonError(path.clone(), ron_de.span_error(err)) - }) - } - #[cfg(feature = "yaml")] - "yaml" => deserializer - .deserialize(serde_yaml::Deserializer::from_slice(bytes)) - .map_err(PrototypeError::from), - other => Err(PrototypeError::UnsupportedExtension(other.to_string())), - } - } } diff --git a/src/schematics.rs b/src/schematics.rs index 7ca6db0..68256c1 100644 --- a/src/schematics.rs +++ b/src/schematics.rs @@ -44,7 +44,7 @@ impl<'de, 'a> DeserializeSeed<'de> for SchematicsDeserializer<'a> { while let Some(registration) = map.next_key_seed(TypeRegistrationDeserializer::new(self.registry))? { - if schematics.contains(registration.type_name()) { + if schematics.contains_by_name(registration.type_name()) { return Err(Error::custom(format_args!( "duplicate schematic: `{}`", registration.type_name() @@ -70,7 +70,7 @@ impl<'de, 'a> DeserializeSeed<'de> for SchematicsDeserializer<'a> { .create_dynamic(input) .map_err(Error::custom)?; - schematics.insert(schematic); + schematics.insert_dynamic(schematic); } Ok(schematics) @@ -119,7 +119,7 @@ mod tests { assert_eq!( &MySchematic { foo: 123 }, schematics - .get("bevy_proto::schematics::tests::MySchematic") + .get::() .unwrap() .input() .downcast_ref::()