Skip to content

Commit

Permalink
Merge pull request #34 from MrGVSV/schematic-context
Browse files Browse the repository at this point in the history
Optional spawning and `SchematicContext`
  • Loading branch information
MrGVSV authored Apr 30, 2023
2 parents 190ed69 + 5c84ecc commit 658f42a
Show file tree
Hide file tree
Showing 34 changed files with 490 additions and 404 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,18 @@ This crate is mostly feature-complete.
There are a few more things I think would be really nice to add. Below is a list of my current goals and what's been
accomplished thus far:

| Goal | Status |
|--------------------------------------------------|--------------------|
| Reflection support | :white_check_mark: |
| Nested prototypes | :white_check_mark: |
| Package-specifier | :construction: |
| Configurable schematics filtering and processing | :construction: |
| Prototype arguments | :construction: |
| Entity-less prototypes | :construction: |
| Value access | :construction: |
| Custom file format support | :construction: |
| Improved documentation | :construction: |
| Benchmarks | :construction: |
| Goal | Status |
|--------------------------------------------------|--------|
| Reflection support | |
| Nested prototypes | |
| Package-specifier | 🚧 |
| Configurable schematics filtering and processing | 🚧 |
| Prototype arguments | 🚧 |
| Entity-less prototypes | |
| Value access | 🚧 |
| Custom file format support | 🚧 |
| Improved documentation | 🚧 |
| Benchmarks | 🚧 |

## 🕰 Previous Versions

