Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

document public items, fix doc tests #14

Merged
merged 5 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand All @@ -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
// ...
Expand Down
73 changes: 72 additions & 1 deletion src/components.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,83 @@
//! 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 type inserts components into an entity.
///
/// 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`].
///
/// 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
///
/// 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, Component, ProtoComponent)]
/// pub struct Movement {
/// speed: u16,
/// }
///
/// // Also works on tuple structs:
/// #[derive(Clone, Serialize, Deserialize, Component, ProtoComponent)]
/// struct Inventory(Option<Vec<String>>);
/// ```
///
/// 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].
///
/// For other cases, [`ProtoComponent`] can be implemented manually:
///
/// ```
/// use serde::{Deserialize, Serialize};
/// use bevy::prelude::*;
/// use bevy::ecs::system::EntityCommands;
/// use bevy_proto::prelude::*;
///
/// // 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<String>);
///
/// // We will also insert this separate `QuestItems` struct.
/// #[derive(Clone, Serialize, Deserialize, Component)]
/// struct QuestItems(Vec<String>);
///
/// #[typetag::serde] // Required
/// impl ProtoComponent for Inventory {
/// // 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<AssetServer>) {
/// 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.
fn insert_self(&self, commands: &mut ProtoCommands, asset_server: &Res<AssetServer>);
/// 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) {}
}
46 changes: 30 additions & 16 deletions src/data.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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
Expand Down Expand Up @@ -45,6 +47,7 @@ pub struct ProtoData {
}

impl ProtoData {
/// Creates a new, empty instance of [`ProtoData`].
pub fn empty() -> Self {
Self {
handles: HashMap::default(),
Expand Down Expand Up @@ -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<AssetServer>, mut data: ResMut<ProtoData>) {
/// let comp = MyComponent {
Expand All @@ -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<Image> = asset_server.load(comp.texture_path.0.as_str());
/// let handle: Handle<Image> = asset_server.load(comp.texture_path.0.as_str());
///
/// data.insert_handle(&proto, &comp, &comp.texture_path, handle);
/// }
Expand Down Expand Up @@ -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
///
Expand All @@ -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<Box<dyn Prototypical>> {
/// if let Ok(value) = serde_yaml::from_str::<Prototype>(data) {
/// Some(Box::new(value))
Expand All @@ -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<String>,
/// 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,
Expand All @@ -472,17 +475,17 @@ pub struct ProtoDataOptions {
/// };
/// ```
pub recursive_loading: bool,
/// A custom deserializer for prototypes
/// A custom deserializer for prototypes.
pub deserializer: Box<dyn ProtoDeserializer + Send + Sync>,
/// 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.
///
/// # Examples
///
/// ```
/// use bevy_proto::ProtoDataOptions;
/// use bevy_proto::data::ProtoDataOptions;
///
/// let opts = ProtoDataOptions {
/// // Only allow .yaml or .json files
Expand All @@ -492,3 +495,14 @@ pub struct ProtoDataOptions {
/// ```
pub extensions: Option<Vec<&'static str>>,
}

impl Default for ProtoDataOptions {
fn default() -> Self {
Self {
directories: Default::default(),
recursive_loading: Default::default(),
deserializer: Box::new(DefaultProtoDeserializer),
extensions: Default::default(),
}
}
}
67 changes: 66 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,67 @@
#![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.
MrGVSV marked this conversation as resolved.
Show resolved Hide resolved
//!
//! # Examples
//! Define a serialized prototype:
MrGVSV marked this conversation as resolved.
Show resolved Hide resolved
//! ```
//! 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<ProtoData>, asset_server: Res<AssetServer>) {
//! 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;
Expand All @@ -8,10 +72,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).
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth suggesting that it's often best used as use bevy_proto::prelude::*? Or is it better to just let the user decide how they want to use it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might depend on how they're using it, and if they're using all of the bevy_proto types in the same place. I'd just let users decide how to use it.

On a related note I was thinking we could reduce the number of public modules. Specifically, we could hide components and plugin and just pub use components::ProtoComponent and pub use plugin::ProtoPlugin since those mods don't contain anything else anyway.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's do that.


pub use super::components::*;
pub use super::data::*;
pub use super::plugin::*;
pub use super::prototype::{Prototype, Prototypical};
pub use bevy_proto_derive::*;
}
Loading