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 all 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
135 changes: 99 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,16 @@ 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()
.add_plugins(DefaultPlugins)
// 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());
}
```

Expand Down Expand Up @@ -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 {
Expand All @@ -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<Vec<String>>);

#[typetag::serde] // Required
Expand Down Expand Up @@ -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<ProtoData>, asset_server: Res<AssetServer>) {
let proto = data.get_prototype("Adventurer").expect("Prototype doesn't exist!");

Expand All @@ -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<ProtoData>, asset_server: Res<AssetServer>) {
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
Expand All @@ -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<Image>
}

#[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<AssetServer>) {
fn insert_self(&self, proto_commands: &mut ProtoCommands, asset_server: &Res<AssetServer>) {
let handle: Handle<Image> = 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
});
}
}
Expand All @@ -286,31 +308,36 @@ 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<AssetServer>) {
let material: Handle<ColorMaterial> = commands
fn insert_self(&self, proto_commands: &mut ProtoCommands, asset_server: &Res<AssetServer>) {
let texture: Handle<Image> = proto_commands
.get_handle(self, &self.texture_path)
.expect("Expected ColorMaterial handle to have been created");
.expect("Expected Image handle to have been created");
let entity_commands = proto_commands.raw_commands();

entity.insert_bundle(SpriteBundle {
material,
entity_commands.insert_bundle(SpriteBundle {
texture,
..Default::default()
});
}

fn prepare(
&self,
world: &mut World,
prototype: &Box<dyn Prototypical>,
prototype: &dyn Prototypical,
data: &mut ProtoData
) {
// === Load Handles === //
Expand All @@ -328,6 +355,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
Expand All @@ -342,7 +372,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;
Expand All @@ -359,11 +410,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,11 +424,8 @@ 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
// ...
.run();
})
});
}
```

Expand Down Expand Up @@ -408,8 +456,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<Image>);

fn attach<T: Prototypical>(
prototype: T,
my_asset: Handle<Image>,
commands: &mut Commands,
data: &Res<ProtoData>,
asset_server: &Res<AssetServer>,
) {
// 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
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) {}
}
Loading