Expand Down
1 change: 0 additions & 1 deletion assets/examples/basic_schematic/Player.prototype.ron
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
schematics: {
"basic_schematic::Playable": (),
"basic_schematic::Alignment": Good,
"basic_schematic::PlayerHealth": (100),
// This schematic is provided by `bevy_proto` and resolves
// to a Bevy `SpriteBundle`:
"bevy_proto::custom::SpriteBundle": (
Expand Down
9 changes: 9 additions & 0 deletions assets/examples/basic_schematic/PlayerConfig.prototype.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(
name: "PlayerConfig",
// Since this prototype only contains a resource,
// we can mark it as not needing an entity to be spawned.
entity: false,
schematics: {
"basic_schematic::MaxPlayers": (1),
}
)
10 changes: 4 additions & 6 deletions bevy_proto_backend/src/impls/bevy_impls/asset.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use bevy::app::App;
use bevy::asset::{Asset, AssetServer, Handle};
use bevy::ecs::world::EntityMut;

use crate::impls::macros::register_schematic;
use bevy_proto_derive::impl_external_schematic;

use crate::proto::ProtoAsset;
use crate::schematics::FromSchematicInput;
use crate::tree::EntityTree;
use crate::schematics::{FromSchematicInput, SchematicContext};

#[allow(unused_variables)]
pub(super) fn register(app: &mut App) {
Expand Down Expand Up @@ -70,10 +68,10 @@ impl_external_schematic! {
struct Handle<T: Asset> {}
// ---
impl<T: Asset> FromSchematicInput<ProtoAsset> for Handle<T> {
fn from_input(input: ProtoAsset, entity: &mut EntityMut, _: &EntityTree) -> Self {
fn from_input(input: ProtoAsset, context: &mut SchematicContext) -> Self {
match input {
ProtoAsset::AssetPath(path) => entity.world().resource::<AssetServer>().load(path),
ProtoAsset::HandleId(handle_id) => entity.world().resource::<AssetServer>().get_handle(handle_id),
ProtoAsset::AssetPath(path) => context.world().resource::<AssetServer>().load(path),
ProtoAsset::HandleId(handle_id) => context.world().resource::<AssetServer>().get_handle(handle_id),
}
}
}
Expand Down
15 changes: 7 additions & 8 deletions bevy_proto_backend/src/impls/bevy_impls/text.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use bevy::app::App;
use bevy::ecs::world::EntityMut;
use bevy::math::Vec2;
use bevy::reflect::{std_traits::ReflectDefault, FromReflect, Reflect};
use bevy::text::{BreakLineOn, Text, Text2dBounds, TextAlignment, TextSection, TextStyle};

use crate::impls::macros::{from_to, from_to_default, from_to_input, register_schematic};
use crate::proto::{ProtoAsset, ProtoColor};
use crate::schematics::FromSchematicInput;
use crate::schematics::{FromSchematicInput, SchematicContext};
use bevy_proto_derive::impl_external_schematic;

pub(super) fn register(app: &mut App) {
Expand Down Expand Up @@ -34,10 +33,10 @@ impl_external_schematic! {
from_to_input! {
Text,
TextInput,
move |input: Input, entity: &mut EntityMut, tree| {
move |input: Input, context: &mut SchematicContext| {
let mut sections = Vec::with_capacity(input.sections.len());
for section in input.sections {
sections.push(FromSchematicInput::from_input(section, &mut *entity, tree));
sections.push(FromSchematicInput::from_input(section, &mut *context));
}

Self {
Expand Down Expand Up @@ -66,9 +65,9 @@ impl_external_schematic! {
from_to_input! {
TextSection,
TextSectionInput,
|input: Input, entity, tree| Self {
|input: Input, context| Self {
value: input.value,
style: FromSchematicInput::from_input(input.style, entity, tree)
style: FromSchematicInput::from_input(input.style, context)
}
}

Expand All @@ -81,8 +80,8 @@ impl_external_schematic! {
from_to_input! {
TextStyle,
TextStyleInput,
|input: Input, entity, tree| Self {
font: FromSchematicInput::from_input(input.font, entity, tree),
|input: Input, context| Self {
font: FromSchematicInput::from_input(input.font, context),
font_size: input.font_size,
color: input.color.into(),
}
Expand Down
10 changes: 4 additions & 6 deletions bevy_proto_backend/src/impls/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,9 @@ macro_rules! from_to_input {
impl $crate::schematics::FromSchematicInput<Input> for $mock {
fn from_input(
input: Input,
entity: &mut bevy::ecs::world::EntityMut,
tree: &$crate::tree::EntityTree,
context: &mut $crate::schematics::SchematicContext,
) -> Self {
$body(input, entity, tree)
$body(input, context)
}
}
};
Expand All @@ -92,10 +91,9 @@ macro_rules! from_to_input {
impl $crate::schematics::FromSchematicInput<Input> for $real {
fn from_input(
input: Input,
entity: &mut bevy::ecs::world::EntityMut,
tree: &$crate::tree::EntityTree,
context: &mut $crate::schematics::SchematicContext,
) -> Self {
$body(input, entity, tree)
$body(input, context)
}
}
};
Expand Down
147 changes: 87 additions & 60 deletions bevy_proto_backend/src/proto/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ use std::marker::PhantomData;

use bevy::asset::Assets;
use bevy::ecs::system::{Command, EntityCommands, SystemParam};
use bevy::ecs::world::EntityMut;
use bevy::prelude::{Commands, Entity, Mut, World};

use crate::proto::{Config, Prototypical};
use crate::registration::ProtoRegistry;
use crate::schematics::DynamicSchematic;
use crate::tree::{EntityTree, EntityTreeNode};
use crate::schematics::{DynamicSchematic, SchematicContext};
use crate::tree::EntityTreeNode;

/// A system parameter similar to [`Commands`], but catered towards [prototypes].
///
Expand Down Expand Up @@ -39,6 +38,29 @@ impl<'w, 's, T: Prototypical> ProtoCommands<'w, 's, T> {
ProtoEntityCommands::new(self.commands.spawn_empty().id(), self)
}

/// Apply the prototype with the given [ID] to the world.
///
/// This should only be called on prototypes that do not [require an entity].
/// To spawn this prototype as a new entity, use [`spawn`] instead.
///
/// [ID]: Prototypical::id
/// [require an entity]: Prototypical::require_entity
/// [`spawn`]: Self::spawn
pub fn apply<I: Into<T::Id>>(&mut self, id: I) {
self.add(ProtoInsertCommand::<T>::new(id.into(), None));
}

/// Remove the prototype with the given [ID] from the world.
///
/// This should only be called on prototypes that do not [require an entity].
/// To remove this prototype from an entity, use [`ProtoEntityCommands::remove`] instead.
///
/// [ID]: Prototypical::id
/// [require an entity]: Prototypical::require_entity
pub fn remove<I: Into<T::Id>>(&mut self, id: I) {
self.add(ProtoInsertCommand::<T>::new(id.into(), None));
}

/// Get the [`ProtoEntityCommands`] for the given entity.
///
/// This internally calls [`Commands::entity`].
Expand Down Expand Up @@ -118,7 +140,7 @@ impl<'w, 's, 'a, T: Prototypical> ProtoEntityCommands<'w, 's, 'a, T> {
pub fn insert<I: Into<T::Id>>(&mut self, id: I) -> &mut Self {
let id = id.into();
self.proto_commands
.add(ProtoInsertCommand::<T>::new(id, self.entity));
.add(ProtoInsertCommand::<T>::new(id, Some(self.entity)));
self
}

Expand All @@ -128,7 +150,7 @@ impl<'w, 's, 'a, T: Prototypical> ProtoEntityCommands<'w, 's, 'a, T> {
pub fn remove<I: Into<T::Id>>(&mut self, id: I) -> &mut Self {
let id = id.into();
self.proto_commands
.add(ProtoRemoveCommand::<T>::new(id, self.entity));
.add(ProtoRemoveCommand::<T>::new(id, Some(self.entity)));
self
}

Expand All @@ -152,7 +174,7 @@ pub struct ProtoInsertCommand<T: Prototypical> {
}

impl<T: Prototypical> ProtoInsertCommand<T> {
pub fn new(id: T::Id, entity: Entity) -> Self {
pub fn new(id: T::Id, entity: Option<Entity>) -> Self {
Self {
data: ProtoCommandData { id, entity },
}
Expand All @@ -164,8 +186,8 @@ impl<T: Prototypical> Command for ProtoInsertCommand<T> {
self.data.assert_is_registered(world);

self.data
.for_each_schematic(world, true, |schematic, entity, tree| {
schematic.apply(entity, tree).unwrap();
.for_each_schematic(world, true, |schematic, context| {
schematic.apply(context).unwrap();
});
}
}
Expand All @@ -179,7 +201,7 @@ pub struct ProtoRemoveCommand<T: Prototypical> {
}

impl<T: Prototypical> ProtoRemoveCommand<T> {
pub fn new(id: T::Id, entity: Entity) -> Self {
pub fn new(id: T::Id, entity: Option<Entity>) -> Self {
Self {
data: ProtoCommandData { id, entity },
}
Expand All @@ -191,15 +213,15 @@ impl<T: Prototypical> Command for ProtoRemoveCommand<T> {
self.data.assert_is_registered(world);

self.data
.for_each_schematic(world, false, |schematic, entity, tree| {
schematic.remove(entity, tree).unwrap();
.for_each_schematic(world, false, |schematic, context| {
schematic.remove(context).unwrap();
});
}
}

struct ProtoCommandData<T: Prototypical> {
id: T::Id,
entity: Entity,
entity: Option<Entity>,
}

impl<T: Prototypical> ProtoCommandData<T> {
Expand All @@ -225,7 +247,7 @@ impl<T: Prototypical> ProtoCommandData<T> {

fn for_each_entity<F>(&self, world: &mut World, is_apply: bool, callback: F)
where
F: Fn(&EntityTreeNode, &mut EntityMut, &EntityTree, &Assets<T>, &mut T::Config),
F: Fn(&EntityTreeNode, &mut SchematicContext, &Assets<T>, &mut T::Config),
{
world.resource_scope(|world: &mut World, registry: Mut<ProtoRegistry<T>>| {
world.resource_scope(|world: &mut World, mut config: Mut<T::Config>| {
Expand All @@ -238,17 +260,19 @@ impl<T: Prototypical> ProtoCommandData<T> {
for node in entity_tree.iter() {
entity_tree.set_current(node);

let mut entity = world.entity_mut(node.entity());
let mut context = SchematicContext::new(world, &entity_tree);

#[cfg(feature = "auto_name")]
if is_apply && !entity.contains::<bevy::core::Name>() {
entity.insert(bevy::core::Name::new(format!(
"{} (Prototype)",
node.id()
)));
if let Some(mut entity) = context.entity_mut() {
if is_apply && !entity.contains::<bevy::core::Name>() {
entity.insert(bevy::core::Name::new(format!(
"{} (Prototype)",
node.id()
)));
}
}

callback(node, &mut entity, &entity_tree, &prototypes, &mut config);
callback(node, &mut context, &prototypes, &mut config);
}
})
})
Expand All @@ -261,48 +285,51 @@ impl<T: Prototypical> ProtoCommandData<T> {
/// [prototype]: Prototypical
fn for_each_schematic<F>(&self, world: &mut World, is_apply: bool, callback: F)
where
F: Fn(&DynamicSchematic, &mut EntityMut, &EntityTree),
F: Fn(&DynamicSchematic, &mut SchematicContext),
{
self.for_each_entity(
world,
is_apply,
|node, entity, entity_tree, prototypes, config| {
let on_before_prototype = if is_apply {
Config::<T>::on_before_apply_prototype
} else {
Config::<T>::on_before_remove_prototype
};
let on_after_prototype = if is_apply {
Config::<T>::on_after_apply_prototype
} else {
Config::<T>::on_after_remove_prototype
};
let on_before_schematic = if is_apply {
Config::<T>::on_before_apply_schematic
} else {
Config::<T>::on_before_remove_schematic
};
let on_after_schematic = if is_apply {
Config::<T>::on_after_apply_schematic
} else {
Config::<T>::on_after_remove_schematic
};

for handle_id in node.prototypes() {
let handle = prototypes.get_handle(*handle_id);
let proto = prototypes.get(&handle).unwrap();

on_before_prototype(config, proto, entity, entity_tree);

for (_, schematic) in proto.schematics().iter() {
on_before_schematic(config, schematic, entity, entity_tree);
callback(schematic, entity, entity_tree);
on_after_schematic(config, schematic, entity, entity_tree);
}
self.for_each_entity(world, is_apply, |node, context, prototypes, config| {
let on_before_prototype = if is_apply {
Config::<T>::on_before_apply_prototype
} else {
Config::<T>::on_before_remove_prototype
};
let on_after_prototype = if is_apply {
Config::<T>::on_after_apply_prototype
} else {
Config::<T>::on_after_remove_prototype
};
let on_before_schematic = if is_apply {
Config::<T>::on_before_apply_schematic
} else {
Config::<T>::on_before_remove_schematic
};
let on_after_schematic = if is_apply {
Config::<T>::on_after_apply_schematic
} else {
Config::<T>::on_after_remove_schematic
};

for handle_id in node.prototypes() {
let handle = prototypes.get_handle(*handle_id);
let proto = prototypes.get(&handle).unwrap();

if proto.requires_entity() && !context.entity().is_some() {
panic!(
"could not apply command for prototype {:?}: requires entity",
proto.id()
);
}

on_before_prototype(config, proto, context);

on_after_prototype(config, proto, entity, entity_tree);
for (_, schematic) in proto.schematics().iter() {
on_before_schematic(config, schematic, context);
callback(schematic, context);
on_after_schematic(config, schematic, context);
}
},
);

on_after_prototype(config, proto, context);
}
});
}
}
Loading

0 comments on commit 658f42a

Please sign in to comment.