diff --git a/README.md b/README.md index 584d60a..80fd704 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,4 @@ Arbitrary data can be passed into the macro using normal rust blocks. The macro returns portable `Template` values, which can be spliced into other templates using `@{ ... }`. Not only is the macro declarative and composable, it also supports basic incrementalization (doing partial updates to the ecs rather than rebuilding from scratch). -Building the same macro multiple times with `commands.build(template)` does only the work necessary to bring the ecs into alignment with the template. +Building the same macro multiple times only does the work necessary to bring the `entity` into alignment with the template. diff --git a/examples/scene_tree.rs b/examples/scene_tree.rs new file mode 100644 index 0000000..9116844 --- /dev/null +++ b/examples/scene_tree.rs @@ -0,0 +1,171 @@ +use i_cant_believe_its_not_bsn::*; + +use bevy::color::palettes::*; +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(scene_tree_plugin) + .run(); +} + +/// Plugin for the editor scene tree pane. +fn scene_tree_plugin(app: &mut App) { + app.add_systems(Startup, mock_setup_pane); + app.add_systems(Update, build_scene_tree); +} + +const DEFAULT_BACKGROUND: Srgba = tailwind::NEUTRAL_600; +const SELECTED_BACKGROUND: Srgba = tailwind::NEUTRAL_700; +const HOVERED_BACKGROUND: Srgba = tailwind::NEUTRAL_500; + +// Mock an editor pane. +fn mock_setup_pane(mut commands: Commands) { + commands.insert_resource(Selection { entity: None }); + + commands.spawn((Name::new("Camera"), Camera2d, Hover { count: 0 })); + commands.spawn((Name::new("Example Entity"), Hover { count: 0 })); + + commands.spawn_empty().build(template! { + // A full-screen node which centers it's content + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + ..default() + } => [ + // The actual window we will use as the root of the scene tree + ( + SceneTreeRoot, + Node { + flex_direction: FlexDirection::Column, + column_gap: Val::Px(2.0), + padding: UiRect::all(Val::Px(8.0)), + ..default() + }, + BackgroundColor(DEFAULT_BACKGROUND.into()), + ); + ]; + }); + + // Add observers to drive input interaction. + commands.add_observer(select_row); + commands.add_observer(hover_over_row); + commands.add_observer(hover_leave_row); +} + +// Build the scene tree row template. +fn build_scene_tree( + mut commands: Commands, + scene_tree: Single>, + query: Query<(Entity, &Name, Option<&Hover>)>, + selection: Res, +) { + let template: Template = query + .iter() + .map(|(entity, name, hover)| scene_tree_row(entity, name, hover, &selection)) + .flatten() + .collect(); + + commands.entity(*scene_tree).build_children(template); +} + +// Generate scene tree template. +fn scene_tree_row( + target_entity: Entity, + target_name: &str, + hover: Option<&Hover>, + selection: &Selection, +) -> Template { + let is_hovered = hover.map(|h| h.count).unwrap_or(0) > 0; + let is_selected = selection.entity == Some(target_entity); + + template! { + {target_name}: ( + SceneTreeRow(target_entity), + Node { + padding: UiRect::all(Val::Px(4.0)), + align_items: AlignItems::Center, + ..default() + }, + BorderRadius::all(Val::Px(4.0)), + BackgroundColor( + if is_selected { SELECTED_BACKGROUND.into() } else if is_hovered { HOVERED_BACKGROUND.into() } else { Color::NONE } + ) + ) => [ + (Text::new(target_name), TextFont { font_size: 11.0, ..default() }, PickingBehavior::IGNORE); + ]; + } +} + +/// Root UI node of the scene tree. +#[derive(Component)] +struct SceneTreeRoot; + +/// A scene tree row UI node. +/// +/// Contains the row's corresponding scene entity. +#[derive(Component)] +struct SceneTreeRow(Entity); + +/// The selected entity +#[derive(Resource)] +struct Selection { + entity: Option, +} + +// Mark selection of scene tree rows. +fn select_row( + trigger: Trigger>, + rows: Query<&SceneTreeRow>, + mut selection: ResMut, +) { + if let Ok(SceneTreeRow(target_entity)) = rows.get(trigger.target) { + selection.entity = Some(*target_entity); + } +} + +/// The selected entity +#[derive(Component)] +struct Hover { + count: usize, +} + +// Notice how the template pattern encourages you to seperate state and +// presentation. Components used for presentation (those generated by +// `Template`) are overwritten every frame, so we have to store the state else +// where in the ecs. Template state does not have to be derived, but it's often +// easier if it is. +// +// In this case, we store 'hover' information not on the rows themselves but on +// the entities they represent. This would let us centralize hover logic if we +// had other ways to hover entities than the list, for example if they had +// meshes. But I havn't bothered to implement that. + +// Track hover +fn hover_over_row( + trigger: Trigger>, + rows: Query<&SceneTreeRow>, + mut hovers: Query<&mut Hover>, +) { + if let Ok(SceneTreeRow(target_entity)) = rows.get(trigger.target) { + if let Ok(mut hover) = hovers.get_mut(*target_entity) { + hover.count += 1; + } + } +} + +// Track hover +fn hover_leave_row( + trigger: Trigger>, + rows: Query<&SceneTreeRow>, + mut hovers: Query<&mut Hover>, +) { + if let Ok(SceneTreeRow(target_entity)) = rows.get(trigger.target) { + if let Ok(mut hover) = hovers.get_mut(*target_entity) { + hover.count -= 1; + } + } +} diff --git a/examples/super-sheep-counter-2000.rs b/examples/super-sheep-counter-2000.rs index 833c9df..0cd6ad9 100644 --- a/examples/super-sheep-counter-2000.rs +++ b/examples/super-sheep-counter-2000.rs @@ -22,8 +22,12 @@ fn sheep_plugin(app: &mut App) { fn setup(mut commands: Commands) { commands.spawn(Camera2d); + commands.spawn(UiRoot); } +#[derive(Component)] +struct UiRoot; + #[derive(Component)] struct Sheep; @@ -34,35 +38,32 @@ enum Button { } // A query that pulls data from the ecs and then updates it using a template. -fn sheep_system(mut commands: Commands, sheep: Query<&Sheep>) { +fn sheep_system(mut commands: Commands, sheep: Query<&Sheep>, root: Single>) { let num_sheep = sheep.iter().len(); - let template = template!( - { - Node { - position_type: PositionType::Absolute, - bottom: Val::Px(5.0), - right: Val::Px(5.0), - ..default() - } - } [ + let template = template! { + Node { + position_type: PositionType::Absolute, + bottom: Val::Px(5.0), + right: Val::Px(5.0), + ..default() + } => [ @{ counter(num_sheep, "sheep", Button::Increment, Button::Decrement) }; ]; - ); + }; - commands.build(template); + commands.entity(*root).build(template); } // A function that returns an ecs template. fn counter(num: usize, name: &str, inc: T, dec: T) -> Template { template! { - { Text::new("You have ") } - [ - { TextSpan::new(format!("{num}")) }; - { TextSpan::new(format!(" {name}!")) }; + Text::new("You have ") => [ + TextSpan::new(format!("{num}")); + TextSpan::new(format!(" {name}!")); ]; - {( Button, Text::new("Increase"), TextColor(css::GREEN.into()), inc, visible_if(num < 100) )}; - {( Button, Text::new("Decrease"), TextColor(css::RED.into()), dec, visible_if(num > 0) )}; + ( Button, Text::new("Increase"), TextColor(css::GREEN.into()), inc, visible_if(num < 100) ); + ( Button, Text::new("Decrease"), TextColor(css::RED.into()), dec, visible_if(num > 0) ); } } diff --git a/src/template.rs b/src/template.rs index 3b34598..89773a6 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,35 +1,57 @@ use std::collections::{HashMap, HashSet}; +use std::mem; -use bevy_ecs::{component::ComponentId, prelude::*}; +use bevy_ecs::{ + component::ComponentId, prelude::*, system::IntoObserverSystem, world::DeferredWorld, +}; use bevy_hierarchy::prelude::*; -/// A template is an ordered collection of heterogenous prototypes, which can be -/// inserted into the world. Returned by the [`template`] macro. -pub type Template = Vec>; +// ----------------------------------------------------------------------------- +// Templates and fragments -trait BuildTemplate { - /// Builds a template onto the world. - /// - /// Each top-level prototype in the template will be built on a different - /// entity. Each prototype's name is used to determine what entity to build - /// it on, so naming root level entities is recomended. Unamed prototypes - /// are indexed according to order. Different templates *will* conflict if - /// they share the same root names or if root names are ommited on both. - /// - /// For information about what happens when a prototype is built on a - /// specific entity, see [`Prototype::build`]. - fn build(self, world: &mut World); +/// A fragment is a tree of bundles with optional names. This is typically built +/// using the [`template!`](crate::template!) macro. +pub struct Fragment { + /// The name of the fragment, used to identify children across builds. + pub name: Option, + /// The bundle to be inserted on the entity. + pub bundle: BoxedBundle, + /// The template for the children. This boils down to a type-erased + /// `Fragment` vector. + pub children: Template, +} + +/// A template is a list of fragments. Each fragment in the list is expected to +/// have either a unique name, or no specific name. +pub type Template = Vec; + +pub trait BuildTemplate { + /// Builds the template on a node. The fragments in the template become the + /// children of the node. + fn build( + self, + entity_id: Entity, + world: &mut World, + current_anchors: HashMap, + ) -> HashMap; } impl BuildTemplate for Template { - fn build(self, world: &mut World) { - world.init_resource::(); - world.resource_scope(|world, mut root: Mut| { - let mut i = 0; - for prototype in self.into_iter() { - // Determine the anchor for the prototype. - let prototype_anchor = match prototype.name() { - Some(name) => Anchor::Named(name.to_string()), + fn build( + self, + entity_id: Entity, + world: &mut World, + mut current_anchors: HashMap, + ) -> HashMap { + // Get or create an entity for each fragment. + let mut i = 0; + let fragments: Vec<_> = self + .into_iter() + .map(|fragment| { + // Compute the anchor for this fragment, using it's name if supplied + // or an auto-incrementing counter if not. + let anchor = match fragment.name { + Some(ref name) => Anchor::Named(name.clone()), None => { let anchor = Anchor::Auto(i); i += 1; @@ -37,205 +59,317 @@ impl BuildTemplate for Template { } }; - // Get or create the receipt for that anchor. - let root_receipt = root.receipts.entry(prototype_anchor).or_default(); + // Find the existing child entity based on the anchor, or spawn a + // new one. + let entity_id = current_anchors + .remove(&anchor) + .unwrap_or_else(|| world.spawn_empty().id()); + + // Store the fragment, it's anchor, and it's entity id. + (fragment, anchor, entity_id) + }) + .collect(); + + // Clear any remaining orphans from the previous template. We do this + // first (before deparenting) so that hooks still see the parent when + // they run. + for orphan_id in current_anchors.into_values() { + world.entity_mut(orphan_id).despawn_recursive(); + } + + // Position the entities as children. + let mut entity = world.entity_mut(entity_id); + let child_entities: Vec<_> = fragments.iter().map(|(_, _, entity)| *entity).collect(); + entity.replace_children(&child_entities); - // Build the prototype. - prototype.build(world, root_receipt); - } - }); + // Build the children and produce the receipts. It's important that this + // happens *after* the entities are positioned as children to make hooks + // work correctly. + fragments + .into_iter() + .map(|(fragment, anchor, entity_id)| { + fragment.build(entity_id, world); + (anchor, entity_id) + }) + .collect() } } -pub trait WorldTemplateExt { - /// Builds a template. See [`BuildTemplate::build`] for more documentation. - fn build(&mut self, template: Template); +pub enum TemplateIntoFragmentError { + Empty, + MultipleFragments(usize), } -impl WorldTemplateExt for World { - fn build(&mut self, template: Template) { - template.build(self) +impl TryInto for Template { + type Error = TemplateIntoFragmentError; + + fn try_into(mut self) -> Result { + match self.len() { + 0 => Err(TemplateIntoFragmentError::Empty), + 1 => Ok(self.pop().unwrap()), + n => Err(TemplateIntoFragmentError::MultipleFragments(n)), + } } } -/// A command for building a template. The shorthand for this is -/// [`CommandsTemplateExt::build`]. See [`BuildTemplate::build`] for more -/// documentation. -pub struct BuildTemplateCommand(Template); - -impl Command for BuildTemplateCommand { - fn apply(self, world: &mut World) { - self.0.build(world) +impl Default for Fragment { + fn default() -> Fragment { + Fragment { + name: None, + bundle: BoxedBundle::from(()), + children: Template::default(), + } } } -pub trait CommandsTemplateExt { - /// Builds a template. See [`BuildTemplate::build`] for more documentation. - fn build(&mut self, template: Template); +// ----------------------------------------------------------------------------- +// Dynamically typed bundles + +/// Bundles are not dyn-compatable, which means they cannot be boxed. This +/// trait provides a dyn-compatable alternative. +pub trait ErasedBundle { + /// Inserts a bundle on the specified entity, and removes components present + /// in the provided hash set which are no-longer needed. + fn build( + self: Box, + entity_id: Entity, + world: &mut World, + current_components: HashSet, + ) -> HashSet; } -impl<'w, 's> CommandsTemplateExt for Commands<'w, 's> { - fn build(&mut self, template: Template) { - self.queue(BuildTemplateCommand(template)); +impl ErasedBundle for B +where + B: Bundle, +{ + fn build( + self: Box, + entity_id: Entity, + world: &mut World, + current_components: HashSet, + ) -> HashSet { + // Collect set of component ids present in the bundle + let mut new_components = HashSet::new(); + B::get_component_ids(world.components(), &mut |maybe_id| { + if let Some(id) = maybe_id { + new_components.insert(id); + } + }); + + // Insert the bundle + let mut entity = world.entity_mut(entity_id); + entity.insert(*self); + + // Remove the components in the previous bundle but not this one + for component_id in current_components.difference(&new_components) { + entity.remove_by_id(*component_id); + } + + // Return the new set of components. + new_components } } -/// Identifies data in a `Receipt` based on the positon and name of a -/// `Prototype`. -#[derive(Hash, Eq, PartialEq)] -enum Anchor { - /// If a name is ommited from a the prototype, it will be given an - /// incrementing id. - Auto(u64), - /// If a name is provided, we use that. - Named(String), +/// A Boxed version of a bundle, built using [`ErasedBundle`]. +pub struct BoxedBundle { + inner: Box, } -/// A prototype is the type-erased trait form of a [`Fragment`] contained within -/// a [`Template`]. It has a name, and can be inserted into the world multiple -/// times, updating it's previous value each time. -/// -/// This trait is mostly needed to get around `Bundle` not being dyn compatible. -pub trait Prototype { - /// Returns the name of this prototype. - fn name(&self) -> Option; +impl From for BoxedBundle +where + B: Bundle, +{ + fn from(bundle: B) -> BoxedBundle { + BoxedBundle { + inner: Box::new(bundle), + } + } +} - /// Builds the prototype on a specific entity. - /// - /// The prototype uses a receipt to keep track of the state it left the - /// world in when it was last built. The first time it is built, it should - /// use the default receipt. The next time it is built, you should pass the - /// same receipt back in. - /// - /// The receipt is used to clean up old values after which were previously - /// included in the template and now are not. Components added by the - /// previous template but not the current one are removed. Children not - /// added by the current template are despawned recursively. The children - /// are also re-ordered to match the template. - /// - /// Where possible, this function tries to re-use existing entities instead - /// of spawning new ones. - /// - /// To instead build an entire `Template` at the root level, see - /// [`BuildTemplate::build`]. - fn build(self: Box, world: &mut World, receipt: &mut Receipt); +// ----------------------------------------------------------------------------- +// Callbacks and observers + +/// This is a helper for adding observers to a `template` macro. +/// +/// ``` +/// # use i_cant_believe_its_not_bsn::*; +/// # use bevy::prelude::*; +/// template!{ +/// Name::new("MyEntity") => [ +/// on(|trigger: Trigger>| { +/// // Do something when "MyEntity" is clicked. +/// }); +/// on(|trigger: Trigger>| { +/// // Do something when "MyEntity" is dragged. +/// }); +/// ]; +/// }; +/// ``` +pub fn on(system: I) -> Callback +where + E: Event, + B: Bundle, + I: IntoObserverSystem, +{ + Callback::new(system) } -/// Receipts contain hints about the previous outcome of building a particular -/// prototype. -#[derive(Default)] -pub struct Receipt { - /// The entity this prototype was last built on (if any). - target: Option, - /// The coponents it inserted. - components: HashSet, - /// The receipts of all the children, organized by name. - children: HashMap, +#[derive(Component)] +#[component(on_insert = insert_callback)] +#[component(on_remove = remove_callback)] +pub struct Callback { + observer: Option, } -/// A resource that tracks the receipts for root-level templates. -#[derive(Resource, Default)] -struct RootReceipt { - receipts: HashMap, +impl Callback { + pub fn new(system: I) -> Callback + where + E: Event, + B: Bundle, + I: IntoObserverSystem, + { + Callback { + observer: Some(Observer::new(system)), + } + } } -/// A fragment is a tree of bundles with optional names. It implements -/// [`Prototype`] and can be stored or used as a `Box`. -pub struct Fragment { - /// The name of the fragment, used to identify children across builds. - pub anchor: Option, - /// The bundle to be inserted on the entity. - pub bundle: B, - /// The template for the children. This boils down to a type-errased - /// `Fragment` vector. - pub children: Template, +impl From for Callback { + fn from(observer: Observer) -> Callback { + Callback { + observer: Some(observer), + } + } } -impl Prototype for Fragment { - fn name(&self) -> Option { - self.anchor.clone() +fn insert_callback(mut world: DeferredWorld, entity_id: Entity, _component: ComponentId) { + let mut callback = world.get_mut::(entity_id).unwrap(); + let Some(mut observer) = mem::take(&mut callback.observer) else { + return; + }; + if let Some(parent_id) = world.get::(entity_id).map(|parent| parent.get()) { + observer.watch_entity(parent_id); } + let mut commands = world.commands(); + commands.entity(entity_id).insert(observer); +} - fn build(self: Box, world: &mut World, receipt: &mut Receipt) { - // Collect the set of components in the bundle - let mut components = HashSet::new(); - B::get_component_ids(world.components(), &mut |maybe_id| { - if let Some(id) = maybe_id { - components.insert(id); - } - }); +fn remove_callback(mut world: DeferredWorld, entity_id: Entity, _component: ComponentId) { + let mut commands = world.commands(); + commands.entity(entity_id).remove::(); +} - // Get or spawn the entity - let mut entity = match receipt.target.and_then(|e| world.get_entity_mut(e).ok()) { - Some(entity) => entity, - None => world.spawn_empty(), - }; - let entity_id = entity.id(); - receipt.target = Some(entity_id); +// ----------------------------------------------------------------------------- +// Building fragments - // Insert the bundle - entity.insert(self.bundle); +/// An anchor is an identifier for a built fragment. +#[derive(Hash, Eq, PartialEq, Clone)] +pub enum Anchor { + /// The fragment is static and using an automatic incrementing ID. + Auto(u64), + /// The fragment has been explicitly named. + Named(String), +} - // Remove the components in the previous bundle but not this one - for component_id in receipt.components.difference(&components) { - entity.remove_by_id(*component_id); - } +/// Receipts allow templates to intelligently update existing ecs structures. +#[derive(Default, Component, Clone)] +pub struct Receipt { + /// The components it inserted. + components: HashSet, + /// The receipts of all the children, organized by name. + anchors: HashMap, +} + +impl Fragment { + /// Builds the fragment on an entity. This accesses the entity itself, and + /// also some or all of it's children. The receipt is stored as a component + /// on the entity, and used when the entity is built again. + pub fn build(self, entity_id: Entity, world: &mut World) { + // Clone the receipt for the targeted entity. + let receipt = world + .get::(entity_id) + .map(|receipt| receipt.to_owned()) + .unwrap_or_default(); + + // Build the bundle + let components = self + .bundle + .inner + .build(entity_id, world, receipt.components); // Build the children - let num_children = self.children.len(); - let mut children = Vec::with_capacity(num_children); - let mut child_receipts = HashMap::with_capacity(num_children); - let mut i = 0; - for child in self.children { - // Compute the anchor for this child, using it's name if supplied or an auto-incrementing - // counter if not. - let child_anchor = match child.name() { - Some(name) => Anchor::Named(name), - None => { - let anchor = Anchor::Auto(i); - i += 1; - anchor - } - }; - - // Get or create receipt - let mut child_receipt = receipt.children.remove(&child_anchor).unwrap_or_default(); - - // Build the child - child.build(world, &mut child_receipt); - - // Return the receipts - children.push(child_receipt.target.unwrap()); - child_receipts.insert(child_anchor, child_receipt); - } + let anchors = self.children.build(entity_id, world, receipt.anchors); - // Position the children beneith the entity - world.entity_mut(entity_id).replace_children(&children); + // Place the new receipt onto the entity + world.entity_mut(entity_id).insert(Receipt { + components, + anchors, + }); + } +} - // Clear any remaining orphans - for receipt in receipt.children.values() { - if let Some(entity) = receipt.target { - world.entity_mut(entity).despawn_recursive(); - } - } +// ----------------------------------------------------------------------------- +// Commands - // Update the receipt for use next frame - receipt.components = components; - receipt.children = child_receipts; - } +/// An extension trait for `EntityCommands` which allows templates to be built +/// on entities. +pub trait TemplateEntityCommandsExt { + /// Builds a fragment directly on the entity. Accepts anything that + /// implements `TryInto` and does nothing on a failure. This is + /// implemented for `Template` and is `Ok` when template has exactly one + /// fragment. + /// + /// To build the fragments in a template as children of the entity, see + /// [`build_children`](TemplateEntityCommandsExt::build_children). + fn build(&mut self, fragment: F) -> &mut Self + where + F: TryInto; + + /// Builds the fragments in the template as children of the entity. If the + /// template is empty this will remove all children. + /// + /// To build a fragment directly on the entity, see + /// [`build`](TemplateEntityCommandsExt::build). + fn build_children(&mut self, template: Template) -> &mut Self; } -// We implement this so that it is easy to return manually constructed a `Fragment` -// from a block in the `template!` macro. -impl IntoIterator for Fragment { - type Item = Box; - type IntoIter = core::iter::Once; +impl<'a> TemplateEntityCommandsExt for EntityCommands<'a> { + fn build(&mut self, fragment: F) -> &mut Self + where + F: TryInto, + { + if let Ok(fragment) = fragment.try_into() { + // Build the fragment + self.queue(|entity: Entity, world: &mut World| { + fragment.build(entity, world); + }) + } else { + self + } + } - fn into_iter(self) -> Self::IntoIter { - std::iter::once(Box::new(self) as Box<_>) + fn build_children(&mut self, children: Template) -> &mut Self { + self.queue(|entity_id: Entity, world: &mut World| { + // Access the receipt for the parent. + let receipt = world + .get::(entity_id) + .map(|receipt| receipt.to_owned()) + .unwrap_or_default(); + + // Build the children. + let anchors = children.build(entity_id, world, receipt.anchors); + + // Place the new receipt onto the parent. + world + .entity_mut(entity_id) + .insert(Receipt { anchors, ..receipt }); + }) } } +// ----------------------------------------------------------------------------- +// Macros + /// This is a declarative template macro for Bevy! /// /// It gives you something a little like `bsn` and a little `jsx`. Like `bsn`, @@ -245,152 +379,308 @@ impl IntoIterator for Fragment { /// /// Here's an example of what it looks like: /// -/// ```rust +/// ``` /// # use i_cant_believe_its_not_bsn::*; /// # use bevy::prelude::*; /// # let dark_mode = false; /// # #[derive(Component)] /// # pub struct MyMarkerComponent; -/// template! { -/// {( -/// Text::new(""), -/// TextFont::from_font_size(28.0), -/// if dark_mode { TextColor::WHITE } else { TextColor::BLACK } -/// )} [ -/// { TextSpan::new("Hello ") }; -/// { TextSpan::new("World") }; -/// {( TextSpan::new("!"), MyMarkerComponent )}; -/// ]; -/// }; +/// pub fn my_template(dark_mode: bool) -> Template { +/// // We create a new `Template` using the template macro. +/// template! { +/// ( +/// // Here we define an entity with a bundle of components. +/// Text::new(""), +/// TextFont::from_font_size(28.0), +/// if dark_mode { TextColor::WHITE } else { TextColor::BLACK } +/// ) => [ +/// // Here we define the entity's children. +/// TextSpan::new("Hello "); +/// TextSpan::new("World"); +/// ( TextSpan::new("!"), MyMarkerComponent ); +/// ]; +/// } +/// } /// ``` /// -/// The grammer is simple: Every time you see `{ ... }` it's a normal rust -/// code-block, and the template itself is just a list of fragments. +/// # Fragment +/// +/// The template macro is a sequence of "fragments" and "splices" delimited by +/// semicolons. A fragment represents a single entity, with optional children. A +/// splice is a way to insert one template into the middle of another. /// /// # Fragments /// -/// What's a fragment? Well, it's just a block that returns a `Bundle`, with an -/// optional name and list of child fragments. Names must be followed by a -/// colon, children are given in square brakets, and the whole thing always ends -/// with a semicolon. Behind the scenes these are used to create boxed -/// [`Fragment`] values. +/// A fragment consists of three things: a name, a bundle, and a list of +/// children. They generally looks something like the following +/// ```text +/// ( $name: )? $bundle ( => [ $children ] )? ; +/// ``` +/// where `(...)?` indicates an optional part. +/// +/// ## Names +/// +/// The first part of a fragment is an optional name (followed by a colon). A +/// name can be either static or dynamic. Static names are symbols which are +/// converted to strings at compile time. Dynamic names are blocks returning a +/// type that implements `Display`, and which are converted to strings at +/// runtime. +/// +/// ``` +/// # use i_cant_believe_its_not_bsn::*; +/// # use bevy::prelude::*; +/// fn template(dynamic_name: &str) -> Template { +/// template! { +/// Text::new("I don't have a name!"); +/// static_name: Text::new("I am named `static_name`!"); +/// {dynamic_name}: Text::new(format!("I am named `{}`!", dynamic_name)); +/// } +/// } +/// ``` +/// +/// Names give fragments continuity, and used to ensure the right entity gets +/// updated when a template is built multiple times. It is usually fine to not +/// give fragments explicit names, and let the macro name them automatically. +/// See the section on "Static and Dynamic Fragments" for more information about +/// the limits of anonymous fragments and automatic naming. +/// +/// ## Bundles +/// +/// The only required part of a fragment is the bundle. Every fragment must have +/// a bundle, even if it is the empty bundle `()`. Specifically, the bundle +/// portion of a fragment must be a rust expression that evaluates to a type +/// implementing `Into` (which includes every `Bundle` type). Here +/// are some examples of valid fragments with different bundles. +/// +/// ``` +/// # use i_cant_believe_its_not_bsn::*; +/// # use bevy::prelude::*; +/// # #[derive(Component)] +/// # struct ComponentA; +/// # #[derive(Component)] +/// # struct ComponentB; +/// # #[derive(Component)] +/// # enum EnumComponent { A, B } +/// # let foo = true; +/// # template! { +/// // The empty bundle. +/// (); +/// +/// // A code-block returning the empty bundle. +/// { () }; +/// +/// // A single component. +/// ComponentA; +/// +/// // An if statement that switches between different values for the same component. +/// if foo { EnumComponent::A } else { EnumComponent::B }; +/// +/// // An if statement that switches between different bundle types. +/// if foo { b!(ComponentA) } else { b!(ComponentB) }; +/// +/// // A tuple-bundle. +/// (ComponentA, ComponentB); +/// +/// // A code-block returning a tuple bundle. +/// { (ComponentA, ComponentB) }; +/// # }; +/// ``` +/// +/// Since these are all normal rust expressions, they have complete access to +/// the entire language. They can access local variables, use iterators, and +/// even include loops or cause side-effects. The only caveat is that each +/// fragment's bundle expression *must* evaluate to a specific type. If you want +/// to return bundles of different types, you must manually convert them to a +/// `BoxedBundle` using [`b!`](crate::b!) or [`Into`] before +/// returning the different bundles. +/// +/// ## Children +/// +/// Fragments may have an optional list of children, separated from the bundle +/// by `=>` and enclosed in square brackets. The list of children is itself a +/// template, and has semantics identical to the top-level `template!{}` macro. +/// +/// Every child fragment will create a child entity when built. When a template +/// is built multiple times (or applied to an existing entity) all children not +/// created by the template will are removed, and the entity children are +/// re-ordered to match the template. /// /// # Splices /// -/// Templates can also have other template "spliced" into the list of fragments. -/// A splice is just a codeblock prefixed with `@` and returning a `Template` -/// (or more generally an iterator of `Box>`). The contents of -/// this iterator is inserted into the list of fragments at the splice point. -/// Like fragments, splices also must be followed by a semicolon. +/// Splices allow you to insert a sub-template into the middle of another. This +/// inserts the sub-template's fragments into the other template's fragment list +/// at the splice point. /// /// ``` /// # use i_cant_believe_its_not_bsn::*; /// # use bevy::prelude::*; -/// let children_template = template! { -/// { TextSpan::new("child 1") }; -/// { TextSpan::new("child 2") }; -/// }; +/// # #[derive(Component)] +/// # struct MyComponent; +/// fn sub_template() -> Template { +/// template! { +/// one: MyComponent; +/// two: MyComponent; +/// } +/// } /// /// let parent_template = template! { -/// { Text::new("parent") } [ -/// @{ children_template }; +/// parent: Text::new("parent") => [ +/// // Insert both "one" and "two" as children of "parent". +/// @{ sub_template() }; /// ]; +/// // Inserts both "one" and "two" as siblings of "parent". +/// @{ sub_template() }; /// }; /// ``` /// -/// # Names +/// # Building Templates +/// +/// Templates can be "built" on an entity in much the same way that normal +/// bundles can be "inserted", but building a template builds the entire +/// hierarchy, spawning new child entities when required. Template building is +/// also "nondestructive" and "incremental". Nondestructive means you can build +/// a template on an existing entity without messing up unrelated components. +/// Incremental means that, if you build two templates on the same entity, the +/// second build will undo all the stuff done by the first build so that it +/// matches up with the second (without touching other components not added by +/// the first template). For more information about this, see the +/// `TemplateEntityCommandsExt` extension trait. +/// +/// # Template Functions /// -/// Fragments can be optionally prefixed by a name. A name is either a literal -/// symbols or code blocks that return a type implementing `Display`, followed -/// by a colon. +/// As indicated above, templates are most useful when built "multiple times". +/// Unfortunately, due to borrow-checker rules, `Template` is neither `Copy` nor +/// `Clone` and is consumed when built. The recommended approach is to write +/// functions which create `Template` values. /// /// ``` /// # use i_cant_believe_its_not_bsn::*; /// # use bevy::prelude::*; -/// let dynamic_name = "my cool name"; -/// template! { -/// static_name: { Text::new("statically named.") }; -/// {dynamic_name}: { Text::new("dynamically named.") }; -/// }; +/// fn template(cond: bool) -> Template { +/// template! [ +/// Text::new( if cond { "true" } else { "false" } ); +/// ] +/// } /// ``` /// -/// Most fragments don't need names, and you can safely omit the name. But you -/// should give fragments unique names in the following three cases: -/// + Entities which only apper conditionally. -/// + Children that may be re-ordered between builds. -/// + Lists or iterators of entities of variable length. +/// Each template function can be thought of as generating some specific version +/// of a given template. The template incrementalism rules ensure that if +/// multiple versions of the "same" template are built on the same entity, it +/// will be gracefully updated to match only the most recent. +/// +/// # Static and Dynamic Fragments /// -/// Failing to name dynamic fragments will produce bugs and strange behavior. +/// In the context of a template function emerge two kinds of fragment: Static +/// and Dynamic. /// -/// # Limitations +/// Static fragments are ones which do not change *position* between invocations +/// of a template function; they always appear in the same exact spot, never +/// omitted or moved around. More formally, a fragment is static if and only if +/// the same number of (other) static fragments always appear in front of it +/// within the template. /// -/// This macro is fairly limited, and it's implementation is less than 50 lines. -/// You should expect to run into the following pain points: -/// + Each fragment must have a statically defined bundle type. -/// + The syntax for optional or conditional fagments is cumbersome (you have to use splices). -/// + You are responsible for ensuring dynamic fragments are named properly, and will not be warned if you mess up. -/// + It's hard to customize how templates are built, or build them on specific entities. +/// Clearly if the fist fragment is always present in the template, then it is +/// static. Likewise if the second fragment is always present it is static too, +/// and so on and so on. This property allows the macro to able to give a fixed +/// index to each static fragment, and for this reason static fragment do not +/// need to be given names. /// -/// All of these can (and hopefully will) be addressed in a future version. +/// Dynamic fragments, by contrast, are ones which: +/// + Are conditional, or otherwise not always present in the template. +/// + Move around between invocations of the template. +/// +/// Both of these can mess with automatic indexing, so dynamic fragments do need +/// to be given names. Especially in the case where fragments can move around, +/// users need to ensure that the name moves with the fragment. +/// +/// For example when creating a dynamic list of fragments that may be +/// re-ordered, you should try to use some sort of stable name. +/// +/// ``` +/// # use i_cant_believe_its_not_bsn::*; +/// # use bevy::prelude::*; +/// # struct Item { id: usize, value: usize }; +/// # #[derive(Component)] +/// # struct Value(usize); +/// fn template(list: Vec) -> Template { +/// list.into_iter() +/// .map(|item| template! { +/// {item.id}: Value(item.value); +/// }) +/// .flatten() // Flatten out all the templates into an iterator of fragments +/// .collect() // Collect the fragments into a single template +/// } +/// ``` /// /// # Grammar /// /// The entire `template!` macro is defined with the following ABNF grammar /// -/// ```ignore -///