From fd8428939cf47cf6c1772d7ce1b4ed044f0ca7b0 Mon Sep 17 00:00:00 2001 From: "Bruce Reif (Buswolley)" Date: Tue, 8 Mar 2022 16:59:48 -0800 Subject: [PATCH 1/5] document public items, fix doc tests --- src/components.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++- src/data.rs | 46 +++++++++++++++++++++++------------- src/lib.rs | 10 +++++++- src/plugin.rs | 45 +++++++++++++++++------------------- src/prototype.rs | 37 +++++++++++++---------------- src/utils.rs | 2 +- 6 files changed, 135 insertions(+), 64 deletions(-) diff --git a/src/components.rs b/src/components.rs index 8596cee..8f3b615 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,12 +1,69 @@ +//! Contains the [`ProtoComponent`] trait. use bevy::prelude::{AssetServer, Res, World}; use crate::data::{ProtoCommands, ProtoData}; use crate::prototype::Prototypical; -/// A trait that allows components to be used within [`Prototypical`] structs +/// Specifies how a struct inserts components into an entity. +/// +/// Any struct which is `Send + Sync + 'static` can implement [`ProtoComponent`]. +/// The implementing struct may or may not be a component itself. +/// Commonly, [data transfer objects](https://en.wikipedia.org/wiki/Data_transfer_object) +/// can implement [`ProtoComponent`] to generate components or bundles. +/// +/// The [`insert_self`][`ProtoComponent::insert_self`] method provides full mutable access to [`EntityCommands`][bevy::ecs::system::EntityCommands]. +/// Implementations can arbitrarily insert zero, one, or many components or bundles at once into an entity. +/// +/// This trait allows components to be used within [`Prototypical`](crate::prototype::Prototypical) structs. +/// +/// # Examples +/// +/// For simple components, [`ProtoComponent`] can be derived: +/// +/// ``` +/// use serde::{Deserialize, Serialize}; +/// use bevy::prelude::*; +/// use bevy_proto::prelude::*; +/// +/// #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] +/// pub struct Movement { +/// speed: u16, +/// } +/// +/// // Also works on tuple structs: +/// #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] +/// struct Inventory (Option>); +/// ``` +/// +/// The derived implementation clones `Self` and inserts the cloned value into the entity. +/// To derive [`ProtoComponent`], a struct must also be [`Clone`], [`serde::Deserialize`], [`serde::Serialize`], and [`Component`][bevy::ecs::component::Component]. +/// +/// [`ProtoComponent`] can also be implemented manually: +/// +/// ``` +/// use serde::{Deserialize, Serialize}; +/// use bevy::prelude::*; +/// use bevy::ecs::system::EntityCommands; +/// use bevy_proto::prelude::*; +/// +/// #[derive(Serialize, Deserialize, Component)] // Required +/// struct Inventory(Option>); +/// +/// #[typetag::serde] // Required +/// impl ProtoComponent for Inventory { +/// // Required +/// fn insert_self(&self, commands: &mut ProtoCommands, asset_server: &Res) { +/// commands.insert( +/// Self (self.0.clone()) +/// ); +/// } +/// } +/// ``` #[typetag::serde(tag = "type", content = "value")] pub trait ProtoComponent: Send + Sync + 'static { + /// Defines how this struct inserts components and/or bundles into an entity. fn insert_self(&self, commands: &mut ProtoCommands, asset_server: &Res); + /// Defines how this struct creates and inserts asset handles for later use. #[allow(unused_variables)] fn prepare(&self, world: &mut World, prototype: &dyn Prototypical, data: &mut ProtoData) {} } diff --git a/src/data.rs b/src/data.rs index 3dbc682..74927ea 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,3 +1,4 @@ +//! Provides resource and deserialization for prototype data. use std::any::{Any, TypeId}; use std::ffi::OsStr; use std::ops::{Deref, DerefMut}; @@ -12,6 +13,7 @@ use dyn_clone::DynClone; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; +use crate::prelude::DefaultProtoDeserializer; use crate::{components::ProtoComponent, prototype::Prototypical, utils::handle_cycle}; /// A String newtype for a handle's asset path @@ -45,6 +47,7 @@ pub struct ProtoData { } impl ProtoData { + /// Creates a new, empty instance of [`ProtoData`]. pub fn empty() -> Self { Self { handles: HashMap::default(), @@ -78,13 +81,13 @@ impl ProtoData { /// /// ``` /// use bevy::prelude::*; - /// use bevy_proto::{HandlePath, ProtoData, Prototype, PrototypeDataContainer}; + /// use bevy_proto::prelude::*; + /// use serde::{Deserialize, Serialize}; /// + /// #[derive(Clone, Deserialize, Serialize, Component, ProtoComponent)] /// struct MyComponent { /// texture_path: HandlePath - /// } - /// - /// // impl ProtoComponent for MyComponent { ... } + /// } /// /// fn some_loader(asset_server: Res, mut data: ResMut) { /// let comp = MyComponent { @@ -93,10 +96,10 @@ impl ProtoData { /// let proto = Prototype { /// name: String::from("My Prototype"), /// templates: Vec::default(), - /// components: vec![Box::new(comp)] + /// components: vec![Box::new(comp.clone())] /// }; /// - /// let handle: Handle = asset_server.load(comp.texture_path.0.as_str()); + /// let handle: Handle = asset_server.load(comp.texture_path.0.as_str()); /// /// data.insert_handle(&proto, &comp, &comp.texture_path, handle); /// } @@ -423,6 +426,7 @@ impl<'w, 's, 'a, 'p> DerefMut for ProtoCommands<'w, 's, 'a, 'p> { } } +/// Defines a method for deserializing a prototype file input. pub trait ProtoDeserializer: DynClone { /// Deserializes file input (as a string) into a [`Prototypical`] object /// @@ -436,7 +440,7 @@ pub trait ProtoDeserializer: DynClone { /// /// ``` /// // The default implementation: - /// use bevy_proto::{Prototype, Prototypical}; + /// use bevy_proto::prototype::{Prototype, Prototypical}; /// fn example_deserialize(data: &str) -> Option> { /// if let Ok(value) = serde_yaml::from_str::(data) { /// Some(Box::new(value)) @@ -450,20 +454,19 @@ pub trait ProtoDeserializer: DynClone { dyn_clone::clone_trait_object!(ProtoDeserializer); -/// Options for controlling how prototype data is handled +/// Options for controlling how prototype data is handled. #[derive(Clone)] pub struct ProtoDataOptions { - /// Directories containing prototype data + /// Directories containing prototype data. pub directories: Vec, - /// Whether to resursively load extension files from the specified top-level directories. + /// Whether to load files recursively from the specified top-level directories. /// /// # Examples /// - /// Example that recursively loads all yaml files from the assets/prototypes directory. - /// /// ``` - /// use bevy_proto::ProtoDataOptions; + /// use bevy_proto::data::ProtoDataOptions; /// + /// // Recursively loads all yaml files from the "assets/prototypes" directory. /// let opts = ProtoDataOptions { /// directories: vec![String::from("assets/prototypes")], /// recursive_loading: true, @@ -472,9 +475,9 @@ pub struct ProtoDataOptions { /// }; /// ``` pub recursive_loading: bool, - /// A custom deserializer for prototypes + /// A custom deserializer for prototypes. pub deserializer: Box, - /// A collection of extensions to filter the directories by. These do __not__ + /// An optional collection of file extensions to filter prototypes. These do __not__ /// have a dot ('.') prepended to them. /// /// A value of None allows all files to be read. @@ -482,7 +485,7 @@ pub struct ProtoDataOptions { /// # Examples /// /// ``` - /// use bevy_proto::ProtoDataOptions; + /// use bevy_proto::data::ProtoDataOptions; /// /// let opts = ProtoDataOptions { /// // Only allow .yaml or .json files @@ -492,3 +495,14 @@ pub struct ProtoDataOptions { /// ``` pub extensions: Option>, } + +impl Default for ProtoDataOptions { + fn default() -> Self { + Self { + directories: Default::default(), + recursive_loading: Default::default(), + deserializer: Box::new(DefaultProtoDeserializer), + extensions: Default::default(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 50f2173..e34ba6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,10 @@ +#![warn(missing_docs)] +//! Serializable entity configuration for the Bevy game engine. +//! +//! This crate provides several abstractions for specifying serializable entities and components: +//! - The [`ProtoComponent`](components::ProtoComponent) trait provides methods to load components from assets. +//! - The [`ProtoDeserializer`](data::ProtoDeserializer) trait describes component deserialization. +//! - [`ProtoPlugin`](plugin::ProtoPlugin) provides configuration for asset loading. extern crate bevy_proto_derive; pub mod components; @@ -8,10 +15,11 @@ pub mod prototype; mod utils; pub mod prelude { - pub use bevy_proto_derive::*; + //! Includes all public types and the macro to derive [`ProtoComponent`](super::components::ProtoComponent). pub use super::components::*; pub use super::data::*; pub use super::plugin::*; pub use super::prototype::{Prototype, Prototypical}; + pub use bevy_proto_derive::*; } diff --git a/src/plugin.rs b/src/plugin.rs index 88e4583..40d61e6 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -1,3 +1,4 @@ +//! Contains [`ProtoPlugin`]. use bevy::app::{App, Plugin}; use crate::{ @@ -5,24 +6,25 @@ use crate::{ prototype::{Prototype, Prototypical}, }; +/// Inserts resources for loading prototypes. #[derive(Default)] pub struct ProtoPlugin { + /// Optional plugin configuration. pub options: Option, } impl ProtoPlugin { - /// Specify the directory containing the prototype files + /// Creates a [`ProtoPlugin`], using the given path to find prototype files. + /// See also: [`with_dir_recursive`][`ProtoPlugin::with_dir_recursive`]. /// - /// # Arguments + /// # Parameters /// - /// * `dir`: The directory path, relative to the project root - /// - /// returns: ProtoPlugin + /// * `dir`: The directory path, relative to the project root. /// /// # Examples /// /// ``` - /// use bevy_proto::ProtoPlugin; + /// use bevy_proto::plugin::ProtoPlugin; /// /// let plugin = ProtoPlugin::with_dir("assets/config"); /// ``` @@ -37,18 +39,17 @@ impl ProtoPlugin { } } - /// Same as [with_dir] but recursively loads prototype files. - /// - /// # Arguments + /// Creates a [`ProtoPlugin`], using the given path recursively to find prototype files. + /// See also: [`with_dir`][`ProtoPlugin::with_dir`]. /// - /// * `dir`: The directory path, relative to the project root + /// # Parameters /// - /// returns: ProtoPlugin + /// * `dir`: The directory path, relative to the project root. /// /// # Examples /// /// ``` - /// use bevy_proto::ProtoPlugin; + /// use bevy_proto::plugin::ProtoPlugin; /// /// let plugin = ProtoPlugin::with_dir_recursive("assets/config"); /// ``` @@ -63,18 +64,16 @@ impl ProtoPlugin { } } - /// Specify a set of directories containing the prototype files + /// Creates a [`ProtoPlugin`], using the given vec of paths to find prototype files. /// - /// # Arguments + /// # Parameters /// - /// * `dirs`: The directory paths, relative to the project root - /// - /// returns: ProtoPlugin + /// * `dirs`: The directory paths, relative to the project root. /// /// # Examples /// /// ``` - /// use bevy_proto::ProtoPlugin; + /// use bevy_proto::plugin::ProtoPlugin; /// /// let plugin = ProtoPlugin::with_dirs(vec![ /// String::from("assets/config"), @@ -92,18 +91,16 @@ impl ProtoPlugin { } } - /// Same as [with_dirs] but recursively loads prototype files. + /// Creates a [`ProtoPlugin`], using the given vec of dirs recursively to find prototype files. /// - /// # Arguments + /// # Parameters /// /// * `dirs`: The directory paths, relative to the project root /// - /// returns: ProtoPlugin - /// /// # Examples /// /// ``` - /// use bevy_proto::ProtoPlugin; + /// use bevy_proto::plugin::ProtoPlugin; /// /// let plugin = ProtoPlugin::with_dirs(vec![ /// String::from("assets/config"), @@ -143,7 +140,7 @@ impl Plugin for ProtoPlugin { } #[derive(Clone)] -struct DefaultProtoDeserializer; +pub(crate) struct DefaultProtoDeserializer; impl ProtoDeserializer for DefaultProtoDeserializer { fn deserialize(&self, data: &str) -> Option> { diff --git a/src/prototype.rs b/src/prototype.rs index fd5f198..d6ddf5e 100644 --- a/src/prototype.rs +++ b/src/prototype.rs @@ -1,3 +1,4 @@ +//! Provides the core abstractions [`Prototypical`] and [`Prototype`] for implementing prototypical structs. use std::fmt::Formatter; use std::iter::Rev; use std::slice::Iter; @@ -22,44 +23,40 @@ pub trait Prototypical: 'static + Send + Sync { /// This should be unique amongst all prototypes in the world fn name(&self) -> &str; - /// The names of the parent templates (if any) + /// The names of the parent templates (if any). fn templates(&self) -> &[String] { &[] } - /// The names of the parent templates (if any) in reverse order + /// The names of the parent templates (if any) in reverse order. fn templates_rev(&self) -> Rev> { self.templates().iter().rev() } - /// Returns an iterator of [`ProtoComponent`] objects + /// Returns an iterator of [`ProtoComponent`] trait objects. fn iter_components(&self) -> Iter<'_, Box>; - /// Creates the [`ProtoCommands`] object used for modifying the given entity + /// Creates [`ProtoCommands`] used to modify the given entity. /// - /// # Arguments + /// # Parameters /// /// * `entity`: The entity commands /// * `data`: The prototype data in this world /// - /// returns: ProtoCommands - /// fn create_commands<'w, 's, 'a, 'p>( &'p self, entity: EntityCommands<'w, 's, 'a>, data: &'p Res, ) -> ProtoCommands<'w, 's, 'a, 'p>; - /// Spawns an entity with this prototype's component structure + /// Spawns an entity with this prototype's component structure. /// - /// # Arguments + /// # Parameters /// /// * `commands`: The world `Commands` /// * `data`: The prototype data in this world /// * `asset_server`: The asset server /// - /// returns: EntityCommands - /// /// # Examples /// /// ``` @@ -92,7 +89,7 @@ pub trait Prototypical: 'static + Send + Sync { self.insert(entity, data, asset_server) } - /// Inserts this prototype's component structure to the given entity + /// Inserts this prototype's component structure to the given entity. /// /// __Note:__ This _will_ override existing components of the same type. /// @@ -102,15 +99,13 @@ pub trait Prototypical: 'static + Send + Sync { /// * `data`: The prototype data in this world /// * `asset_server`: The asset server /// - /// returns: EntityCommands - /// /// # Examples /// /// ``` /// use bevy::prelude::*; /// use bevy_proto::prelude::{ProtoData, Prototype, Prototypical}; /// - /// #[derive(Component, Default)] + /// #[derive(Component)] /// struct Player(pub Entity); /// /// fn setup_system(mut commands: Commands, data: Res, asset_server: &Res, player: Query<&Player>) { @@ -201,19 +196,19 @@ fn spawn_internal<'a>( } } -/// The default prototype object, providing the basics for the prototype system +/// The default prototype object, providing the basics for the prototype system. #[derive(Serialize, Deserialize)] pub struct Prototype { - /// The name of this prototype + /// The name of this prototype. pub name: String, - /// The names of this prototype's templates (if any) + /// The names of this prototype's templates (if any). /// - /// See [`deserialize_templates_list`], for how these names are deserialized. + /// See [`deserialize_templates_list`] for how these names are deserialized. #[serde(default)] #[serde(alias = "template")] #[serde(deserialize_with = "deserialize_templates_list")] pub templates: Vec, - /// The components belonging to this prototype + /// The components belonging to this prototype. #[serde(default)] pub components: Vec>, } @@ -240,7 +235,7 @@ impl Prototypical for Prototype { } } -/// A function used to deserialize a list of templates +/// A function used to deserialize a list of templates. /// /// A template list can take on the following forms: /// diff --git a/src/utils.rs b/src/utils.rs index 529b3d5..99ca17e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -12,7 +12,7 @@ use std::ops::Add; /// /// # Examples /// -/// ``` +/// ```ignore /// use indexmap::IndexSet; /// /// let mut traversed = IndexSet::<_>::default(); From c9bc59015d20daffe386e9bf7f0315d7f15b5764 Mon Sep 17 00:00:00 2001 From: "Bruce Reif (Buswolley)" Date: Wed, 9 Mar 2022 11:14:59 -0800 Subject: [PATCH 2/5] review changes --- README.md | 6 ++--- src/components.rs | 58 +++++++++++++++++++++++++++++------------------ src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++ src/plugin.rs | 12 +++++----- src/prototype.rs | 4 ++-- 5 files changed, 104 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index e367a7d..697e25b 100644 --- a/README.md +++ b/README.md @@ -359,11 +359,11 @@ impl ProtoDeserializer for CustomProtoDeserializer { } fn main() { - App::build() + App::new() .add_plugins(DefaultPlugins) // ... .add_plugin(ProtoPlugin { - options: ProtoDataOptions { + options: Some(ProtoDataOptions { // Specify your custom deserializer deserializer: Box::new(CustomProtoDeserializer), @@ -373,7 +373,7 @@ fn main() { recursive_loading: false, // You can also update the allowed extensions within those directories extensions: Some(vec!["yaml", "json"]), - } + }) }) // other plugins, resources, not needed by ProtoPlugin // ... diff --git a/src/components.rs b/src/components.rs index 8f3b615..e463ae1 100644 --- a/src/components.rs +++ b/src/components.rs @@ -4,41 +4,38 @@ use bevy::prelude::{AssetServer, Res, World}; use crate::data::{ProtoCommands, ProtoData}; use crate::prototype::Prototypical; -/// Specifies how a struct inserts components into an entity. +/// Specifies how a type inserts components into an entity. /// -/// Any struct which is `Send + Sync + 'static` can implement [`ProtoComponent`]. -/// The implementing struct may or may not be a component itself. -/// Commonly, [data transfer objects](https://en.wikipedia.org/wiki/Data_transfer_object) -/// can implement [`ProtoComponent`] to generate components or bundles. +/// Types implementing [`ProtoComponent`] describe how to insert any number of components or bundles when spawning a prototype. +/// Any type which is `Send + Sync + 'static` can implement [`ProtoComponent`]. /// -/// The [`insert_self`][`ProtoComponent::insert_self`] method provides full mutable access to [`EntityCommands`][bevy::ecs::system::EntityCommands]. -/// Implementations can arbitrarily insert zero, one, or many components or bundles at once into an entity. -/// -/// This trait allows components to be used within [`Prototypical`](crate::prototype::Prototypical) structs. +/// Notably, this means a [`ProtoComponent`] might not be a [`Component`][bevy::prelude::Component] itself. +/// The [`ProtoComponent`] can be a kind of [data transfer object](https://en.wikipedia.org/wiki/Data_transfer_object) which +/// describes inserting any arbitrary set of components or bundles. /// /// # Examples /// -/// For simple components, [`ProtoComponent`] can be derived: +/// To just insert a type which is a [`Component`][bevy::prelude::Component], [`ProtoComponent`] can be derived: /// /// ``` /// use serde::{Deserialize, Serialize}; /// use bevy::prelude::*; /// use bevy_proto::prelude::*; /// -/// #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] +/// #[derive(Clone, Serialize, Deserialize, Component, ProtoComponent)] /// pub struct Movement { /// speed: u16, /// } /// /// // Also works on tuple structs: -/// #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] -/// struct Inventory (Option>); +/// #[derive(Clone, Serialize, Deserialize, Component, ProtoComponent)] +/// struct Inventory(Option>); /// ``` /// -/// The derived implementation clones `Self` and inserts the cloned value into the entity. -/// To derive [`ProtoComponent`], a struct must also be [`Clone`], [`serde::Deserialize`], [`serde::Serialize`], and [`Component`][bevy::ecs::component::Component]. +/// The derived [`ProtoComponent`] implementation clones `Self` and inserts the cloned value into the entity. +/// A deriving type must also be [`Clone`], [`serde::Deserialize`], [`serde::Serialize`], and [`Component`][bevy::ecs::component::Component]. /// -/// [`ProtoComponent`] can also be implemented manually: +/// For other cases, [`ProtoComponent`] can be implemented manually: /// /// ``` /// use serde::{Deserialize, Serialize}; @@ -46,19 +43,36 @@ use crate::prototype::Prototypical; /// use bevy::ecs::system::EntityCommands; /// use bevy_proto::prelude::*; /// -/// #[derive(Serialize, Deserialize, Component)] // Required -/// struct Inventory(Option>); +/// // We'll implement `ProtoComponent` on this `Inventory` struct. +/// // Our implementation will insert multiple different components. +/// #[derive(Serialize, Deserialize)] // Required +/// struct Inventory { +/// items: Items, +/// quest_items: QuestItems, +/// } +/// +/// // This `Items` struct will be one of the component types we insert. +/// #[derive(Clone, Serialize, Deserialize, Component)] +/// struct Items(Vec); +/// +/// // We will also insert this separate `QuestItems` struct. +/// #[derive(Clone, Serialize, Deserialize, Component)] +/// struct QuestItems(Vec); /// /// #[typetag::serde] // Required /// impl ProtoComponent for Inventory { -/// // Required +/// // The `Inventory` implementation of `insert_self` inserts two components: +/// // one for `Items`, and one for `QuestItems`. /// fn insert_self(&self, commands: &mut ProtoCommands, asset_server: &Res) { -/// commands.insert( -/// Self (self.0.clone()) -/// ); +/// commands.insert(self.items.clone()); +/// commands.insert(self.quest_items.clone()); /// } /// } /// ``` +/// +/// Implementations of insert_self can arbitrarily insert zero, one, or many components or bundles. +/// +/// This trait allows components to be used within [`Prototypical`](crate::prototype::Prototypical) structs. #[typetag::serde(tag = "type", content = "value")] pub trait ProtoComponent: Send + Sync + 'static { /// Defines how this struct inserts components and/or bundles into an entity. diff --git a/src/lib.rs b/src/lib.rs index e34ba6a..4683711 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,63 @@ //! - The [`ProtoComponent`](components::ProtoComponent) trait provides methods to load components from assets. //! - The [`ProtoDeserializer`](data::ProtoDeserializer) trait describes component deserialization. //! - [`ProtoPlugin`](plugin::ProtoPlugin) provides configuration for asset loading. +//! +//! # Examples +//! Define a serialized prototype: +//! ``` +//! use bevy::prelude::*; +//! use bevy_proto::prelude::*; +//! use serde::{Deserialize, Serialize}; +//! +//! // Define a serialized prototype. +//! // In this example we would load this from a .yaml file in "assets/prototypes". +//! +//! // name: "Simple Enemy" +//! // components: +//! // - type: Enemy +//! // - type: Attack +//! // value: +//! // damage: 10 +//! +//! // Implement `ProtoComponent` for the component types: +//! +//! #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] +//! struct Enemy; +//! +//! #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] +//! struct Attack { +//! damage: u16 +//! } +//! +//! // Add the plugin: +//! fn main() { +//! App::new() +//! .add_plugins(DefaultPlugins) +//! +//! .add_plugin(ProtoPlugin { +//! options: Some(ProtoDataOptions { +//! // You can also change the prototype directories here +//! directories: vec![String::from("assets/prototypes")], +//! // And specify whether you want the prototype files to be recursively loaded +//! recursive_loading: false, +//! // You can also update the allowed extensions within those directories +//! extensions: Some(vec!["yaml", "json"]), +//! ..ProtoDataOptions::default() +//! }) +//! }); +//! } +//! +//! // Finally, spawn a prototype with a system: +//! +//! fn spawn_enemy(mut commands: Commands, data: Res, asset_server: Res) { +//! let proto = data.get_prototype("Simple Enemy").expect("Prototype doesn't exist!"); +//! +//! // Spawns in our "Simple Enemy" Prototype +//! proto.spawn(&mut commands, &data, &asset_server); +//! } +//! +//! ``` +//! extern crate bevy_proto_derive; pub mod components; diff --git a/src/plugin.rs b/src/plugin.rs index 40d61e6..29325db 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -17,7 +17,7 @@ impl ProtoPlugin { /// Creates a [`ProtoPlugin`], using the given path to find prototype files. /// See also: [`with_dir_recursive`][`ProtoPlugin::with_dir_recursive`]. /// - /// # Parameters + /// # Arguments /// /// * `dir`: The directory path, relative to the project root. /// @@ -39,10 +39,10 @@ impl ProtoPlugin { } } - /// Creates a [`ProtoPlugin`], using the given path recursively to find prototype files. + /// Creates a [`ProtoPlugin`], using the given path to recursively find prototype files. /// See also: [`with_dir`][`ProtoPlugin::with_dir`]. /// - /// # Parameters + /// # Arguments /// /// * `dir`: The directory path, relative to the project root. /// @@ -66,7 +66,7 @@ impl ProtoPlugin { /// Creates a [`ProtoPlugin`], using the given vec of paths to find prototype files. /// - /// # Parameters + /// # Arguments /// /// * `dirs`: The directory paths, relative to the project root. /// @@ -91,9 +91,9 @@ impl ProtoPlugin { } } - /// Creates a [`ProtoPlugin`], using the given vec of dirs recursively to find prototype files. + /// Creates a [`ProtoPlugin`], using the given vec of dirs to recursively find prototype files. /// - /// # Parameters + /// # Arguments /// /// * `dirs`: The directory paths, relative to the project root /// diff --git a/src/prototype.rs b/src/prototype.rs index d6ddf5e..a9f3354 100644 --- a/src/prototype.rs +++ b/src/prototype.rs @@ -38,7 +38,7 @@ pub trait Prototypical: 'static + Send + Sync { /// Creates [`ProtoCommands`] used to modify the given entity. /// - /// # Parameters + /// # Arguments /// /// * `entity`: The entity commands /// * `data`: The prototype data in this world @@ -51,7 +51,7 @@ pub trait Prototypical: 'static + Send + Sync { /// Spawns an entity with this prototype's component structure. /// - /// # Parameters + /// # Arguments /// /// * `commands`: The world `Commands` /// * `data`: The prototype data in this world From cad2be6241282bb0973e0f26c84b6446f657a2ed Mon Sep 17 00:00:00 2001 From: "Bruce Reif (Buswolley)" Date: Wed, 9 Mar 2022 11:43:05 -0800 Subject: [PATCH 3/5] split front page doc blocks --- src/lib.rs | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4683711..bd325a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,24 +7,24 @@ //! - [`ProtoPlugin`](plugin::ProtoPlugin) provides configuration for asset loading. //! //! # Examples +//! //! Define a serialized prototype: +//! ```yaml +//! # assets/prototypes/simple-enemy.yaml +//! name: "Simple Enemy" +//! components: +//! - type: Enemy +//! - type: Attack +//! value: +//! damage: 10 +//! ``` +//! +//! Implement `ProtoComponent` for the component types: //! ``` //! use bevy::prelude::*; //! use bevy_proto::prelude::*; //! use serde::{Deserialize, Serialize}; //! -//! // Define a serialized prototype. -//! // In this example we would load this from a .yaml file in "assets/prototypes". -//! -//! // name: "Simple Enemy" -//! // components: -//! // - type: Enemy -//! // - type: Attack -//! // value: -//! // damage: 10 -//! -//! // Implement `ProtoComponent` for the component types: -//! //! #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] //! struct Enemy; //! @@ -32,8 +32,13 @@ //! struct Attack { //! damage: u16 //! } +//! ``` +//! +//! Add the plugin: +//! ``` +//! use bevy::prelude::*; +//! use bevy_proto::prelude::*; //! -//! // Add the plugin: //! fn main() { //! App::new() //! .add_plugins(DefaultPlugins) @@ -50,8 +55,13 @@ //! }) //! }); //! } +//! ``` //! -//! // Finally, spawn a prototype with a system: +//! Finally, spawn a prototype with a system: +//! +//! ``` +//! use bevy::prelude::*; +//! use bevy_proto::prelude::*; //! //! fn spawn_enemy(mut commands: Commands, data: Res, asset_server: Res) { //! let proto = data.get_prototype("Simple Enemy").expect("Prototype doesn't exist!"); From 577d515e3722460f072a284e121eeadd9a59f158 Mon Sep 17 00:00:00 2001 From: "Bruce Reif (Buswolley)" Date: Wed, 9 Mar 2022 12:33:12 -0800 Subject: [PATCH 4/5] add README doc tests, fix some tests --- README.md | 128 +++++++++++++++++++++++++++++++++++++++-------------- src/lib.rs | 12 +++++ 2 files changed, 107 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 697e25b..7e0ccb5 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,8 @@ bevy_proto = "0.3" Then add it to your app like so: ```rust -use bevy_proto::ProtoPlugin; +use bevy::prelude::*; +use bevy_proto::plugin::ProtoPlugin; fn main() { App::new() @@ -72,10 +73,7 @@ fn main() { // add dependent plugins, resources, etc. here // ... // Add this plugin after any other plugins/resources needed for prototype preparation - .add_plugin(ProtoPlugin::default()) - // other plugins, resources, not needed by ProtoPlugin - // ... - .run(); + .add_plugin(ProtoPlugin::default()); } ``` @@ -104,7 +102,9 @@ First, create a struct that implements `ProtoComponent`. This can be done one of For simple components, `ProtoComponent` may be derived: ```rust -use bevy_proto::ProtoComponent; +use serde::{Deserialize, Serialize}; +use bevy::prelude::*; +use bevy_proto::prelude::*; #[derive(Clone, Serialize, Deserialize, ProtoComponent, Component)] struct Movement { @@ -123,12 +123,12 @@ struct Inventory ( Otherwise, you can define them manually (the two attributes are required with this method): ```rust -use bevy_proto::{ProtoComponent, ProtoCommands}; -use bevy::ecs::system::EntityCommands; use bevy::prelude::*; +use bevy::ecs::system::EntityCommands; +use bevy_proto::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] // Required +#[derive(Serialize, Deserialize, Component)] // Required struct Inventory(Option>); #[typetag::serde] // Required @@ -229,6 +229,9 @@ To spawn a prototype, add a system that has access to: Then write something like the following: ```rust +use bevy::prelude::*; +use bevy_proto::prelude::*; + fn spawn_adventurer(mut commands: Commands, data: Res, asset_server: Res) { let proto = data.get_prototype("Adventurer").expect("Prototype doesn't exist!"); @@ -241,11 +244,24 @@ The `spawn(...)` method returns the `EntityCommands` used to create the entity. components, bundles, etc.: ```rust -let adventurer: Entity = proto - .spawn( & mut commands, & data, & asset_server) - .insert(Friendly) - .insert(Named("Bob".to_string())) - .id(); +use bevy::prelude::*; +use bevy_proto::prelude::*; + +#[derive(Component)] +struct Friendly; + +#[derive(Component)] +struct Named(String); + +fn spawn_adventurer(mut commands: Commands, data: Res, asset_server: Res) { + let proto = data.get_prototype("Adventurer").expect("Prototype doesn't exist!"); + + let adventurer: Entity = proto + .spawn(&mut commands, &data, &asset_server) + .insert(Friendly) + .insert(Named("Bob".to_string())) + .id(); +} ``` ### Using Assets @@ -259,21 +275,27 @@ when no longer needed. use bevy::ecs::system::EntityCommands; use bevy::prelude::*; use serde::{Deserialize, Serialize}; -use bevy_proto::{HandlePath, ProtoComponent, ProtoCommands}; +use bevy_proto::prelude::*; -#[derive(Serialize, Deserialize)] +#[derive(Component)] struct Renderable { + pub texture_path: Handle +} + +#[derive(Serialize, Deserialize)] +struct Creature { pub texture_path: HandlePath } #[typetag::serde] impl ProtoComponent for Creature { // Required - fn insert_self(&self, commands: &mut ProtoCommands, asset_server: &Res) { + fn insert_self(&self, proto_commands: &mut ProtoCommands, asset_server: &Res) { let handle: Handle = asset_server.load(self.texture_path.as_str()); + let entity_commands = proto_commands.raw_commands(); - entity.insert(SomeTexture { - texture_handle: handle + entity_commands.insert(Renderable { + texture_path: handle }); } } @@ -286,23 +308,27 @@ be disposed of manually when no longer needed. Setting up an asset is done via t use bevy::ecs::system::EntityCommands; use bevy::prelude::*; use serde::{Deserialize, Serialize}; -use bevy_proto::{HandlePath, ProtoComponent, ProtoCommands, Prototypical}; +use bevy_proto::prelude::*; #[derive(Serialize, Deserialize)] struct Renderable { pub texture_path: HandlePath } +#[derive(Serialize, Deserialize)] +struct Creature { + pub texture_path: HandlePath +} #[typetag::serde] impl ProtoComponent for Creature { // Required - fn insert_self(&self, commands: &mut ProtoCommands, asset_server: &Res) { - let material: Handle = commands + fn insert_self(&self, proto_commands: &mut ProtoCommands, asset_server: &Res) { + let texture: Handle = proto_commands .get_handle(self, &self.texture_path) - .expect("Expected ColorMaterial handle to have been created"); + .expect("Expected Image handle to have been created"); - entity.insert_bundle(SpriteBundle { - material, + proto_commands.raw_commands().insert_bundle(SpriteBundle { + texture, ..Default::default() }); } @@ -310,7 +336,7 @@ impl ProtoComponent for Creature { fn prepare( &self, world: &mut World, - prototype: &Box, + prototype: &dyn Prototypical, data: &mut ProtoData ) { // === Load Handles === // @@ -328,6 +354,9 @@ impl ProtoComponent for Creature { The default Prototype object looks like this: ```rust +use bevy_proto::prelude::*; +use serde::{Deserialize, Serialize}; + #[derive(Serialize, Deserialize)] pub struct Prototype { /// The name of this prototype @@ -342,7 +371,28 @@ pub struct Prototype { However, you can use your own Prototype object. Any struct that implements `Prototypical` can be used in place of the default Prototype. Then you just have to supply your own deserializer to the `ProtoPlugin` object. ```rust -use bevy_proto::{ProtoDataOptions, ProtoDeserializer, ProtoPlugin, Prototypical}; +use serde::{Deserialize, Serialize}; +use bevy::prelude::*; +use bevy::ecs::system::EntityCommands; +use bevy_proto::prelude::*; + +#[derive(Serialize, Deserialize)] +struct CustomPrototype; +impl Prototypical for CustomPrototype { + fn name(&self) -> &str { + "CustomPrototype" + } + fn iter_components(&self) -> std::slice::Iter<'_, std::boxed::Box<(dyn ProtoComponent + 'static)>> { + todo!() + } + fn create_commands<'w, 's, 'a, 'p>( + &'p self, + entity_commands: EntityCommands<'w, 's, 'a>, + proto_data: &'p Res<'_, ProtoData> + ) -> ProtoCommands<'w, 's, 'a, 'p> { + todo!() + } +} #[derive(Clone)] struct CustomProtoDeserializer; @@ -374,10 +424,7 @@ fn main() { // You can also update the allowed extensions within those directories extensions: Some(vec!["yaml", "json"]), }) - }) - // other plugins, resources, not needed by ProtoPlugin - // ... - .run(); + }); } ``` @@ -408,8 +455,23 @@ breakdown of the top current/potential issues: holds onto the handle. This can be prevented by hosting the asset on a separate component and manually creating the handles when spawning that Prototype: ```rust - // Attach fictional OtherComponent with asset "my_asset" which should unload when despawned - prototype.spawn(...).insert(OtherComponent(my_asset)); + use bevy::prelude::*; + use bevy_proto::prelude::*; + + #[derive(Component)] + struct OtherComponent(Handle); + + fn attach( + prototype: T, + my_asset: Handle, + commands: &mut Commands, + data: &Res, + asset_server: &Res, + ) { + // Attach fictional OtherComponent with asset "my_asset" which should unload when despawned + prototype.spawn(commands, data, asset_server).insert(OtherComponent(my_asset)); + } + ``` With all of that said, this package is meant to speed up development and make changes to entity archetypes easier for diff --git a/src/lib.rs b/src/lib.rs index bd325a5..2aa7964 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,3 +90,15 @@ pub mod prelude { pub use super::prototype::{Prototype, Prototypical}; pub use bevy_proto_derive::*; } + +#[cfg(doctest)] +mod test_readme { + macro_rules! external_doc_test { + ($x:expr) => { + #[doc = $x] + extern "C" {} + }; + } + + external_doc_test!(include_str!("../README.md")); +} From 99945d313887dde44b6e1c73b7aacb162ee18af1 Mon Sep 17 00:00:00 2001 From: "Bruce Reif (Buswolley)" Date: Wed, 9 Mar 2022 19:29:05 -0800 Subject: [PATCH 5/5] elide raw_commands lifetime --- README.md | 3 ++- src/data.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e0ccb5..65f5bb7 100644 --- a/README.md +++ b/README.md @@ -326,8 +326,9 @@ impl ProtoComponent for Creature { let texture: Handle = proto_commands .get_handle(self, &self.texture_path) .expect("Expected Image handle to have been created"); + let entity_commands = proto_commands.raw_commands(); - proto_commands.raw_commands().insert_bundle(SpriteBundle { + entity_commands.insert_bundle(SpriteBundle { texture, ..Default::default() }); diff --git a/src/data.rs b/src/data.rs index 74927ea..3368e56 100644 --- a/src/data.rs +++ b/src/data.rs @@ -341,7 +341,7 @@ pub struct ProtoCommands<'w, 's, 'a, 'p> { impl<'w, 's, 'a, 'p> ProtoCommands<'w, 's, 'a, 'p> { /// Get raw access to [`EntityCommands`] - pub fn raw_commands(&'p mut self) -> &'p mut EntityCommands<'w, 's, 'a> { + pub fn raw_commands(&mut self) -> &mut EntityCommands<'w, 's, 'a> { &mut self.commands } /// Get the associated prototype