From 006bb162f6bbfc6dba164e619bd5027bab689cfa Mon Sep 17 00:00:00 2001 From: laund Date: Tue, 19 May 2026 15:15:11 +0200 Subject: [PATCH 01/14] progress on docs --- crates/bevy_scene/macros/src/lib.rs | 498 ++------------------ crates/bevy_scene/src/lib.rs | 685 +++++++++++++++++++++++++--- 2 files changed, 646 insertions(+), 537 deletions(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index 61ca6e92ea36c..28a64e7966f91 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -4,483 +4,51 @@ mod scene_component; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; -/// Creates a `Scene` using BSN (Bevy Scene Notation) syntax. +/// Macro which returns a [`Scene`](https://docs.rs/bevy/latest/bevy/prelude/trait.Scene.html), comprehensive docs at [`bevy_scene`](https://docs.rs/bevy/latest/bevy/scene/index.html) /// -/// BSN is a concise DSL for defining Bevy scenes as hierarchical collections -/// of entities and components. -/// While BSN's syntax largely follows Rust, it has quite a few features. -/// Don't feel like you need to master all of it before you begin. -/// Start simple and check back on the documentation as you run into problems. -/// -/// Trying to decipher a strange combination of glyphs? Jump to the **Syntax Reference** section below. -/// -/// ## Basic usage -/// -/// Let's begin by spawning a single named entity with a couple of components: -/// -/// ```rust, ignore -/// #[derive(Component, Default, Clone)] -/// struct Score(u32); -/// -/// #[derive(Component, FromTemplate)] -/// struct Health { current: u32, max: u32 } -/// -/// // Spawns a single entity named "Player" with a Score and Health component. -/// world.spawn_scene(bsn! { -/// #Player -/// Score(0) -/// Health { current: 100, max: 100 } -/// }); -/// ``` -/// -/// Each `bsn!` block describes a single root entity. -/// -/// ## Children and relationships -/// -/// You can add child entities with the `Children` component. -/// To begin a new child entity, separate it from the previous one with a comma. -/// Separate components on the same entity with whitespace. -/// -/// ```rust, ignore -/// // ONE child entity with both the Head and Body components: -/// bsn! { -/// #Player -/// Life -/// Children [Head Body] -/// } -/// -/// // TWO child entities — one with Head, one with Body: -/// bsn! { -/// #Player -/// Life -/// Children [Head, Body] -/// } -/// ``` -/// -/// **Warning:** Separating items with whitespace places them on the **same** entity; -/// separating them with commas creates **separate** entities. -/// This is a common source of problems for new BSN users, -/// so be sure to check your commas if an entity seems to be missing! -/// -/// Child entities can themselves have components and children, so scenes nest arbitrarily deep: -/// -/// ```rust, ignore -/// bsn! { -/// #Town -/// Children [ -/// // Parentheses and indentation help clarify the structure of nested scenes, -/// // but are optional — this scene would be the same without them. -/// ( -/// #Tavern -/// Children [ -/// #Innkeeper, -/// #Barkeep, -/// ] -/// ), -/// ( -/// #Blacksmith -/// Children [ -/// #Anvil, -/// ] -/// ), -/// ] -/// } -/// ``` -/// -/// Swap `Children` for any `RelationshipTarget` component to define custom relationships. -/// -/// To attach entities to an *existing* entity not created by this scene, use the `ChildOf` component directly. -/// The following example uses [`bsn_list!`] to spawn two entities both attached to a pre-existing root: -/// -/// ```rust, ignore -/// let root: Entity = /* some pre-existing entity */; -/// bsn_list! { -/// ( #Child1 ChildOf(root) ), -/// ( #Child2 ChildOf(#Child1) ), -/// } -/// ``` -/// -/// `ChildOf` accepts either a plain `Entity` value or a named `#Name` reference from the same scope. -/// -/// ## Named entity references -/// -/// The `#Name` syntax does two things at once: it adds a `Name("Name")` component to the entity -/// and registers it so that other entities in the same scene scope can refer to it by name. -/// To use a named entity as a value — for example, as a component field that holds an `Entity` id — -/// write `#Name` in the value position: -/// -/// ```rust, ignore -/// #[derive(Component, FromTemplate)] -/// struct Link(Entity); -/// -/// // Spawn a parent named "Hub" with two children that each hold a back-reference to it. +/// Example, intended as a hint for allowed syntax.: +/// ```rust,ignore /// bsn! { -/// #Hub -/// Children [ -/// Link(#Hub), -/// Link(#Hub), -/// ] -/// } -/// ``` -/// -/// References can point in any direction within the scene: a parent can reference a descendant, -/// a child can reference a parent, and siblings within the same `Children [...]` block can -/// reference each other. -/// All names in a single `bsn!` call share one scope; names from composed or inherited scenes -/// (`my_scene()`, `:my_scene`) live in their own separate scopes and are not visible here. -/// If two scopes both define the same name (e.g. both use `#Player`), each `#Player` resolves -/// to its own entity — there is no conflict or shadowing. -/// -/// For dynamic names computed at runtime, use `#{expr}`: -/// -/// ```rust, ignore -/// fn reference_named_entity(name: &str) -> impl Scene { -/// bsn! { #{name} } -/// } -/// ``` -/// -/// In [`bsn_list!`], all root entries share one scope, so sibling root entities can -/// cross-reference each other — see the `bevy_scene` crate docs for more details. -/// -/// ## Defaults and patching -/// -/// BSN supports *patching*: writing `Health { current: 100 }` creates a patch that sets only -/// `current`. Unmentioned fields keep their values from earlier patches or the type's defaults, -/// and multiple patches to the same component merge rather than overwrite. This works for both -/// `Clone + Default` and `FromTemplate` types. -/// -/// The difference between the two is about what values a field can hold at spawn time: -/// -/// - **`Clone + Default` types** (e.g. `#[derive(Component, Default, Clone)]`): the simple -/// case. This just works in BSN with no extra derives. All field values must be plain Rust values — the -/// template cannot fill them in correctly based on world or context state. -/// -/// - **`FromTemplate` types** (e.g. `#[derive(Component, FromTemplate)]`): needed when a field -/// requires spawn-time context. Use this when a field's type itself implements `FromTemplate` -/// — for example, `Handle` fields that resolve asset path strings, or `Entity` fields that -/// reference named entities in the scene. -/// -/// Because each approach generates a different `Template` implementation, `Clone + Default` and -/// `FromTemplate` cannot both be derived on the same type. This would create incoherent trait implementations! -/// Use `Clone + Default` by default, and switch to `FromTemplate` only when you need the extra flexibility it provides. -/// -/// ## Expressions and dynamic values -/// -/// BSN supports embedding arbitrary Rust expressions anywhere a value is expected, -/// using `{...}` (curly braces): -/// -/// ```rust, ignore -/// fn enemy(name: &str, hp: u32) -> impl Scene { -/// let sprite_path = name.to_string() + ".png"; -/// bsn! { -/// #{name} -/// Health { current: {hp}, max: {hp} } -/// Sprite { image: {sprite_path} } -/// } -/// } -/// ``` -/// -/// A `{...}` block can also hold an expression that implements `Scene` or, inside a -/// `Children [...]` block, one that implements `SceneList`: -/// -/// ```rust, ignore -/// fn unit_with_armor(unit_base: impl Scene) -> impl Scene { -/// bsn! { -/// {unit_base} -/// Armor(50) -/// } -/// } -/// ``` -/// -/// **Note:** `.bsn` asset files will not support arbitrary Rust expressions, -/// as we do not intend to require Bevy games to ship a Rust compiler. -/// -/// ## Automatic type conversion -/// -/// BSN performs some automatic type conversion for you, -/// reducing boilerplate when creating scenes. -/// -/// The `bsn!` macro appends `.into()` to expressions (`{...}`), bare identifiers, closures, and string -/// literals when they appear as field values. This means any field assignment that would be -/// valid via Rust's standard [`From`]/[`Into`] traits works transparently in BSN — no explicit -/// cast needed: -/// -/// ```rust, ignore -/// #[derive(Component, Default, Clone)] -/// struct Label(String); -/// -/// let greeting: &'static str = "Hello"; -/// bsn! { -/// // &str → String via Into, no .to_string() required -/// Label(greeting) -/// } -/// ``` -/// -/// A related, more advanced trick is what makes string literals work as asset paths for `Handle` fields. -/// See the docs on `HandleTemplate` for more information! -/// -/// Note: non-string literals are used as-is. Only string literals get `.into()` appended; -/// all other literals (integers, floats, booleans) are emitted directly, -/// so `Health { current: 100 }` assigns `100` without any conversion. -/// If the types don't match (e.g. you forgot to append a decimal point to a float), -/// you'll get a normal Rust type error. -/// -/// ## Asset loading -/// -/// When a component field is a `Handle`, BSN accepts a string literal in its place. -/// The string is resolved to an asset handle at spawn time via the asset server, reusing -/// an existing handle if the asset is already loaded: -/// -/// ```rust, ignore -/// commands.spawn_scene(bsn! { -/// Sprite { image: "player.png" } -/// }); -/// ``` -/// -/// This works for your own `FromTemplate`-derived components too — any `Handle` field -/// automatically accepts asset path strings: -/// -/// ```rust, ignore -/// #[derive(Component, FromTemplate)] -/// struct Icon { -/// image: Handle, -/// tint: Color, -/// } -/// -/// commands.spawn_scene(bsn! { -/// Icon { image: "icon.png", tint: Color::WHITE } -/// }); -/// ``` -/// -/// ## Observers -/// -/// Use `on` inside `bsn!` to attach an entity observer — a closure that fires when a given -/// `EntityEvent` targets the entity. The first parameter's type determines which event is -/// observed. Multiple observers can be stacked on the same entity, and the closure has full -/// access to the ECS via system parameters: -/// -/// ```rust, ignore -/// #[derive(EntityEvent)] -/// struct Damage(u32); -/// -/// fn player() -> impl Scene { -/// bsn! { -/// Health { max: 100, current: 100 } -/// on(|ev: On, mut query: Query<&mut Health>| { -/// let mut health = query.get_mut(ev.target()).unwrap(); -/// health.current = health.current.saturating_sub(ev.0); -/// }) -/// } -/// } -/// -/// // `move` closures work too, capturing variables from the enclosing scope: -/// fn enemy(bonus_damage: u32) -> impl Scene { -/// bsn! { -/// on(move |ev: On, mut query: Query<&mut Health>| { -/// let mut health = query.get_mut(ev.target()).unwrap(); -/// health.current = health.current.saturating_sub(ev.0 + bonus_damage); -/// }) -/// } -/// } -/// ``` -/// -/// ## Scene composition and inheritance -/// -/// There are two ways to build on an existing scene: **inline composition** and **inheritance**. -/// -/// **Inline composition** calls a scene function directly inside `bsn!`. The parent's unresolved -/// templates are merged with the child's and everything resolves together in one pass: -/// -/// ```rust, ignore -/// fn base_enemy() -> impl Scene { -/// bsn! { -/// Health { current: 100, max: 100 } -/// Power(10) -/// } -/// } -/// -/// // Compose base_enemy() and patch just the fields that differ. -/// fn boss() -> impl Scene { -/// bsn! { -/// base_enemy() -/// Health { max: 500 } -/// Power(50) -/// } -/// } -/// ``` -/// -/// **Inheritance** uses the `:` prefix. The parent is *pre-resolved* first — its templates are -/// fully flattened into a `ResolvedScene` — and the child's patches are applied on top. -/// When the scene is parameterless, this will "cache" the scene and share it across all inheriting scenes. -/// For larger scenes that are inherited many times, this can be much faster than re-computing -/// the scene each time. -/// -/// ```rust, ignore -/// fn boss() -> impl Scene { -/// bsn! { -/// :base_enemy -/// Health { max: 500 } -/// Power(50) +/// :some_scene() // inherit from a scene function, its +/// #SomeName // entity name, will insert Name("SomeName") +/// ComponentA // component without a value will use default +/// ComponentB(0.0) // passing a value, other fields will use default +/// Node { +/// height: px(0.1) // same with named fields, unmentioned ones stay default /// } -/// } -/// -/// // Asset inheritance (.bsn format not yet released, sorry!): -/// bsn! { -/// :"enemy.bsn" -/// Health { max: 500 } -/// } -/// ``` -/// -/// | | Function inheritance `:my_scene` | Asset inheritance `:"my_scene.bsn"` | Inline composition `my_scene()` | -/// |---------------------------|-------------------------------------|-------------------------------------|----------------------------------| -/// | Accepts parameters | Yes | No | Yes | -/// | Asset-based | No | Yes | No | -/// | Cached resolution | Parameterless scenes only | Yes | No | -/// -/// Prefer scene inheritance over inline composition in general: the expensive scene resolution is cached, saving work during reuse. -/// Inline composition should be reserved for parameterized scenes that vary based on a given input, -/// small scenes that are shared across contexts (like styles), -/// or one-off scenes that do not require reuse. -/// -/// /// ## Formatting BSN -/// -/// Whitespace, parentheses, and comments have no effect on the generated scene — -/// they exist purely to help you organize and read your code. -/// -/// **Whitespace** (spaces, newlines, tabs) separates items on the *same* entity. -/// Use it freely for alignment and to make groupings of both components and entities clearer. -/// -/// **Parentheses** group a set of items into one logical unit inside a `[...]` list. -/// Trailing commas are the delimiter used to end one entity and start another, -/// but these can be hard to spot in deeply nested or one-line scenes. -/// Parentheses (often with associated indentation) make the boundary explicit: -/// -/// ```rust, ignore -/// bsn! { -/// Children [ -/// // Hard to see where one entity ends and the next begins: -/// ComponentA -/// ComponentB, -/// ComponentA -/// ComponentC, -/// -/// // Much clearer: -/// ( -/// ComponentA -/// ComponentB -/// ), -/// ( -/// ComponentA -/// ComponentC -/// ), +/// on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer +/// let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA +/// b.0 += evt.value; +/// }) +/// Children [ // spawning multiple related entities using a RelationshipTarget component +/// #Child1 ComponentA // whitespace doesn't have to be newlines +/// , // entities are comma-separated +/// (:other_scene() #Child3), // parentheses around a single entity optional +/// Link(#SomeName), // pasing a entity reference to a component as `Entity`, component has to imlement FromTemplate +/// :MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from +/// @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments +/// normal_field: 5 // while normal fields are the actual fields of the component +/// }, +/// ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} +/// :Container { +/// @items: { +/// bsn_list![ // sometimes you may need to nest macro calls +/// #item1 SomeComponent, // note: the name #item1 here is in its own scope +/// :some_scene() #item2 +/// ] +/// } +/// } /// ] /// } /// ``` -/// -/// ```rust, ignore -/// // Without parentheses, one-line definitions of children are prone to subtle mistakes: -/// bsn! { Children [ComponentA ComponentB, ComponentC ComponentD] } -/// -/// // With parentheses, the structure is clear: -/// bsn! { Children [ (ComponentA ComponentB), (ComponentC ComponentD) ] } -/// ``` -/// -/// **Comments** (`//` line comments and `/* */` block comments) work exactly as in -/// normal Rust and are stripped by the macro before parsing. -/// -/// ## Syntax Reference -/// -/// ### Components on the same entity vs. separate entities -/// -/// | Syntax | Meaning | Explanation | -/// |--------|---------|-------------| -/// | `CompA CompB CompC` | Same entity | **Whitespace** between items — all go on the **same** entity | -/// | `A, B, C` | Separate entities | **Commas** inside `[…]` — each becomes its **own** entity | -/// | `(CompA CompB)` | Entity group | Parentheses group components for readability; equivalent to whitespace alone | -/// -/// ### Naming entities -/// -/// | Syntax | Meaning | Explanation | -/// |--------|---------|-------------| -/// | `#Name` | Named entity | Adds a `Name("Name")` component and registers the entity for cross-referencing within this scope | -/// | `#{ expr }` | Dynamic name | Names an entity using the result of a Rust expression | -/// -/// ### Relationships -/// -/// | Syntax | Meaning | Explanation | -/// |--------|---------|-------------| -/// | `Children [s1, s2]` | Add children | Spawns each entry as a child **of this** entity | -/// | `ChildOf(entity)` | Set parent | Makes **this** entity a child of `entity`; accepts a plain `Entity` or a `#Name` reference | -/// | `MyRel [s1, s2]` | Custom relationship | Like `Children`, but uses any `RelationshipTarget` component | -/// -/// ### Dynamic values -/// -/// | Syntax | Meaning | Explanation | -/// |--------|---------|-------------| -/// | `{ expr }` | Rust expression | Evaluated at spawn time; may be a component, `impl Scene`, or (inside `[…]`) `impl SceneList` | -/// | `field: { expr }` | Expression in field | Embeds a Rust expression as the value of a named field | -/// -/// ### Composition and inheritance -/// -/// | Syntax | Meaning | Explanation | -/// |--------|---------|-------------| -/// | `my_scene()` | Inline composition | Merges `my_scene`'s unresolved templates into this entity | -/// | `:my_scene` | Inheritance | Pre-resolves `my_scene` first, then patches on top | -/// | `:"path.bsn"` | Asset inheritance | Inherits from a `.bsn` asset file; requires `queue_spawn_scene` | -/// -/// ### Observers -/// -/// | Syntax | Meaning | Explanation | -/// |--------|---------|-------------| -/// | `on(\|ev: On\| { … })` | Observer | Attaches an entity observer that fires when `Ev` targets this entity | -/// -/// ### Other Rust syntax -/// -/// If you're new to Rust, you might struggle with some of its syntax when you see it in BSN. -/// Here are the most important syntax patterns to be aware of: -/// -/// | Syntax | Meaning | Explanation | -/// |--------|---------|-------------| -/// | `MyComponent` | Unit or defaulted struct | A struct with no fields, or in BSN only, with all fields at their defaults | -/// | `MyComponent { field: val, field2: val2 }` | Struct with named fields | Sets named fields; unmentioned fields keep their defaults or values from prior patches | -/// | `MyComponent(val1, val2)` | Tuple struct | Constructs a tuple-struct component | -/// | `MyEnum::Variant` | Enum variant | A value of the `MyEnum` type with the `Variant` variant | -/// | `module::MyComponent` | Path | A module path to a struct type | -/// | `GREEN`` | Constant | A constant value | -/// | `f32::PI` | Associated constant | A constant (here, `PI`) associated with a type (here, `f32`) | -/// | `\|param\| { … }` | Closure | A closure; effectively an unnamed function | -/// | `Vec` | Generic type | A type with a generic type parameter `T` | -/// | `spawn_enemy("orc", 10, true)` | Function call | Calls a function with the given arguments by position | -/// | `spawn_player()` | Argumentless function call | Calls a function that takes no arguments | -/// | `//` | Comment | Line comment; all text after `//` on the same line is ignored | -/// | `/* */` | Block comment | Standard Rust block comment; all text inside `/* */` is ignored | -/// | `bsn! { … }` | Macro call | Calls a macro on the value inside of the braces | -/// -/// ## Further reading -/// -/// See [`bsn_list!`] if you want to create multiple scenes at once, -/// or want to have multiple root entities. -/// -/// See the `bevy_scene` crate docs for a high-level overview of the key concepts. +#[doc(hidden)] #[proc_macro] pub fn bsn(input: TokenStream) -> TokenStream { crate::bsn::bsn(input) } -/// Creates a `SceneList` using BSN (Bevy Scene Notation) syntax. -/// -/// This is useful when you want multiple root entities in your scene -/// that do not share a common parent, or if you want to create multiple scenes at once. -/// -/// Like in [`bsn!`], commas separate entities, -/// while whitespace separates components on the same entity. -/// -/// All root entries in a [`bsn_list!`] share a single name scope, so sibling root entities -/// can cross-reference each other by `#Name`. -/// This is not possible with separate [`bsn!`] calls, and is a key motivation for using [`bsn_list!`]. /// -/// See [`bsn!`] for more details on syntax. -/// See the `bevy_scene` crate docs for a high-level overview of the key concepts. +#[doc(hidden)] #[proc_macro] pub fn bsn_list(input: TokenStream) -> TokenStream { crate::bsn::bsn_list(input) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index e33672f147b94..a6614b11f5c8c 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -6,7 +6,7 @@ //! A 2D character might need a distinct sprite entity for weapon, hat and boots. //! A UI popup might need text and multiple buttons for accept, cancel, minimize and close actions. //! Spawning these collections as individual, disjointed entities is tedious, error-prone, and hard to reuse. -//! A **scene** lets you describe a conceptual **object** — an entity, its components, children, and assets — once +//! A **scene** lets you describe a conceptual **object**, made of an entity, its components, children, and assets once //! and spawn it wherever you need it. //! //! Any scene system must overcome three challenges: @@ -28,6 +28,11 @@ //! resolve merge conflicts and keep file sizes under control. //! The macro includes full Rust Analyzer support (autocomplete, go-to-definition, hover docs)! //! +//! ## BSN syntax reference +//! +//! For a quick rundown on how to read and write BSN syntax, +//! see the docs for [`bsn!`]. +//! //! ## Quick Start //! //! Spawn entities in a [`Scene`] by calling [`World::spawn_scene`], @@ -183,16 +188,18 @@ //! ### Scope rules //! //! Each [`bsn!`] invocation creates its own name scope. A name is visible to the root -//! entity, its children, and any deeper descendants — as long as the reference is written +//! entity, its children, and any deeper descendants, as long as the reference is written //! in the same [`bsn!`] call. Composed or inherited scenes (via `my_scene()` or `:my_scene`) //! each bring their own separate scope, so names do not leak across scene boundaries. +//! However, the results of a named entity reference, a [`EntityTemplate`], can be passed to +//! composed or inherited scenes, since it represents a sort of global identifier for the name. //! //! If both a parent and a composed child define the same name (e.g. both use `#X`), -//! each scope's `#X` resolves to its own entity — there is no conflict or shadowing. +//! each scope's `#X` resolves to its own entity, avoiding conflicts or potentially unintuitive shadowing. //! //! In a [`bsn_list!`], all root entities share a single name scope, so sibling scenes //! can reference each other by name. This is useful for wiring up relationships between -//! entities that are spawned together — for example, a group of UI panels where each +//! entities that are spawned together. For example, a group of UI panels where each //! panel needs a relationship to its neighbor: //! //! ```ignore @@ -221,18 +228,17 @@ //! //! The distinction is about what values a field can hold at spawn time: //! -//! - **`Clone + Default`** covers the simple case, and should be your default choice. The blanket [`Template`] impl handles patching -//! automatically with no extra derives needed. +//! - **[`Clone`] + [`Default`]** (e.g. `#[derive(Component, Default, Clone)]`): covers the simple case, and should be your default choice. +//! The blanket [`Template`] impl handles patching automatically with no scene-specific derives needed. //! -//! - **[`FromTemplate`]** is needed when a field requires spawn-time context — for example, -//! a `Handle` field that resolves an asset path, or an [`Entity`] field that references -//! a named entity. If any of your fields' types implement [`FromTemplate`], -//! you must derive it for the parent type as well. +//! - **[`FromTemplate`]** (e.g. `#[derive(Component, FromTemplate)]`) is needed when a field requires spawn-time context. +//! Examples include [`Handle`] fields which need [`AssetServer`] to resolve asset paths, or [`Entity`] +//! fields which resolve [`EntityTemplate`]s from named entity references. If any of your fields' types +//! implement [`FromTemplate`], you must derive it for the parent type as well. //! -//! Deriving [`FromTemplate`] and [`Default`] on the same type is not allowed — -//! both would supply a [`FromTemplate`] impl and conflict. -//! You still have access to a default constructor of sorts though: the derive generates a companion -//! `YourTypeTemplate` struct that implements `Default`, so `YourTypeTemplate::default()` serves the same purpose. +//! Deriving [`FromTemplate`] and [`Default`] on the same type is not allowed, as both would supply a [`FromTemplate`] impl and conflict. +//! You still have access to a default constructor of sorts though: the derive generates a companion struct +//! for `YourType` named `YourTypeTemplate` which implements `Default`, so `YourTypeTemplate::default()` serves the same purpose. //! //! You compose scenes by writing functions that return `impl Scene` and calling them //! inside [`bsn!`]: @@ -293,8 +299,8 @@ //! } //! ``` //! -//! **Inheritance** uses the `:` prefix. The parent is **pre-resolved** — its templates are -//! fully flattened into a [`ResolvedScene`] *before* the child's patches apply on top: +//! **Inheritance** uses the `:` prefix. The parent is pre-resolved and its templates are +//! flattened into a [`ResolvedScene`] *before* the child's templates/patches apply on top: //! //! ```ignore //! // Inheritance: `:` prefix, no parentheses or arguments @@ -349,7 +355,7 @@ //! ``` //! //! This also works for components you define yourself. Any `Handle` field on a -//! [`FromTemplate`]-derived component automatically accepts asset path strings: +//! [`FromTemplate`]-derived component automatically accepts asset paths: //! //! ```ignore //! #[derive(Component, FromTemplate)] @@ -371,10 +377,10 @@ //! //! ## Observers //! -//! Use [`on`] inside [`bsn!`] to attach an entity observer — a closure that runs when a -//! given [`EntityEvent`] fires on that entity. The first parameter's type determines -//! which event is observed. You can attach multiple observers to the same entity, and -//! the closure has full access to the ECS via system parameters: +//! Use [`on(func)`](on) inside [`bsn!`] to attach an entity [`Observer`]. Entity observers are closures or +//! functions which fire when a given [`EntityEvent`] is triggered and targets this entity. +//! The first parameter's type determines which event is observed. Multiple observers can be addedc to +//! the same entity, and the observer has full access to the ECS via [system parameters]: //! //! ```ignore //! #[derive(EntityEvent)] @@ -383,26 +389,34 @@ //! #[derive(EntityEvent)] //! struct Heal(u32); //! +//! fn is_dead(ev: On, query: Query<&Health>){ +//! let health = query.get(ev.event_target()).unwrap(); +//! if health.current == 0 { +//! info!("{} is dead", ev.event_target()); +//! } +//! } +//! //! fn player() -> impl Scene { //! bsn! { //! Health { max: 100, current: 100 } //! // Each `on(...)` attaches a separate observer. //! on(|damage: On, mut query: Query<&mut Health>| { -//! let mut health = query.get_mut(damage.target()).unwrap(); +//! let mut health = query.get_mut(damage.event_target()).unwrap(); //! health.current = health.current.saturating_sub(damage.0); //! }) //! on(|heal: On, mut query: Query<&mut Health>| { -//! let mut health = query.get_mut(heal.target()).unwrap(); +//! let mut health = query.get_mut(heal.event_target()).unwrap(); //! health.current = (health.current + heal.0).min(health.max); //! }) +//! on(is_dead) //! } //! } //! ``` //! //! This is useful for self-contained logic like click handlers, damage reactions, //! or scripting-style triggers. -//! Closures passed to `on` work like any Rust closure: -//! you can use `move` and capture variables from the enclosing scope normally. +//! Closures passed to [`on`] work like any Rust closure: +//! you can use [`move`](https://doc.rust-lang.org/std/keyword.move.html) and capture variables from the enclosing scope normally. //! //! ## Using Dynamic Expressions in Scenes //! @@ -412,26 +426,25 @@ //! //! ```ignore //! fn enemy(hp: u32, name: &str) -> impl Scene { -//! let sprite_path = name.to_string() + ".png"; +//! let sprite_path = name.to_string(); //! //! bsn! { //! #{name} -//! Health { current: {hp}, max: {hp} } -//! Sprite { image: {sprite_path} } +//! Health { current: {hp / 2}, max: hp } +//! Sprite { image: {sprite_path + ".png"} } //! } //! } //! //! // Call it like an ordinary Rust function -//! commands.spawn_scene(bsn! { enemy(200, "goblin.png") }); +//! commands.spawn_scene(bsn! { enemy(200, "goblin") }); //! ``` //! //! Braces are required when the macro would otherwise misparse the expression //! and for complex expressions like `{hp * 2}`. -//! Variables used as positional or named fields (like `hp` above) also need braces. //! -//! ### Dynamic children +//! ### Expressions as scenes //! -//! You can splice a runtime [`SceneList`] into a `Children [...]` block with `{...}`: +//! You can insert a [`Scene`] or [`SceneList`] in another Scene using curly-bracketed expressions: //! //! ```ignore //! fn container(contents: impl SceneList) -> impl Scene { @@ -444,11 +457,11 @@ //! } //! } //! -//! let items = bsn_list![#A, #B, #C]; +//! let items = bsn_list![#A, #B, #C]; // or bsn! if container takes a `impl Scene` //! commands.spawn_scene(container(items)); //! ``` //! -//! ### Conditional components +//! ### Conditional values //! //! There is no `if`/`match` syntax inside the [`bsn!`] grammar, but you can embed //! conditionals via `{...}` blocks or handle them outside the macro: @@ -456,34 +469,30 @@ //! ```ignore //! fn unit(is_boss: bool) -> impl Scene { //! let hp = if is_boss { 500 } else { 100 }; -//! bsn! { Health { current: {hp}, max: {hp} } } +//! bsn! { Health { current: hp, max: hp } } //! } //! ``` -//! -//! ### Expressions as scenes -//! -//! A `{...}` block can also represent a variable or -//! expression that implements [`Scene`]. -//! This allows you to pass in scenes to helper functions, -//! allowing you to provide APIs based around partially complete scenes: -//! +//! One way to achieve conditional scenes is using a [`Box`] to store different scenes in one variable. //! ```ignore -//! fn unit_with_armor(unit_base: impl Scene) -> impl Scene { +//! fn unit(is_boss: bool, level: u32) -> impl Scene { +//! let scene: Box = if is_boss { +//! Box::new(bsn! { +//! Boss +//! Followers [ // the boss is followed by some grunts +//! :unit(false, level - 1) #Grunt1, +//! :unit(false, level - 2) #Grunt2 +//! ] +//! }) +//! } else { +//! Box::new(bsn! { Grunt }) +//! }; //! bsn! { -//! {unit_base} -//! Armor(50) +//! Level(level) +//! {scene} //! } //! } -//! -//! let my_unit = bsn! { Health { current: 100, max: 100 } }; -//! commands.spawn_scene(unit_with_armor(my_unit)); //! ``` //! -//! ## BSN syntax reference -//! -//! For a quick rundown on how to read and write BSN syntax, -//! see the docs for [`bsn!`]. -//! //! ## Scene Components //! //! A [`SceneComponent`] is a specialized type of [`Component`] that has an associated [`Scene`]: @@ -491,10 +500,7 @@ //! ``` //! # use bevy_scene::prelude::*; //! # use bevy_ecs::prelude::*; -//! # #[derive(Component, Default, Clone)] -//! # struct Sword; -//! # #[derive(Component, Default, Clone)] -//! # struct Shield; +//! //! #[derive(SceneComponent, Default, Clone)] //! struct Player { //! score: usize @@ -505,8 +511,8 @@ //! bsn! { //! #Player //! Children [ -//! Sword, -//! Shield, +//! #Sword, +//! #Shield, //! ] //! } //! } @@ -532,21 +538,12 @@ //! ``` //! //! This will spawn the `Player` component _and_ the entire scene with it. This means that you write -//! systems that query for the `Player` component, they can assume the rest of the scene will be there +//! systems that query for the `Player` component, they can generally assume the rest of the scene will be there //! too! //! //! [`SceneComponent`]s can only be spawned using scene APIs like [`World::spawn_scene`]. Spawning //! them using [`World::spawn`] will log an error. //! -//! ### Inheritance Syntax vs Patch Syntax -//! -//! Notice that this uses inheritance syntax in BSN (`:`), rather than normal "component patch" syntax -//! (ex: `bsn! { Player { score: 0 } }`. Semantically these are different things: -//! - Scene inheritance syntax: constructs the full scene and inherits from it -//! - Component patch syntax: _Just_ patches the component directly and creates it if it doesn't exist. -//! This will not do any scene inheritance. You can still patch scene components this way as long -//! as the scene component is inherited somewhere "earlier" in the inheritance hierarchy. -//! //! ### Custom Scene Functions //! //! When deriving [`SceneComponent`], it defaults to using `Self::scene` as the "scene function". @@ -566,6 +563,8 @@ //! //! ### `SceneComponent` Asset Paths //! +//! (Note: Currently, there is no `.bsn` asset format. This exists to help you understand whats planned.) +//! //! Alternatively, a scene asset path can be specified: //! //! ``` @@ -578,7 +577,6 @@ //! } //! ``` //! -//! (Note that we haven't yet landed the `.bsn` asset format or ported the glTF asset loader to BSN) //! //! ### Scene Components are Template-able //! @@ -781,6 +779,7 @@ //! [`FromTemplate`]: bevy_ecs::template::FromTemplate //! [`Asset`]: bevy_asset::Asset //! [`Entity`]: bevy_ecs::entity::Entity +//! [`EntityTemplate`]: bevy_ecs::template::EntityTemplate //! [`RelationshipTarget`]: bevy_ecs::relationship::RelationshipTarget //! [`EntityEvent`]: bevy_ecs::event::EntityEvent //! [`Name`]: bevy_ecs::name::Name @@ -813,7 +812,6 @@ mod scene_patch; mod spawn; mod spawn_system; -pub use bevy_scene_macros::*; pub use resolved_scene::*; pub use scene::*; pub use scene_component::*; @@ -826,6 +824,123 @@ use bevy_app::{App, Plugin, SceneSpawnerSystems, SpawnScene}; use bevy_asset::AssetApp; use bevy_ecs::prelude::*; +/// Creates a `Scene` using BSN (Bevy Scene Notation) syntax. +/// +/// These docs primarily contain syntax +/// See [`bevy_scene`](crate) module-level docs for in-depth details about usage and interactions. +/// +/// +/// ## Syntax Reference +/// +/// ### Parts of a scene +/// | Examples | Explanation | +/// | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +/// | `CompA` | A unit or default component. Fields, if any exist, will be default | +/// | `CompA(val)`
`CompA(val, val)` | Tuple Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | +/// | `CompA { name: val }` | Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | +/// | `mymodule::CompA { name: val }` | Same as above, but referring to the component by module path | +/// | `MyEnum::Variant` | Enum Component `MyEnum` with the `Variant` variant | +/// | `template_value(component)` | Insert the component value from a variable | +/// | `template_value(Transform::from_xyz(2., 3., 1.,))` | Insert the component value by immediately calling the constructor | +/// | **Composition and Inheritance** | | +/// | `scene()`
`scene(val)` | Composition with [`Scene`] function | +/// | `{ expr }` | Composition with the result of `expr`, which should be a [`Scene`] | +/// | `:scene()`
`:scene(val)` | Inherit from [`Scene`] function | +/// | `:CompA` | Inherit from [`SceneComponent`]. Fields, if any exist, will be default | +/// | `:CompA { @prop: val }` | Inherit from [`SceneComponent`] with a `prop` field, passed to this components scene function | +/// | `:CompA { name: val }` | Inherit from [`SceneComponent`] with a normal field, works the same as it does for normal components | +/// | `:CompA { @prop: val1, name: val2 }` | Inherit from [`SceneComponent`] with both a `prop` and a field | +/// | `:"scene.bsn"` |
Not yet implemented!
Inherit from a scene asset file | +/// | **Named entity references** | | +/// | `#MyName` | Becomes `Name("MyName")` when used as a `part` of a scene | +/// | `#{ expr }` | Same as `#MyName` but using the result of the expression as the name | +/// | `CompA(#MyName)`
`:scene(#MyName)` | Referring to the entity which was named `MyName` in this scope, results in an [`EntityTemplate`] being passed | +/// | `CompA(#{ expr })`
`:scene(#{ expr })` | Using the result of `expr` as the name. Note: This has a special case, allowing a [`Entity`] id as well | +/// | **Observers** | | +/// | `on(\|ev: On\| { … })` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a closure | +/// | `on(my_observer)` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a function | +/// | **Relationships** | | +/// | `Children []` | Spawns each entry as a child of this entity, see **Scene Lists** below for details | +/// | `ChildOf(entity)` | Makes **this** entity a child of `entity`, accepts a [`Entity`]/[`EntityTemplate`] or a `#Name` reference | +/// | `MyRel []` | Like `Children`, but uses any `RelationshipTarget` component | +/// +/// [`EntityTemplate`]: bevy_ecs::template::EntityTemplate +/// ### Scene Lists +/// +/// In `bsn_list!` and Relationships a list of scenes delimited by `[]` is allowed. +/// Unlike parts of a scene, which are whitespace-separated, the scenes in a scene list are comma-separated. +/// +/// Note: examples are part of a relationship like `Children []` or a list macro like `bsn_list![]` +/// +/// | Example | Meaning | +/// | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | +/// | `#Child1 CompA, #Child2` | Spawns 2 children, one with `(Name("Child1"), CompA::default())` and the other with `Name("Child2")` | +/// | `(#Child1 CompA), (#Child2)` | Same as above, with explicity parentheses | +/// | `#First, { expr }, #Last ` | Spawns an entity with name `First`, then every entity from the `SceneList` returned by expr, then one named `Last` | +/// | `#First, ({ expr }), #Last ` | Same as above, but the `expr` should result in a `Scene` and will only spawn one entity using it | +/// +/// ### Values +/// +/// Values in BSN (as in `val`,`val1` etc used above) are generally any literal Rust values, plus a few bsn-specific quirks. +/// +/// +/// | Example | Meaning | Explanation | +/// | -------------------------------- | ------------- | +/// | `1` | unsigned int | positive number, common types: `usize`, `u8`, `u32`, `u64` | +/// | `1` or `-1` | signed int | positive or negative number, common types: `i32`, `i64` | +/// | `1.1` or `-0.1` or `1.` or `-2.` | float | Floating point number, common types: `f32`, `f64` | +/// | `true` or `false` | bool | Boolean, type: `bool` | +/// | `"somename"` | string | Text, types: `String` or `&'static str` | +/// | `"mypicture.png"` | asset path | Asset, when used in a field which expects a `Handle` to the matching `Asset` type | +/// | `some(1)` | function call | , only works if the function returns the required type | +/// | `GREEN` | constant | fixed value, must be in scope | +/// | `std::f32::consts::PI` | constant | fixed value, uses full path so doesn't need to be in scope | +/// | **Expression syntax** | | | +/// | `{ 1 + 2 }` | expression | Any rust expression works in `{}`, in this case addition of 2 integers | +/// | `{ vec![true, false] }` | vector | An expression returning a vec, collection of multiple items, in the example `Vec` but can be any inner type | +/// | `{ bsn!{ Text("foo") Style } }` | scene | Sometimes, you may need to pass a small `Scene` as a value to something else | +/// +/// ### Other Rust syntax +/// +/// If you're new to Rust, you might struggle with some of its syntax when you see it in or around BSN. +/// Here are some syntax snippets which haven't been shown so far: +/// +/// | Syntax | Meaning | Explanation | +/// | ----------------- | ------------- | --------------------------------------------------------------- | +/// | `\|param\| { … }` | Closure | A closure; effectively an unnamed function | +/// | `Vec` | Generic type | A type with a generic type parameter `T` | +/// | `//` | Comment | Line comment; all text after `//` on the same line is ignored | +/// | `/* */` | Block comment | Standard Rust block comment; all text inside `/* */` is ignored | +/// | `bsn! { … }` | Macro call | Calls a macro on the value inside of the braces | +/// +/// ### Syntax example +// Note: the actual syntax example comes from the original bevy_scene_macros::bsn docs. +// rustdoc appends these #[doc(inline)] docs before those +// rust-analyzer ignores these docs and only shows the original ones, see https://github.com/rust-lang/rust-analyzer/issues/14079 +// despite those being #[doc(hidden)] +// +pub struct Foo; // comment this in to get rust-analyzer highlighting in the above docs, probably same issue as above +#[doc(inline)] +pub use bevy_scene_macros::bsn; + +/// Creates a `SceneList` using BSN (Bevy Scene Notation) syntax. +/// +/// This is useful when you want multiple root entities in your scene +/// that do not share a common parent, or if you want to create multiple scenes at once. +/// +/// Like in [`bsn!`], commas separate entities, +/// while whitespace separates components on the same entity. +/// +/// All root entries in a [`bsn_list!`] share a single name scope, so sibling root entities +/// can cross-reference each other by `#Name`. +/// This is not possible with separate [`bsn!`] calls, and is a key motivation for using [`bsn_list!`]. +/// +/// See [`bsn!`] for more details on syntax. +/// See the `bevy_scene` crate docs for a high-level overview of the key concepts. +#[doc(inline)] +pub use bevy_scene_macros::bsn_list; +pub use bevy_scene_macros::SceneComponent; + /// Adds support for spawning Bevy Scenes. See [`Scene`], [`SceneList`], [`ScenePatch`], and the [`bsn!`] macro for more information. #[derive(Default)] pub struct ScenePlugin; @@ -853,6 +968,7 @@ mod tests { use crate::{prelude::*, ScenePatch}; use alloc::sync::Arc; use bevy_app::{App, TaskPoolPlugin}; + use bevy_asset::io::embedded::GetAssetServer; use bevy_asset::io::memory::{Dir, MemoryAssetReader}; use bevy_asset::io::{AssetSourceBuilder, AssetSourceId}; use bevy_asset::{Asset, AssetApp, AssetLoader, AssetPlugin, AssetServer, Assets, Handle}; @@ -1332,6 +1448,321 @@ mod tests { let exploded = world.resource::(); assert_eq!(exploded.0, Some(id)); } + #[test] + fn primitive_literals() { + #![allow(dead_code, reason = "test")] + // test that bsn compiles and doesn't fail to spawn a scene with all sorts of literal values + macro_rules! types_fields { + (def $name:ident, $($typ:ident),*) => { + #[derive(Component, FromTemplate)] + struct $name { + $( + $typ: $typ, + )* + } + }; + ($world:ident, $name:ident($a:expr, $b:expr, $c:expr, $d:expr, $e:expr), $($typ:ident),*) => { + types_fields!($world, $name($a, $b, $c, $d), $($typ),*); + types_fields!(val $world, $e, $name, $($typ),*); + }; + ($world:ident, $name:ident($a:expr, $b:expr, $c:expr, $d:expr), $($typ:ident),*) => { + types_fields!($world, $name($a, $b, $c), $($typ),*); + types_fields!(val $world, $d, $name, $($typ),*); + }; + ($world:ident, $name:ident($a:expr, $b:expr, $c:expr), $($typ:ident),*) => { + types_fields!($world, $name($a, $b), $($typ),*); + types_fields!(val $world, $c, $name, $($typ),*); + + }; + ($world:ident, $name:ident($a:expr, $b:expr), $($typ:ident),*) => { + types_fields!($world, $name($a), $($typ),*); + types_fields!(val $world, $b, $name, $($typ),*); + }; + ($world:ident, $name:ident($a:expr), $($typ:ident),*) => { + types_fields!(def $name, $($typ),*); + types_fields!(val $world, $a, $name, $($typ),*); + }; + (val $world:ident, $a:expr, $name:ident, $($typ:ident),*) => { + let v = bsn!{ $name { + $( + $typ: $a, + )* + }}; + $world.spawn_scene(v).unwrap(); + }; + } + let mut app = test_app(); + let world = app.world_mut(); + types_fields!(world, Unsigned(0, 1), usize, u8, u16, u32, u64, u128); + types_fields!(world, Signed(-1, 0, 1), isize, i8, i16, i32, i64, i128); + types_fields!( + world, + Float(-1.0, 0.0, 1.0, core::f32::consts::PI, -1.), + f32, + f64 + ); + types_fields!(world, Bool(true, false), bool); + #[derive(Component, FromTemplate)] + struct Random { + str: &'static str, + string: String, + vec: Vec, + array: [u8; 4], + } + let scene = bsn! { + Random{ + str: "test", + string: "test", + vec: {vec![0, 1]}, + array: {[0, 1, 2, 3]} + } + }; + world.spawn_scene(scene).unwrap(); + } + #[test] + fn children_list_expr() { + fn container(items: impl SceneList) -> impl Scene { + bsn! { + #Root + Children [ + #First, + {items}, + #Last + ] + } + } + let mut app = test_app(); + let world = app.world_mut(); + let items = bsn_list![ + #Second, + #Third + ]; + let id = world.spawn_scene(container(items)).unwrap().id(); + let children = world.entity(id).get::().unwrap(); + let names: Vec<_> = children + .iter() + .map(|id| world.entity(id).get::().unwrap().as_str()) + .collect(); + assert_eq!(&names, &["First", "Second", "Third", "Last"]); + } + #[test] + fn children_single_expr() { + fn container(item: impl Scene) -> impl Scene { + bsn! { + #Root + Children [ + #First, + ({item}), + #Last + ] + } + } + let mut app = test_app(); + let world = app.world_mut(); + let items = bsn![ + #Second + ]; + let id = world.spawn_scene(container(items)).unwrap().id(); + let children = world.entity(id).get::().unwrap(); + let names: Vec<_> = children + .iter() + .map(|id| world.entity(id).get::().unwrap().as_str()) + .collect(); + assert_eq!(&names, &["First", "Second", "Last"]); + } + #[test] + fn conditional_scene() { + #[derive(Component, Clone, Default)] + struct Grunt; + #[derive(Component, Clone, Default)] + struct Boss; + #[derive(Component, Clone, Default, PartialEq, Eq)] + struct Level(u32); + + fn unit(is_boss: bool, level: u32) -> impl Scene { + let scene: Box = if is_boss { + Box::new(bsn! { + Boss + Children [ :unit(false, level - 1) #Grunt1, :unit(false, level - 1) #Grunt2] + }) + } else { + Box::new(bsn! { Grunt }) + }; + bsn! { + Level(level) + {scene} + } + } + let mut app = test_app(); + let world = app.world_mut(); + + let id = world.spawn_scene(unit(true, 10)).unwrap().id(); + let children = world.entity(id).get::().unwrap(); + let names: Vec<_> = children + .iter() + .map(|id| world.entity(id).get::().unwrap().as_str()) + .collect(); + assert_eq!(&names, &["Grunt1", "Grunt2"]); + let names: Vec<_> = children + .iter() + .map(|id| world.entity(id).get::().unwrap().0) + .collect(); + assert_eq!(&names, &[9, 9]); + } + #[test] + fn partial_tuple_struct() { + // Tests that only part of a tuple struct can be patched, + // since its different to named fields + let mut app = test_app(); + let world = app.world_mut(); + #[derive(Component, Default, Clone)] + struct TupleStruct(f32, u32); + + fn a() -> impl Scene { + bsn! { + TupleStruct(0.1) + } + } + let id = world.spawn_scene(a()).unwrap().id(); + let root = world.entity(id); + + let foo = root.get::().unwrap(); + assert_eq!(foo.0, 0.1); + assert_eq!(foo.1, 0); + } + + #[test] + fn scene_expression_passing_pointless() { + // This test exists mostly to ensure that the practice of not passing `impl Scene` into a scene + // is the same as the preferred option, using patching. + #[derive(Component, Default, Clone)] + struct Health { + current: u8, + max: u8, + } + #[derive(Component, Default, Clone)] + struct Armor(u8); + + fn unit_with_armor(unit_base: impl Scene) -> impl Scene { + bsn! { + {unit_base} + Armor(50) + } + } + fn armor() -> impl Scene { + bsn! { + Armor(50) + } + } + let mut app = test_app(); + let world = app.world_mut(); + let inner = bsn! { Health { current: 100, max: 100 } }; + let ida = world.spawn_scene(unit_with_armor(inner)).unwrap().id(); + + // inheritance is the same! + let entity_b = bsn! { + :armor() + Health { current: 100, max: 100 } + }; + let idb = world.spawn_scene(entity_b).unwrap().id(); + + assert_eq!( + world.entity(ida).archetype().components(), + world.entity(idb).archetype().components() + ); + } + + #[test] + fn scene_inheritance_vs_composition() { + #[derive(Component, Default, Clone)] + struct Health { + current: u8, + max: u8, + } + #[derive(Component, Default, Clone)] + struct Armor(u8); + + fn unit_with_armor() -> impl Scene { + bsn! { + #Unit + Armor(50) + Children [ + #First + ] + } + } + fn armor() -> impl Scene { + bsn! { + Armor(10) + Children [ + #Second + ] + } + } + + let mut app = test_app(); + let world = app.world_mut(); + + // inheritance + let scene_a = bsn! { + :unit_with_armor() + armor() + }; + + let entity_a = world.spawn_scene(scene_a).unwrap().id(); + let children = world.entity(entity_a).get::().unwrap(); + let assets = world.resource::>(); + for id in assets.ids() { + dbg!(id); + } + let names_a: Vec<_> = children + .iter() + .map(|id| world.entity(id).get::().unwrap().to_string()) + .collect(); + + // composition + let scene_b = bsn! { + unit_with_armor() + armor() + }; + let entity_b = world.spawn_scene(scene_b).unwrap().id(); + let children = world.entity(entity_b).get::().unwrap(); + let names_b: Vec<_> = children + .iter() + .map(|id| world.entity(id).get::().unwrap().to_string()) + .collect(); + + let scene_c = bsn! { + unit_with_armor() + Armor(10) + Children [ + #Second + ] + }; + let entity_c = world.spawn_scene(scene_c).unwrap().id(); + let children = world.entity(entity_c).get::().unwrap(); + let names_c: Vec<_> = children + .iter() + .map(|id| world.entity(id).get::().unwrap().to_string()) + .collect(); + + assert_eq!(names_a, &["First", "Second"]); + assert_eq!(names_a, names_b); + assert_eq!(names_a, names_c); + + assert_eq!( + world.entity(entity_a).archetype().components(), + world.entity(entity_b).archetype().components() + ); + + assert_eq!( + world.entity(entity_a).archetype().components(), + world.entity(entity_c).archetype().components() + ); + assert_eq!(world.entity(entity_a).get::().unwrap().0, 10); + assert_eq!(world.entity(entity_b).get::().unwrap().0, 10); + assert_eq!(world.entity(entity_).get::().unwrap().0, 10); + } #[test] fn enum_patching() { @@ -1668,6 +2099,19 @@ mod tests { world.trigger(Heal(id)); assert_eq!(world.resource::().0, 1); world.resource_mut::().0 = 0; + fn on_heal(_: On, mut healed: ResMut) { + healed.0 += 2; + } + fn function_scene() -> impl Scene { + bsn! { + on(on_heal) + } + } + + let id = world.spawn_scene(function_scene()).unwrap().id(); + world.trigger(Heal(id)); + assert_eq!(world.resource::().0, 2); + world.resource_mut::().0 = 0; fn move_scene(bonus: u32) -> impl Scene { bsn! { @@ -1981,4 +2425,101 @@ mod tests { b(); } + #[test] + fn macro_doc_test() { + #![allow(unused, reason = "test")] + #![allow(dead_code, reason = "test")] + + fn some_scene() -> impl Scene {} + #[derive(Component, Default, Clone)] + struct ComponentA; + #[derive(Component, Default, Clone)] + struct ComponentB(f32, u8); + #[derive(Component, Default, Clone)] + struct Node { + height: f32, + width: f32, + } + fn px(v: f32) -> f32 { + v + } + #[derive(EntityEvent)] + struct MyEntityEvent { + entity: Entity, + value: f32, + } + fn other_scene() -> impl Scene {} + #[derive(Component, FromTemplate)] + struct Link(Entity); + #[derive(SceneComponent, FromTemplate)] + #[scene(scenecomponentscene(Props))] + struct MySceneComponent { + normal_field: u8, + } + #[derive(Default)] + struct Props { + some_prop: u8, + } + fn scenecomponentscene(props: Props) -> impl Scene {} + let some_variable: f32 = 0.; + #[derive(SceneComponent, FromTemplate)] + #[scene(scenecomponentscene2(Props2))] + struct Container; + struct Props2 { + items: Box, + } + impl Default for Props2 { + fn default() -> Self { + Self { + items: Box::new(bsn_list!()), + } + } + } + fn scenecomponentscene2(props: Props2) -> impl Scene {} + #[derive(Component, Default, Clone)] + struct SomeComponent; + // Copy of the macro from bevy_scene/macros/src/lib.rs + // why? because it should be tested + // why not doctests? because the macro can't depend on this crate + // why not include! it here and include_str! it in the docs? because rust-analyzer ignores #[doc = include_str!()] and this is mostly a showcase for rust-analyzer + let scene = bsn! { + :some_scene() // inherit from a scene function, its + #SomeName // entity name, will insert Name("SomeName") + ComponentA // component without a value will use default + ComponentB(0.0) // passing a value, other fields will use default + Node { + height: px(0.1) // same with named fields, unmentioned ones stay default + } + on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer + let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA + b.0 += evt.value; + }) + Children [ // spawning multiple related entities using a RelationshipTarget component + #Child1 ComponentA // whitespace doesn't have to be newlines + , // entities are comma-separated + (:other_scene() #Child3), // parentheses around a single entity optional + Link(#SomeName), // pasing a entity reference to a component as `Entity`, component has to imlement FromTemplate + :MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from + @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments + normal_field: 5 // while normal fields are the actual fields of the component + }, + Node { + width: some_variable + } + ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} + :Container { + @items: { + bsn_list![ // sometimes you may need to nest macro calls + #item1 SomeComponent, // note: the name #item1 here is in its own scope + :some_scene() #item2 + ] + } + } + ] + }; + // just checking it spawns correctly + let mut app = test_app(); + let world = app.world_mut(); + let entity = world.spawn_scene(scene).unwrap().id(); + } } From 81431d28d39920da309413afc8be8f1971b97151 Mon Sep 17 00:00:00 2001 From: laund Date: Wed, 20 May 2026 00:27:01 +0200 Subject: [PATCH 02/14] further docs filing --- crates/bevy_scene/src/lib.rs | 51 +++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index a6614b11f5c8c..b4d33eb751c79 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -442,6 +442,22 @@ //! Braces are required when the macro would otherwise misparse the expression //! and for complex expressions like `{hp * 2}`. //! +//! ### Using dynamic template values (component values) +//! +//! A [`Template`] value, like a instance of a Component, cannot be directly returned from a curly bracketed `{ ... }` expression. +//! For this, `template_value(...)` has to be used, which returns a [`Template`] version of the given value. +//! +//! ```rs +//! fn enemy(translation: Vec3){ +//! let transform = Transform::from_translation(translation); +//! bsn! { +//! #Foo +//! template_value(transform) +//! } +//! +//! } +//! ``` +//! //! ### Expressions as scenes //! //! You can insert a [`Scene`] or [`SceneList`] in another Scene using curly-bracketed expressions: @@ -833,37 +849,39 @@ use bevy_ecs::prelude::*; /// ## Syntax Reference /// /// ### Parts of a scene -/// | Examples | Explanation | +/// | Examples | Explanation /// | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | /// | `CompA` | A unit or default component. Fields, if any exist, will be default | -/// | `CompA(val)`
`CompA(val, val)` | Tuple Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | -/// | `CompA { name: val }` | Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | +/// | `CompA(val)`
`CompA(val, val)` | Tuple Component with some fields specified. Unspecified fields will be default, see [patching](self#patching)| +/// | `CompA { name: val }` | Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | /// | `mymodule::CompA { name: val }` | Same as above, but referring to the component by module path | /// | `MyEnum::Variant` | Enum Component `MyEnum` with the `Variant` variant | /// | `template_value(component)` | Insert the component value from a variable | /// | `template_value(Transform::from_xyz(2., 3., 1.,))` | Insert the component value by immediately calling the constructor | +/// | `template(|context| { ... })` | Register a function or closure which returns a Template, and is given [`context`] with full World access | /// | **Composition and Inheritance** | | -/// | `scene()`
`scene(val)` | Composition with [`Scene`] function | -/// | `{ expr }` | Composition with the result of `expr`, which should be a [`Scene`] | -/// | `:scene()`
`:scene(val)` | Inherit from [`Scene`] function | -/// | `:CompA` | Inherit from [`SceneComponent`]. Fields, if any exist, will be default | -/// | `:CompA { @prop: val }` | Inherit from [`SceneComponent`] with a `prop` field, passed to this components scene function | -/// | `:CompA { name: val }` | Inherit from [`SceneComponent`] with a normal field, works the same as it does for normal components | -/// | `:CompA { @prop: val1, name: val2 }` | Inherit from [`SceneComponent`] with both a `prop` and a field | +/// | `scene()`
`scene(val)` | Composition with [`Scene`] function | +/// | `{ expr }` | Composition with the result of `expr`, which should be a [`Scene`] | +/// | `:scene()`
`:scene(val)` | Inherit from [`Scene`] function | +/// | `:CompA` | Inherit from [`SceneComponent`]. Fields, if any exist, will be default | +/// | `:CompA { @prop: val }` | Inherit from [`SceneComponent`] with a `prop` field, passed to this components scene function | +/// | `:CompA { name: val }` | Inherit from [`SceneComponent`] with a normal field, works the same as it does for normal components | +/// | `:CompA { @prop: val1, name: val2 }` | Inherit from [`SceneComponent`] with both a `prop` and a field | /// | `:"scene.bsn"` |
Not yet implemented!
Inherit from a scene asset file | /// | **Named entity references** | | /// | `#MyName` | Becomes `Name("MyName")` when used as a `part` of a scene | /// | `#{ expr }` | Same as `#MyName` but using the result of the expression as the name | -/// | `CompA(#MyName)`
`:scene(#MyName)` | Referring to the entity which was named `MyName` in this scope, results in an [`EntityTemplate`] being passed | -/// | `CompA(#{ expr })`
`:scene(#{ expr })` | Using the result of `expr` as the name. Note: This has a special case, allowing a [`Entity`] id as well | +/// | `CompA(#MyName)`
`:scene(#MyName)` | Referring to the entity which was named `MyName` in this scope, results in an [`EntityTemplate`] being passed| +/// | `CompA(#{ expr })`
`:scene(#{ expr })` | Using the result of `expr` as the name. Note: This has a special case, allowing a [`Entity`] id as well | /// | **Observers** | | -/// | `on(\|ev: On\| { … })` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a closure | -/// | `on(my_observer)` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a function | +/// | `on(\|ev: On\| { … })` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a closure | +/// | `on(my_observer)` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a function | /// | **Relationships** | | /// | `Children []` | Spawns each entry as a child of this entity, see **Scene Lists** below for details | -/// | `ChildOf(entity)` | Makes **this** entity a child of `entity`, accepts a [`Entity`]/[`EntityTemplate`] or a `#Name` reference | +/// | `ChildOf(entity)` | Makes **this** entity a child of `entity`, accepts a [`Entity`]/[`EntityTemplate`] or a `#Name` reference | /// | `MyRel []` | Like `Children`, but uses any `RelationshipTarget` component | /// +/// [`context`]: bevy_ecs::template::TemplateContext /// [`EntityTemplate`]: bevy_ecs::template::EntityTemplate /// ### Scene Lists /// @@ -973,6 +991,7 @@ mod tests { use bevy_asset::io::{AssetSourceBuilder, AssetSourceId}; use bevy_asset::{Asset, AssetApp, AssetLoader, AssetPlugin, AssetServer, Assets, Handle}; use bevy_ecs::lifecycle::HookContext; + use bevy_ecs::name::Name; use bevy_ecs::prelude::*; use bevy_ecs::relationship::Relationship; use bevy_ecs::world::DeferredWorld; @@ -1761,7 +1780,7 @@ mod tests { ); assert_eq!(world.entity(entity_a).get::().unwrap().0, 10); assert_eq!(world.entity(entity_b).get::().unwrap().0, 10); - assert_eq!(world.entity(entity_).get::().unwrap().0, 10); + assert_eq!(world.entity(entity_c).get::().unwrap().0, 10); } #[test] From fd755c549de2c07f7043667d11177c76ef73c79e Mon Sep 17 00:00:00 2001 From: laund Date: Wed, 27 May 2026 23:15:52 +0200 Subject: [PATCH 03/14] Finish initial pass on both lib.rs --- crates/bevy_ecs/src/template.rs | 2 + crates/bevy_scene/macros/src/lib.rs | 16 +- crates/bevy_scene/src/lib.rs | 289 ++++++++++++++++++---------- 3 files changed, 196 insertions(+), 111 deletions(-) diff --git a/crates/bevy_ecs/src/template.rs b/crates/bevy_ecs/src/template.rs index a65fc224d0ffa..041c8f9441e53 100644 --- a/crates/bevy_ecs/src/template.rs +++ b/crates/bevy_ecs/src/template.rs @@ -408,6 +408,8 @@ impl FromTemplate for T { pub trait SpecializeFromTemplate: Sized {} /// A [`Template`] reference to an [`Entity`]. +/// +/// This is only valid during scene spawning and should **never** be used as a [`Component`](bevy_ecs::prelude::Component) field. #[derive(Copy, Clone, Default, Debug)] pub enum EntityTemplate { /// A reference to a specific [`Entity`] diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index 28a64e7966f91..e342bc11c1f98 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -6,15 +6,15 @@ use syn::{parse_macro_input, DeriveInput}; /// Macro which returns a [`Scene`](https://docs.rs/bevy/latest/bevy/prelude/trait.Scene.html), comprehensive docs at [`bevy_scene`](https://docs.rs/bevy/latest/bevy/scene/index.html) /// -/// Example, intended as a hint for allowed syntax.: +/// Example macro, intended as a hint for allowed syntax.: /// ```rust,ignore /// bsn! { -/// :some_scene() // inherit from a scene function, its +/// :some_scene // include a cached scene function /// #SomeName // entity name, will insert Name("SomeName") -/// ComponentA // component without a value will use default -/// ComponentB(0.0) // passing a value, other fields will use default +/// ComponentA // component without a value will use its Default +/// ComponentB(0.0) // passing a value, other fields will use its Default /// Node { -/// height: px(0.1) // same with named fields, unmentioned ones stay default +/// height: px(0.1) // same with named fields, other fields will be Default /// } /// on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer /// let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA @@ -25,16 +25,16 @@ use syn::{parse_macro_input, DeriveInput}; /// , // entities are comma-separated /// (:other_scene() #Child3), // parentheses around a single entity optional /// Link(#SomeName), // pasing a entity reference to a component as `Entity`, component has to imlement FromTemplate -/// :MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from +/// @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from /// @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments /// normal_field: 5 // while normal fields are the actual fields of the component /// }, /// ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} -/// :Container { +/// @Container { /// @items: { /// bsn_list![ // sometimes you may need to nest macro calls /// #item1 SomeComponent, // note: the name #item1 here is in its own scope -/// :some_scene() #item2 +/// :some_scene #item2 /// ] /// } /// } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 919b1584ebc97..6b32da3d23690 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -26,12 +26,12 @@ //! by providing a terse syntax for defining [`Scene`]s inline. //! This brevity is essential: making it easier to review and understand scenes at a glance, //! resolve merge conflicts and keep file sizes under control. -//! The macro includes full Rust Analyzer support (autocomplete, go-to-definition, hover docs)! +//! The macro includes best-effort Rust-Analyzer support. Autocomplete, go-to-definition, and hover docs +//! should work basically everywhere! //! //! ## BSN syntax reference //! -//! For a quick rundown on how to read and write BSN syntax, -//! see the docs for [`bsn!`]. +//! For a quick rundown on how to read and write BSN syntax, see the docs for [`bsn!`]. //! //! ## Quick Start //! @@ -63,8 +63,7 @@ //! // #Player adds a `Name("Player")` component to the root entity. //! // Children spawns two child entities: one with Sword, one with Shield. //! world.spawn_scene(bsn! { -//! // This names the entity "Player" -//! #Player +//! #Player // This names the entity "Player" //! Score(0) //! Children [ //! Sword, @@ -75,36 +74,32 @@ //! //! ## Core Concepts //! -//! - **[`Scene`]**: the main authoring type. A [`Scene`] is made up of [`Template`]s (one -//! per component, plus any related entities such as children). Use the [`bsn!`] macro to -//! create a scene. -//! - **[`SceneList`]**: a list of scenes, each producing a separate root entity. -//! Think of it as the `Vec` analogue to [`Scene`]'s single `Entity`. -//! Use the [`bsn_list!`] macro to create a scene list. -//! - **[`Template`]**: a data description that produces a component value at spawn time, -//! given access to the entity and world (e.g. resolving asset paths into handles, or -//! named entity references into [`Entity`] ids). Each component in a [`Scene`] is -//! represented by a [`Template`] rather than a concrete value. -//! - **[`FromTemplate`]**: associates a type with its canonical [`Template`]. Derive -//! [`FromTemplate`] on your components to generate a companion template type where each -//! field is independently set-or-unset, enabling per-field overrides. -//! - **[`ScenePatch`]**: an [`Asset`] that wraps a [`Scene`] together with its dependencies -//! and its [`ResolvedScene`] (once loaded). You'll encounter this when using asset -//! inheritance (`:"enemy.bsn"`), or when you want to treat a scene as a loadable, -//! hot-reloadable prefab. See also [`ScenePatchInstance`] for applying one to an entity. -//! - **[`ResolvedScene`]**: the fully-resolved, ready-to-spawn result produced by resolving -//! one or more [`Scene`]s. User code rarely interacts with this directly. +//! - **[`Scene`]**: Describes what a spawned [`Entity`] should look like, created using [`bsn!`] or, +//! in the future, `.bsn` asset files. Conceptually, a [`Scene`] contains a list of "entries" to apply to an [`Entity`]. +//! - **Scene Composition**: Composition works by including scenes in other scenes. The included scenes "entries" will be +//! treated as if they were written in the outer scene. +//! - **[`SceneList`]**: A list of scenes, returned by [`bsn_list!`]. +//! Each of the scenes in this still produces one [`Entity`]. +//! - **[`Template`]**: Represents a kind of "change" in a [`Scene`]. +//! This is how [`Components`](Component) and a few other things are added to a [`Scene`]. +//! [`Templates`](Template) allow per-field overrides, [see below for details](#composition-and-patching) +//! When spawning, they get access to the [`Entity`] and the ECS [`World`], allowing for +//! some more complex behaviour. [`Template`] is automatically implemented for types which have +//! [`Default`] + [`Clone`] (preferred) or [`FromTemplate`] (when fields implement [`FromTemplate`]). +//! - **[`RelatedScenes`]**: These add a [`SceneList`] as related to this [`Scene`] by a specific [`relationship`](bevy_ecs::relationship::Relationship). +//! This is the kind of "change" added to the [`Scene`] when using a [`RelationshipTarget`] component like [`Children`]. //! //! ## Spawning Scenes //! //! There are two approaches to spawning scenes: //! //! - **Immediate**: [`World::spawn_scene`] and [`Commands::spawn_scene`] -//! resolve and spawn in one step. Returns an error if any asset dependencies are not yet loaded. -//! Use this when your scene has no asset dependencies. +//! resolve and spawn in one step. +//! Returns an error if any asset dependencies are not yet loaded. //! - **Queued**: [`World::queue_spawn_scene`] and [`Commands::queue_spawn_scene`] //! register the scene's dependencies and wait for them to load before resolving and spawning. -//! Use this when inheriting from asset-based scenes. +//! If nothing needs to be loaded, this will spawn during this frames [`SpawnScene`] schedule, +//! between [`Update`](bevy_app::main_schedule::Update) and [`PostUpdate`](bevy_app::main_schedule::PostUpdate). //! //! In all cases, your `*_spawn_scene` method call should wrap an invocation of the [`bsn!`] macro, //! or call a function which returns a [`Scene`]. @@ -115,17 +110,17 @@ //! ## Entity Hierarchies and Relationships //! //! Use `Children [scene1, scene2]` inside [`bsn!`] to spawn child entities. -//! Children (and entities within [`bsn_list!`]) are separated by commas; +//! [`Children`] (and entities within [`bsn_list!`]) are separated by commas; //! add multiple components to the same entity by listing them without a comma: //! //! ```ignore -//! // Spawns one child entity +//! // Spawns one child entity with A, B an C //! bsn! { #Parent Children [ComponentA ComponentB ComponentC] } //! -//! // Spawns two child entities due to the added comma +//! // Spawns two child entities, one with A and B, the other with C, due to the added comma //! bsn! { #Parent Children [ComponentA ComponentB, ComponentC] } //! -//! // Spawns two child entities, but more clearly +//! // Spawns two child entities, but more clearly separated due to parentheses. //! bsn! { #Parent Children [(ComponentA ComponentB), ComponentC] } //! ``` //! @@ -165,10 +160,14 @@ //! #Child2 //! ComponentA //! Children [ -//! #GrandChild1 -//! ComponentA, -//! #GrandChild2 -//! ComponentB +//! ( +//! #GrandChild1 +//! ComponentA +//! ), +//! ( +//! #GrandChild2 +//! ComponentB +//! ) //! ] //! ), //! ] @@ -181,21 +180,44 @@ //! //! ## Named Entity References //! -//! The `#Name` syntax assigns a [`Name`] to an entity and registers it for cross-referencing. -//! Other entities in the same **scope** can refer to a named entity by its `#Name`, -//! receiving the resolved [`Entity`] id at spawn time. +//! The `#Name` syntax assigns a [`Name`] to an entity and registers it for cross-referencing within the same macro invocation. +//! In a few others places in the same invocation, its possible to refer to a named entity by its `#Name`: +//! - `my_scene(#Name)` +//! - `Component(#Name)` +//! - `Component { entity: #Name }` +//! +//! All of these will initially recieve a [`EntityTemplate`], which for [`Components`](Component) +//! will be resolved to an [`Entity`] using [`FromTemplate`]. This is why [`Components`](Component) which contain +//! an [`Entity`] field should likely derive [`FromTemplate`] instead of [`Default`]. +//! +//! ```ignore +//! bsn!{ +//! needs_last_child_scene(#Last) +//! Children [ +//! #First, +//! #Second, +//! #Last +//! ] +//! } +//! ``` //! //! ### Scope rules //! //! Each [`bsn!`] invocation creates its own name scope. A name is visible to the root -//! entity, its children, and any deeper descendants — as long as the reference is written -//! in the same [`bsn!`] call. Composed or cached scenes (via `my_scene()` or `:my_scene`) -//! each bring their own separate scope, so names do not leak across scene boundaries. -//! However, the results of a named entity reference, a [`EntityTemplate`], can be passed to -//! composed or inherited scenes, since it represents a sort of global identifier for the name. +//! entity, its children, and any deeper descendants in the same call. +//! Composed scenes (via `my_scene(#Name)`) or [`SceneComponents`](SceneComponent) +//! each contain their own [`bsn!`] invocation and therefore their own scope, +//! so re-using the same name across multiple different scenes is fine. +//! However, the results of a named entity reference, the [`EntityTemplate`], +//! can be passed to other scenes. Its valid only during the spawning of a scene. +//! That means [`Components`](Component) should never store [`EntityTemplate`] fields, +//! they should store the resolved [`Entity`] instead and +//! derive [`FromTemplate`] to convert [`EntityTemplate`] automatically. //! //! If both a parent and a composed child define the same name (e.g. both use `#X`), //! each scope's `#X` resolves to its own entity, avoiding conflicts or potentially unintuitive shadowing. +//! The same way, individual executions of the [`bsn!`]-generated block will also resolve to different entities, +//! even during the same spawn. This allows, for example, loops containing [`bsn!`] to create different entities. //! //! In a [`bsn_list!`], all root entities share a single name scope, so sibling scenes //! can reference each other by name. This is useful for wiring up relationships between @@ -213,14 +235,14 @@ //! //! ## Composition and Patching //! -//! When you insert a component in normal ECS code, the entire pre-existing value is replaced. -//! If a base scene sets `Button { width: 100, height: 300 }` and a caller wants to +//! When you insert a component into an [`Entity`] in normal ECS code, the entire pre-existing value is replaced. +//! If a scene sets `Button { width: 100, height: 300 }` and a caller wants to //! change just `width`, ordinary component insertion would force them to respecify `height` too. //! //! **Patching** avoids this. When you write `Button { width: 200 }` in [`bsn!`], it creates //! a *patch* that sets only the `width` field. Unmentioned fields keep their existing values -//! (from a parent scene, an earlier patch, or the type's defaults). Multiple patches to the -//! same component merge together rather than overwriting each other. +//! (from a included scene, an earlier patch, or the type's defaults). Multiple patches to the +//! same component and its values are applied in order rather than overwriting each other. //! //! To make a component available in [`bsn!`], derive either [`Default`] + [`Clone`], or [`FromTemplate`]. //! Both support patching: unmentioned fields keep their values from earlier patches or the @@ -228,8 +250,8 @@ //! //! The distinction is about what values a field can hold at spawn time: //! -//! - **[`Clone`] + [`Default`]** (e.g. `#[derive(Component, Default, Clone)]`): covers the simple case, and should be your default choice. -//! The blanket [`Template`] impl handles patching automatically with no scene-specific derives needed. +//! - **[`Clone`] + [`Default`]** (e.g. `#[derive(Component, Default, Clone)]`): covers the simple case, and should be your default choice. +//! The blanket [`Template`] impl handles patching automatically with no scene-specific derives needed. //! //! - **[`FromTemplate`]** (e.g. `#[derive(Component, FromTemplate)]`) is needed when a field requires spawn-time context. //! Examples include [`Handle`] fields which need [`AssetServer`] to resolve asset paths, or [`Entity`] @@ -268,7 +290,7 @@ //! //! // Compose `enemy()` and patch just the `max` field: //! world.spawn_scene(bsn! { -//! enemy() +//! :enemy //! Health { max: 200 } //! }); //! ``` @@ -280,55 +302,37 @@ //! For programmatic patching outside of [`bsn!`], see the [`PatchFromTemplate`] and //! [`PatchTemplate`] traits. //! -//! ## Scene Inheritance -//! -//! There are two ways to build on an existing scene: **inline composition** and **inheritance**. -//! Both let you patch fields on top of the parent, and both merge children from parent -//! and child (parent's children appear first). They differ in *when* the parent is resolved -//! and what kinds of parents they support. +//! ## Scene Caching //! -//! **Inline composition** (shown in the example above) calls a function directly inside -//! [`bsn!`]. The parent's templates are merged *unresolved* alongside the child's, and -//! everything resolves together in one pass: +//! You may have noticed the `:enemy` syntax above. Thats a feature called "scene caching" which allows you +//! to cache a scene or scene component included into another scene if its the first one and if it does not take any arguments. //! +//! No caching, calling without `:` //! ```ignore -//! // Inline composition: call with parentheses, no `:` //! bsn! { //! enemy() //! Health { max: 200 } //! } //! ``` //! -//! **Inheritance** uses the `:` prefix. The parent is pre-resolved and its templates are -//! flattened into a [`ResolvedScene`] *before* the child's templates/patches apply on top: -//! +//! Caching, no call, with `:` //! ```ignore -//! // Inheritance: `:` prefix, no parentheses or arguments //! bsn! { //! :enemy //! Health { max: 200 } //! } +//! ``` //! -//! // Asset inheritance: `:` prefix with a string path to a ScenePatch asset -//! // DISCLAIMER: .bsn file format is not yet released! +//! BSN assets always need to be cached using the `:` prefix. +//! Note that the `.bsn` file format is not yet released. +//! ```ignore //! bsn! { //! :"enemy.bsn" //! Health { max: 200 } //! } //! ``` //! -//! ### Which composition pattern should I choose? -//! -//! | | Function inheritance `:my_scene` | Asset inheritance `:"my_scene.bsn"` | Inline composition `my_scene()` | -//! |---------------------------|-------------------------------------|-------------------------------------|----------------------------------| -//! | Accepts parameters | Yes | No | Yes | -//! | Asset-based | No | Yes | No | -//! | Cached resolution | Parameterless scenes only | Yes | No | -//! -//! Prefer scene inheritance over inline composition in general: the expensive scene resolution is cached, saving work during reuse. -//! Inline composition should be reserved for parameterized scenes that vary based on a given input, -//! small scenes that are shared across contexts (like styles), -//! or one-off scenes that do not require reuse. +//! Note: Caching is not yet fully implemented. All of the structure is there, but its not fully wired up yet. //! //! ## Loading Assets into Scenes //! @@ -339,8 +343,8 @@ //! commands.spawn(Sprite { image: handle, ..default() }); //! ``` //! -//! This can be particularly frustrating when defining helper functions, -//! requiring you to pipe asset handles or collections through multiple layers of function calls. +//! This was particularly frustrating when defining helper functions for spawning entities, +//! requiring you to pass [`AssetServer`] or handles through multiple layers of function calls. //! //! In [`bsn!`], asset paths work directly as field values. When a component field is a //! [`Handle`], the [`bsn!`] macro accepts a string literal in its place. Under the hood, @@ -354,8 +358,7 @@ //! }); //! ``` //! -//! This also works for components you define yourself. Any `Handle` field on a -//! [`FromTemplate`]-derived component automatically accepts asset paths: +//! A [`Component`] has to also derive [`FromTemplate`] to accept asset paths: //! //! ```ignore //! #[derive(Component, FromTemplate)] @@ -379,16 +382,13 @@ //! //! Use [`on(func)`](on) inside [`bsn!`] to attach an entity [`Observer`]. Entity observers are closures or //! functions which fire when a given [`EntityEvent`] is triggered and targets this entity. -//! The first parameter's type determines which event is observed. Multiple observers can be addedc to -//! the same entity, and the observer has full access to the ECS via [system parameters]: +//! The first parameter's type determines which event is observed. Multiple observers can be added to +//! the same entity, and the observer has full access to the ECS via [system parameters](bevy_ecs::system#system-parameter-list): //! //! ```ignore //! #[derive(EntityEvent)] //! struct Damage(u32); //! -//! #[derive(EntityEvent)] -//! struct Heal(u32); -//! //! fn is_dead(ev: On, query: Query<&Health>){ //! let health = query.get(ev.event_target()).unwrap(); //! if health.current == 0 { @@ -404,10 +404,6 @@ //! let mut health = query.get_mut(damage.event_target()).unwrap(); //! health.current = health.current.saturating_sub(damage.0); //! }) -//! on(|heal: On, mut query: Query<&mut Health>| { -//! let mut health = query.get_mut(heal.event_target()).unwrap(); -//! health.current = (health.current + heal.0).min(health.max); -//! }) //! on(is_dead) //! } //! } @@ -535,7 +531,7 @@ //! } //! ``` //! -//! This enables inheriting the [`SceneComponent`] as a scene, using the following syntax: +//! This enables including the [`SceneComponent`] as a scene, using the following syntax: //! //! ```no_run //! # use bevy_scene::prelude::*; @@ -550,6 +546,7 @@ //! # let mut world = World::new(); //! world.spawn_scene(bsn! { //! @Player { score: 0 } +//! Camera3d //! }); //! ``` //! @@ -577,6 +574,20 @@ //! } //! ``` //! +//! This can sometimes be useful, for example if a scene function needs a generic type or constant: +//! +//! ``` +//! # use bevy_scene::prelude::*; +//! # use bevy_ecs::prelude::*; +//! #[derive(SceneComponent, Default, Clone)] +//! #[scene(player::<5>)] +//! struct Player; +//! +//! fn player() -> impl Scene { +//! bsn! { /* scene using ID here */} +//! } +//! ``` +//! //! ### `SceneComponent` Asset Paths //! //! (Note: Currently, there is no `.bsn` asset format. This exists to help you understand whats planned.) @@ -680,8 +691,8 @@ //! ``` //! //! Notice the `@field` syntax, which specifies that a prop is being set instead of a field. -//! Props are evaluated "immediately" at the point of inheritance where the scene is constructed. -//! This means that they are not "patchable". +//! Props are evaluated "immediately" when the scene is included in another. +//! This means that they are not "patchable" since they can be used for any purpose. //! //! You can set _both_ props and normal fields at the same time: //! ```no_run @@ -730,7 +741,7 @@ //! } //! ``` //! However you _can_ patch the scene component in the scene if you would like. This comes in handy -//! if you would like props to contribute to the scene component's value: +//! if you would like props to contribute to the scene component's fields: //! //! ``` //! # use bevy_scene::prelude::*; @@ -758,6 +769,9 @@ //! They are functionally quite different however. It is worth understanding the differences and //! tradeoffs: //! +// TODO: I (laund) don't like this section, and would like to consider removing/reworking it. +// it feels overly verbose and compares sceen components and required components somewhat +// holistically, which feels out of place in these docs. //! - **Required Components**: Context-less (ex: Default constructors), non-hierarchical, can always //! be applied immediately, not dependency aware, automatically enforced at runtime as components //! are added, not patchable, pretty low overhead, not a lot of features / functionality @@ -1057,6 +1071,38 @@ mod tests { assert_eq!(name.as_str(), "Y"); } + #[test] + fn cached_patching_order() { + let mut app = test_app(); + let world = app.world_mut(); + + #[derive(Component, FromTemplate)] + struct Position { + x: f32, + y: f32, + z: f32, + } + + fn a() -> impl Scene { + bsn! { + Position { x: 2. } + } + } + + fn b() -> impl Scene { + bsn! { + Position { x: 1., y: 1., z: 1. } + :a + } + } + + let root = world.spawn_scene(b()).unwrap(); + let position = root.get::().unwrap(); + assert_eq!(position.x, 2.); + assert_eq!(position.y, 2.); + assert_eq!(position.z, 2.); + } + #[test] fn loaded_asset_cached_patching() { #[derive(Component, FromTemplate)] @@ -1374,6 +1420,30 @@ mod tests { .0 ); } + #[test] + fn bsn_reverse_reference() { + let mut app = test_app(); + let world = app.world_mut(); + + fn a() -> impl Scene { + bsn! { + Reference(#Last) + Children [ + #First, + #Second, + #Last + ] + } + } + let id = world.spawn_scene(a()).unwrap().id(); + let ref_id = world.entity(id).get::().unwrap(); + + let children = world.entity(id).get::().unwrap(); + assert_eq!(children[2], ref_id.0); + + let name = world.entity(ref_id.0).get::().unwrap(); + assert_eq!(name.as_str(), "Last"); + } #[test] fn bsn_list_name_references() { @@ -1602,7 +1672,7 @@ mod tests { let scene: Box = if is_boss { Box::new(bsn! { Boss - Children [ :unit(false, level - 1) #Grunt1, :unit(false, level - 1) #Grunt2] + Children [ unit(false, level - 1) #Grunt1, unit(false, level - 1) #Grunt2] }) } else { Box::new(bsn! { Grunt }) @@ -1680,7 +1750,7 @@ mod tests { // inheritance is the same! let entity_b = bsn! { - :armor() + :armor Health { current: 100, max: 100 } }; let idb = world.spawn_scene(entity_b).unwrap().id(); @@ -1724,7 +1794,7 @@ mod tests { // inheritance let scene_a = bsn! { - :unit_with_armor() + :unit_with_armor armor() }; @@ -2317,6 +2387,19 @@ mod tests { .unwrap(); assert_eq!(entity.get::().unwrap().value, 10); assert_eq!(entity.get::().unwrap().len(), 2); + + fn const_val() -> impl Scene { + bsn! { + @Widget { + @children: N + } + } + } + #[derive(SceneComponent, Clone, Default)] + #[scene(const_val::<5>)] + struct SpecificWidget; + let entity = world.spawn_scene(bsn! { @SpecificWidget }).unwrap(); + assert_eq!(entity.get::().unwrap().len(), 5); } #[test] @@ -2643,7 +2726,7 @@ mod tests { // why not doctests? because the macro can't depend on this crate // why not include! it here and include_str! it in the docs? because rust-analyzer ignores #[doc = include_str!()] and this is mostly a showcase for rust-analyzer let scene = bsn! { - :some_scene() // inherit from a scene function, its + :some_scene // inherit from a scene function, its #SomeName // entity name, will insert Name("SomeName") ComponentA // component without a value will use default ComponentB(0.0) // passing a value, other fields will use default @@ -2657,9 +2740,9 @@ mod tests { Children [ // spawning multiple related entities using a RelationshipTarget component #Child1 ComponentA // whitespace doesn't have to be newlines , // entities are comma-separated - (:other_scene() #Child3), // parentheses around a single entity optional + (:other_scene #Child3), // parentheses around a single entity optional Link(#SomeName), // pasing a entity reference to a component as `Entity`, component has to imlement FromTemplate - :MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from + @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments normal_field: 5 // while normal fields are the actual fields of the component }, @@ -2667,11 +2750,11 @@ mod tests { width: some_variable } ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} - :Container { + @Container { @items: { bsn_list![ // sometimes you may need to nest macro calls #item1 SomeComponent, // note: the name #item1 here is in its own scope - :some_scene() #item2 + :some_scene #item2 ] } } From 753395a76d1c8f6ffba3d7e1dd107c8f95719a3f Mon Sep 17 00:00:00 2001 From: laund Date: Thu, 28 May 2026 00:23:40 +0200 Subject: [PATCH 04/14] update and add trait tags for scenes --- Cargo.toml | 3 +++ docs-rs/trait-tags.html | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4f032612a2dee..bf7e497f92fb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5204,6 +5204,9 @@ rustdoc-args = [ "docs-rs/trait-tags.html", ] all-features = true +# approximately equal comand for docs.rs like build locally +# excluding example scraping and adding mergable info for compile speed +# cargo doc --lib -Zrustdoc-mergeable-info --config 'build.rustflags=["--cfg", "docsrs_dep"]' -Zhost-config -Ztarget-applies-to-host --config 'host.rustflags=["--cfg", "docsrs_dep"]' --config 'build.rustdocflags=["-Zunstable-options", "--generate-link-to-definition", "--generate-macro-expansion", "--html-after-content", "./docs-rs/trait-tags.html"]' --no-deps -p bevy --all-features [[example]] name = "monitor_info" diff --git a/docs-rs/trait-tags.html b/docs-rs/trait-tags.html index 07d09ae2e57f7..c271b057d957c 100644 --- a/docs-rs/trait-tags.html +++ b/docs-rs/trait-tags.html @@ -18,7 +18,13 @@ 'SystemSet', 'SystemParam', 'Relationship', - 'RelationshipTarget' + 'RelationshipTarget', + 'Scene', + 'SceneList', + 'Template', + 'FromTemplate', + 'SceneComponent' + ]; // Find all traits that are implemented by the current type. @@ -29,6 +35,16 @@ implementedBevyTraits.delete("Component"); } + // SceneComponent already implies component + if (implementedBevyTraits.has("SceneComponent")) { + implementedBevyTraits.delete("Component"); + } + + // FromTemplate already implies Template + if (implementedBevyTraits.has("FromTemplate")) { + implementedBevyTraits.delete("Template"); + } + // Check if an implemented trait has a `type Mutability = Immutable` associated type. // This is used to determine if a `Component` is immutable or not. // TODO: Ideally we should just check the associated types of the `Component` trait, @@ -186,4 +202,23 @@ .relationshiptarget-tag { --tag-color: oklch(50% 27% 150); } + + .relationship-tag, + .relationshiptarget-tag { + --tag-color: oklch(50% 27% 150); + } + + .scene-tag, + .scenelist-tag { + --tag-color: oklch(50% 30% 300); + } + + .template-tag, + .fromtemplate-tag { + --tag-color: oklch(50% 25% 58); + } + + .scenecomponent-tag { + --tag-color: oklch(50% 27% 20); + } \ No newline at end of file From 85564b67fb96a37e076494add76efd3aafce13ec Mon Sep 17 00:00:00 2001 From: laund Date: Thu, 28 May 2026 00:34:20 +0200 Subject: [PATCH 05/14] fix typos --- crates/bevy_scene/macros/src/lib.rs | 17 ++++--- crates/bevy_scene/src/lib.rs | 72 ++++++++++++++--------------- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index e342bc11c1f98..fd526ddc8aedf 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -9,12 +9,12 @@ use syn::{parse_macro_input, DeriveInput}; /// Example macro, intended as a hint for allowed syntax.: /// ```rust,ignore /// bsn! { -/// :some_scene // include a cached scene function +/// :some_scene // inherit from a scene function, its /// #SomeName // entity name, will insert Name("SomeName") -/// ComponentA // component without a value will use its Default -/// ComponentB(0.0) // passing a value, other fields will use its Default +/// ComponentA // component without a value will use default +/// ComponentB(0.0) // passing a value, other fields will use default /// Node { -/// height: px(0.1) // same with named fields, other fields will be Default +/// height: px(0.1) // same with named fields, unmentioned ones stay default /// } /// on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer /// let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA @@ -23,12 +23,15 @@ use syn::{parse_macro_input, DeriveInput}; /// Children [ // spawning multiple related entities using a RelationshipTarget component /// #Child1 ComponentA // whitespace doesn't have to be newlines /// , // entities are comma-separated -/// (:other_scene() #Child3), // parentheses around a single entity optional -/// Link(#SomeName), // pasing a entity reference to a component as `Entity`, component has to imlement FromTemplate +/// (:other_scene #Child3), // parentheses around a single entity optional +/// Link(#SomeName), // passing a entity reference to a component as `Entity`, component has to implement FromTemplate /// @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from /// @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments /// normal_field: 5 // while normal fields are the actual fields of the component /// }, +/// Node { +/// width: some_variable +/// } /// ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} /// @Container { /// @items: { @@ -39,7 +42,7 @@ use syn::{parse_macro_input, DeriveInput}; /// } /// } /// ] -/// } +/// }; /// ``` #[doc(hidden)] #[proc_macro] diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 6b32da3d23690..36a75f29d060d 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -84,7 +84,7 @@ //! This is how [`Components`](Component) and a few other things are added to a [`Scene`]. //! [`Templates`](Template) allow per-field overrides, [see below for details](#composition-and-patching) //! When spawning, they get access to the [`Entity`] and the ECS [`World`], allowing for -//! some more complex behaviour. [`Template`] is automatically implemented for types which have +//! some more complex behavior. [`Template`] is automatically implemented for types which have //! [`Default`] + [`Clone`] (preferred) or [`FromTemplate`] (when fields implement [`FromTemplate`]). //! - **[`RelatedScenes`]**: These add a [`SceneList`] as related to this [`Scene`] by a specific [`relationship`](bevy_ecs::relationship::Relationship). //! This is the kind of "change" added to the [`Scene`] when using a [`RelationshipTarget`] component like [`Children`]. @@ -186,7 +186,7 @@ //! - `Component(#Name)` //! - `Component { entity: #Name }` //! -//! All of these will initially recieve a [`EntityTemplate`], which for [`Components`](Component) +//! All of these will initially receive a [`EntityTemplate`], which for [`Components`](Component) //! will be resolved to an [`Entity`] using [`FromTemplate`]. This is why [`Components`](Component) which contain //! an [`Entity`] field should likely derive [`FromTemplate`] instead of [`Default`]. //! @@ -770,7 +770,7 @@ //! tradeoffs: //! // TODO: I (laund) don't like this section, and would like to consider removing/reworking it. -// it feels overly verbose and compares sceen components and required components somewhat +// it feels overly verbose and compares scene components and required components somewhat // holistically, which feels out of place in these docs. //! - **Required Components**: Context-less (ex: Default constructors), non-hierarchical, can always //! be applied immediately, not dependency aware, automatically enforced at runtime as components @@ -907,7 +907,7 @@ use bevy_ecs::prelude::*; /// | Example | Meaning | /// | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | /// | `#Child1 CompA, #Child2` | Spawns 2 children, one with `(Name("Child1"), CompA::default())` and the other with `Name("Child2")` | -/// | `(#Child1 CompA), (#Child2)` | Same as above, with explicity parentheses | +/// | `(#Child1 CompA), (#Child2)` | Same as above, with explicit parentheses | /// | `#First, { expr }, #Last ` | Spawns an entity with name `First`, then every entity from the `SceneList` returned by expr, then one named `Last` | /// | `#First, ({ expr }), #Last ` | Same as above, but the `expr` should result in a `Scene` and will only spawn one entity using it | /// @@ -2726,39 +2726,39 @@ mod tests { // why not doctests? because the macro can't depend on this crate // why not include! it here and include_str! it in the docs? because rust-analyzer ignores #[doc = include_str!()] and this is mostly a showcase for rust-analyzer let scene = bsn! { - :some_scene // inherit from a scene function, its - #SomeName // entity name, will insert Name("SomeName") - ComponentA // component without a value will use default - ComponentB(0.0) // passing a value, other fields will use default - Node { - height: px(0.1) // same with named fields, unmentioned ones stay default - } - on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer - let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA - b.0 += evt.value; - }) - Children [ // spawning multiple related entities using a RelationshipTarget component - #Child1 ComponentA // whitespace doesn't have to be newlines - , // entities are comma-separated - (:other_scene #Child3), // parentheses around a single entity optional - Link(#SomeName), // pasing a entity reference to a component as `Entity`, component has to imlement FromTemplate - @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from - @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments - normal_field: 5 // while normal fields are the actual fields of the component - }, - Node { + :some_scene // inherit from a scene function, its + #SomeName // entity name, will insert Name("SomeName") + ComponentA // component without a value will use default + ComponentB(0.0) // passing a value, other fields will use default + Node { + height: px(0.1) // same with named fields, unmentioned ones stay default + } + on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer + let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA + b.0 += evt.value; + }) + Children [ // spawning multiple related entities using a RelationshipTarget component + #Child1 ComponentA // whitespace doesn't have to be newlines + , // entities are comma-separated + (:other_scene #Child3), // parentheses around a single entity optional + Link(#SomeName), // passing a entity reference to a component as `Entity`, component has to implement FromTemplate + @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from + @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments + normal_field: 5 // while normal fields are the actual fields of the component + }, + Node { width: some_variable - } - ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} - @Container { - @items: { - bsn_list![ // sometimes you may need to nest macro calls - #item1 SomeComponent, // note: the name #item1 here is in its own scope - :some_scene #item2 - ] - } - } - ] + } + ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} + @Container { + @items: { + bsn_list![ // sometimes you may need to nest macro calls + #item1 SomeComponent, // note: the name #item1 here is in its own scope + :some_scene #item2 + ] + } + } + ] }; // just checking it spawns correctly let mut app = test_app(); From b053e2f484168a07e2b3871fd38c0a29c0eefaf8 Mon Sep 17 00:00:00 2001 From: laund Date: Mon, 1 Jun 2026 17:18:22 +0200 Subject: [PATCH 06/14] Document bsn asset format can be implemented externally --- crates/bevy_scene/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index d407de0795cf1..367df9b8e257b 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -790,20 +790,24 @@ //! - Is spawn performance a very high priority? Use required components. //! //! ## .bsn Asset Format +//! Bevy does not currently have support for `.bsn` files, +//! but intends to offer a `.bsn` asset format in future releases. //! -//! In future releases, Bevy intends to offer a `.bsn` asset format. //! This would allow you to define your scenes on disk, //! creating/modifying them in various authoring tools and using asset hot-reloading. //! //! This format is intended to have broad syntactic compatibility with the `bsn!` macro, //! making it easy to port your content between both the macro and the asset form. //! -//! Bevy does not currently have support for `.bsn` files: -//! for now, you should use existing non-Bevy asset formats like glTF, +//! When planning to use the asset format later, be aware that `.bsn` asset files, +//! unlike `bsn!` macro calls, will not support expressions or other dynamic features directly. +//! +//! For now, you should use existing non-Bevy asset formats like glTF, //! search for ecosystem implementations or stick to `bsn!` macro calls. //! -//! When planning, be aware that `.bsn` asset files, unlike `bsn!` macro calls, -//! will not support expressions or other dynamic features directly. +//! The surrounding architecture in the macro and other systems to implement such an asset format already exists, +//! allowing community implementations/experimentation until an official version exists. An example of how to go about this +//! can be found in the [scene benchmarks]() //! //! [`Template`]: bevy_ecs::template::Template //! [`FromTemplate`]: bevy_ecs::template::FromTemplate From e93c15420290a0b7dd30de55add8f69b51aeb969 Mon Sep 17 00:00:00 2001 From: laund Date: Mon, 1 Jun 2026 17:41:55 +0200 Subject: [PATCH 07/14] fix macro docs --- crates/bevy_scene/macros/src/lib.rs | 38 ++--- crates/bevy_scene/src/lib.rs | 246 ++++++++++------------------ 2 files changed, 103 insertions(+), 181 deletions(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index fd526ddc8aedf..d251f26eda952 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -6,38 +6,38 @@ use syn::{parse_macro_input, DeriveInput}; /// Macro which returns a [`Scene`](https://docs.rs/bevy/latest/bevy/prelude/trait.Scene.html), comprehensive docs at [`bevy_scene`](https://docs.rs/bevy/latest/bevy/scene/index.html) /// -/// Example macro, intended as a hint for allowed syntax.: +/// Example macro showcasing most syntax (complex, most scenes won't look like this): /// ```rust,ignore /// bsn! { -/// :some_scene // inherit from a scene function, its -/// #SomeName // entity name, will insert Name("SomeName") -/// ComponentA // component without a value will use default -/// ComponentB(0.0) // passing a value, other fields will use default +/// some_scene() // include a scene function +/// #SomeName // entity name, will insert Name("SomeName") +/// ComponentA // component without a value will use default +/// ComponentB(0.0) // passing a value, other fields will use default /// Node { /// height: px(0.1) // same with named fields, unmentioned ones stay default /// } -/// on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer -/// let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA +/// on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer +/// let mut b = query.get_mut(evt.entity).unwrap(); /// b.0 += evt.value; /// }) -/// Children [ // spawning multiple related entities using a RelationshipTarget component -/// #Child1 ComponentA // whitespace doesn't have to be newlines -/// , // entities are comma-separated -/// (:other_scene #Child3), // parentheses around a single entity optional -/// Link(#SomeName), // passing a entity reference to a component as `Entity`, component has to implement FromTemplate -/// @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from -/// @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments -/// normal_field: 5 // while normal fields are the actual fields of the component +/// Children [ // spawning multiple related entities using a RelationshipTarget component +/// #Child1 ComponentA // whitespace doesn't have to be newlines +/// , // entities are comma-separated +/// (other_scene() #Child3), // parentheses around a single entity are optional +/// Link(#SomeName), // passing a entity reference to a component as `Entity`, component has to implement FromTemplate +/// @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from +/// @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments +/// normal_field: 5 // while normal fields are the actual fields of the component /// }, /// Node { -/// width: some_variable +/// width: some_var // you can directly use variables without {} /// } -/// ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} +/// ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} /// @Container { /// @items: { -/// bsn_list![ // sometimes you may need to nest macro calls +/// bsn_list![ // sometimes you may need to nest macro calls /// #item1 SomeComponent, // note: the name #item1 here is in its own scope -/// :some_scene #item2 +/// some_scene() #item2 /// ] /// } /// } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 367df9b8e257b..37552c7734a06 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -866,38 +866,52 @@ use bevy_ecs::prelude::*; /// /// ## Syntax Reference /// -/// ### Parts of a scene -/// | Examples | Explanation -/// | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -/// | `CompA` | A unit or default component. Fields, if any exist, will be default | -/// | `CompA(val)`
`CompA(val, val)` | Tuple Component with some fields specified. Unspecified fields will be default, see [patching](self#patching)| -/// | `CompA { name: val }` | Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | -/// | `mymodule::CompA { name: val }` | Same as above, but referring to the component by module path | -/// | `MyEnum::Variant` | Enum Component `MyEnum` with the `Variant` variant | -/// | `template_value(component)` | Insert the component value from a variable | -/// | `template_value(Transform::from_xyz(2., 3., 1.,))` | Insert the component value by immediately calling the constructor | -/// | `template(|context| { ... })` | Register a function or closure which returns a Template, and is given [`context`] with full World access | -/// | **Composition and Inheritance** | | -/// | `scene()`
`scene(val)` | Composition with [`Scene`] function | -/// | `{ expr }` | Composition with the result of `expr`, which should be a [`Scene`] | -/// | `:scene()`
`:scene(val)` | Inherit from [`Scene`] function | -/// | `:CompA` | Inherit from [`SceneComponent`]. Fields, if any exist, will be default | -/// | `:CompA { @prop: val }` | Inherit from [`SceneComponent`] with a `prop` field, passed to this components scene function | -/// | `:CompA { name: val }` | Inherit from [`SceneComponent`] with a normal field, works the same as it does for normal components | -/// | `:CompA { @prop: val1, name: val2 }` | Inherit from [`SceneComponent`] with both a `prop` and a field | -/// | `:"scene.bsn"` |
Not yet implemented!
Inherit from a scene asset file | -/// | **Named entity references** | | -/// | `#MyName` | Becomes `Name("MyName")` when used as a `part` of a scene | -/// | `#{ expr }` | Same as `#MyName` but using the result of the expression as the name | -/// | `CompA(#MyName)`
`:scene(#MyName)` | Referring to the entity which was named `MyName` in this scope, results in an [`EntityTemplate`] being passed| -/// | `CompA(#{ expr })`
`:scene(#{ expr })` | Using the result of `expr` as the name. Note: This has a special case, allowing a [`Entity`] id as well | -/// | **Observers** | | -/// | `on(\|ev: On\| { … })` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a closure | -/// | `on(my_observer)` | Attaches an entity [`observer`] that fires when `Ev` targets this entity. In this example, using a function | -/// | **Relationships** | | -/// | `Children []` | Spawns each entry as a child of this entity, see **Scene Lists** below for details | -/// | `ChildOf(entity)` | Makes **this** entity a child of `entity`, accepts a [`Entity`]/[`EntityTemplate`] or a `#Name` reference | -/// | `MyRel []` | Like `Children`, but uses any `RelationshipTarget` component | +/// The syntax consists of scene entries which are listed below, which all act on the scene in some way. +/// Often, this is by inserting/patching a component or its values or including other scenes. +/// Scene entries can have prefix characters which specify/disambiguate the following entry. +/// +/// ```text +/// bsn! { +/// +/// : +/// # +/// @ +/// ~ +/// } +/// ``` +/// +/// ### Scene entries +/// | Examples | Explanation | +/// | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | +/// | `CompA` | A unit or default component. Fields, if any exist, will be default | +/// | `CompA(val)`
`CompA(val, val)` | Tuple Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | +/// | `CompA { name: val }` | Component with some fields specified. Unspecified fields will be default, see [patching](self#patching) | +/// | `mymodule::CompA { name: val }` | Same as above, but referring to the component by module path | +/// | `MyEnum::Variant` | Enum Component `MyEnum` with the `Variant` variant | +/// | `template_value(component)` | Insert the component value from a variable `component` | +/// | `template_value(CompA::from_str("foo"))` | Insert the component value by immediately calling the constructor | +/// | `template(|context| { ... })` | Register a function/closure returning a Template (eg. Component). Its passed [`context`] allowing World access | +/// | `~MyType`
`~MyType {name: var}` | Type implementing [`Template`], the prefix is used to distinguish it from Components which use [`FromTemplate`]| +/// | **Including Scenes** | | +/// | `scene()`
`scene(val)` | Include the result of a `impl `[`Scene`] function | +/// | `{ expr }` | Composition with the result of `expr`, which should be a [`Scene`] | +/// | `@MySceneComp` | Include a [`SceneComponent`]. Fields, if any exist, will be default | +/// | `@MySceneComp { @prop: val }` | Include a [`SceneComponent`] with a `prop` field, passed to this components scene function | +/// | `@MySceneComp { name: val }` | Include a [`SceneComponent`] with a normal field, works the same as it does for normal components | +/// | `@MySceneComp { @prop: val1, name: val2 }` | Include a [`SceneComponent`] with both a `prop` and a field | +/// | `:"scene.bsn"` |
Asset format no yet implemented!
Include a cached scene asset file | +/// | `:scene()`
`:@MySceneComp` |
Caching for scene includes not yet implemented!
Include a cached scene function | +/// | **Named entity references** | | +/// | `#MyName` | Becomes `Name("MyName")` when used as a `part` of a scene | +/// | `CompA(#MyName)`
`scene(#MyName)` | Referring to the entity which was named `MyName` in this scope, results in an [`EntityTemplate`] being passed | +/// | `Name("Foo")` | Manually sets the Name component, can be put after a `#MyName` to use a custom name while allowing references | +/// | **Observers** | | +/// | `on(\|ev: On\| { … })` | Attaches an entity [`observer`] for the [`EntityEvent`] `Ev` to this entity. In this example, using a closure | +/// | `on(my_observer)` | Attaches an entity [`observer`] for the [`EntityEvent`] `Ev` to this entity. In this example, using a function | +/// | **Relationships** | | +/// | `Children []` | Spawns each entry as a child of this entity, see **Scene Lists** below for details | +/// | `ChildOf(entity)` | Makes **this** entity a child of `entity`, accepts a [`Entity`] or a `#Name` reference ([`EntityTemplate`]) | +/// | `MyRel []` | Like `Children`, but uses any `RelationshipTarget` component | /// /// [`context`]: bevy_ecs::template::TemplateContext /// [`EntityTemplate`]: bevy_ecs::template::EntityTemplate @@ -910,31 +924,31 @@ use bevy_ecs::prelude::*; /// /// | Example | Meaning | /// | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | -/// | `#Child1 CompA, #Child2` | Spawns 2 children, one with `(Name("Child1"), CompA::default())` and the other with `Name("Child2")` | -/// | `(#Child1 CompA), (#Child2)` | Same as above, with explicit parentheses | -/// | `#First, { expr }, #Last ` | Spawns an entity with name `First`, then every entity from the `SceneList` returned by expr, then one named `Last` | -/// | `#First, ({ expr }), #Last ` | Same as above, but the `expr` should result in a `Scene` and will only spawn one entity using it | +/// | `[ #Child1 CompA, #Child2 ]` | Spawns 2 children, one with `(Name("Child1"), CompA::default())` and the other with `Name("Child2")` | +/// | `[ (#Child1 CompA), (#Child2) ]` | Same as above, with explicit parentheses | +/// | `[ #First, { expr }, #Last ]` | Spawns an entity with name `First`, then every entity from the `SceneList` returned by expr, then one named `Last` | +/// | `[ #First, ({ expr }), #Last ]` | Same as above, but the `expr` should result in a `Scene` and will only spawn one entity using it | /// /// ### Values /// /// Values in BSN (as in `val`,`val1` etc used above) are generally any literal Rust values, plus a few bsn-specific quirks. /// /// -/// | Example | Meaning | Explanation | +/// | Example | Meaning | Explanation | /// | -------------------------------- | ------------- | -/// | `1` | unsigned int | positive number, common types: `usize`, `u8`, `u32`, `u64` | -/// | `1` or `-1` | signed int | positive or negative number, common types: `i32`, `i64` | -/// | `1.1` or `-0.1` or `1.` or `-2.` | float | Floating point number, common types: `f32`, `f64` | -/// | `true` or `false` | bool | Boolean, type: `bool` | -/// | `"somename"` | string | Text, types: `String` or `&'static str` | -/// | `"mypicture.png"` | asset path | Asset, when used in a field which expects a `Handle` to the matching `Asset` type | -/// | `some(1)` | function call | , only works if the function returns the required type | -/// | `GREEN` | constant | fixed value, must be in scope | -/// | `std::f32::consts::PI` | constant | fixed value, uses full path so doesn't need to be in scope | -/// | **Expression syntax** | | | -/// | `{ 1 + 2 }` | expression | Any rust expression works in `{}`, in this case addition of 2 integers | -/// | `{ vec![true, false] }` | vector | An expression returning a vec, collection of multiple items, in the example `Vec` but can be any inner type | -/// | `{ bsn!{ Text("foo") Style } }` | scene | Sometimes, you may need to pass a small `Scene` as a value to something else | +/// | `1` | unsigned int | positive number, common types: [`usize`], [`u8`], [`u32`], [`u64`] | +/// | `1` or `-1` | signed int | positive or negative number, common types: `i32`, `i64` | +/// | `1.1` or `-0.1` or `1.` or `-2.` | float | Floating point number, common types: `f32`, `f64` | +/// | `true` or `false` | bool | Boolean, type: [`bool`] | +/// | `"somename"` | string | Text, types: `String` or `&'static str` | +/// | `"mypicture.png"` | asset path | Asset, when used in a field which expects a [`Handle`] to the matching `Asset` type | +/// | `some(1)` | function call | , only works if the function returns the required type | +/// | `GREEN` | constant | fixed value, must be in scope | +/// | `std::f32::consts::PI` | constant | fixed value, uses full path so doesn't need to be in scope | +/// | **Expression syntax** | | | +/// | `{ 1 + 2 }` | expression | Any rust expression works in `{}`, in this case addition of 2 integers | +/// | `{ vec![true, false] }` | vector | An expression returning a [`Vec`], a collection of multiple items of one specific type. | +/// | `{ bsn!{ Text("foo") Style } }` | scene | Sometimes, you may need to pass a small `Scene` as a value to something else | /// /// ### Other Rust syntax /// @@ -1097,7 +1111,7 @@ mod tests { fn b() -> impl Scene { bsn! { Position { x: 1., y: 1., z: 1. } - :a + a() } } @@ -1754,7 +1768,7 @@ mod tests { // inheritance is the same! let entity_b = bsn! { - :armor + armor() Health { current: 100, max: 100 } }; let idb = world.spawn_scene(entity_b).unwrap().id(); @@ -1765,98 +1779,6 @@ mod tests { ); } - #[test] - fn scene_inheritance_vs_composition() { - #[derive(Component, Default, Clone)] - struct Health { - current: u8, - max: u8, - } - #[derive(Component, Default, Clone)] - struct Armor(u8); - - fn unit_with_armor() -> impl Scene { - bsn! { - #Unit - Armor(50) - Children [ - #First - ] - } - } - fn armor() -> impl Scene { - bsn! { - Armor(10) - Children [ - #Second - ] - } - } - - let mut app = test_app(); - let world = app.world_mut(); - - // inheritance - let scene_a = bsn! { - :unit_with_armor - armor() - }; - - let entity_a = world.spawn_scene(scene_a).unwrap().id(); - let children = world.entity(entity_a).get::().unwrap(); - let assets = world.resource::>(); - for id in assets.ids() { - dbg!(id); - } - let names_a: Vec<_> = children - .iter() - .map(|id| world.entity(id).get::().unwrap().to_string()) - .collect(); - - // composition - let scene_b = bsn! { - unit_with_armor() - armor() - }; - let entity_b = world.spawn_scene(scene_b).unwrap().id(); - let children = world.entity(entity_b).get::().unwrap(); - let names_b: Vec<_> = children - .iter() - .map(|id| world.entity(id).get::().unwrap().to_string()) - .collect(); - - let scene_c = bsn! { - unit_with_armor() - Armor(10) - Children [ - #Second - ] - }; - let entity_c = world.spawn_scene(scene_c).unwrap().id(); - let children = world.entity(entity_c).get::().unwrap(); - let names_c: Vec<_> = children - .iter() - .map(|id| world.entity(id).get::().unwrap().to_string()) - .collect(); - - assert_eq!(names_a, &["First", "Second"]); - assert_eq!(names_a, names_b); - assert_eq!(names_a, names_c); - - assert_eq!( - world.entity(entity_a).archetype().components(), - world.entity(entity_b).archetype().components() - ); - - assert_eq!( - world.entity(entity_a).archetype().components(), - world.entity(entity_c).archetype().components() - ); - assert_eq!(world.entity(entity_a).get::().unwrap().0, 10); - assert_eq!(world.entity(entity_b).get::().unwrap().0, 10); - assert_eq!(world.entity(entity_c).get::().unwrap().0, 10); - } - #[test] fn enum_patching() { let mut app = test_app(); @@ -2759,35 +2681,35 @@ mod tests { // why not doctests? because the macro can't depend on this crate // why not include! it here and include_str! it in the docs? because rust-analyzer ignores #[doc = include_str!()] and this is mostly a showcase for rust-analyzer let scene = bsn! { - :some_scene // inherit from a scene function, its - #SomeName // entity name, will insert Name("SomeName") - ComponentA // component without a value will use default - ComponentB(0.0) // passing a value, other fields will use default + some_scene() // include a scene function + #SomeName // entity name, will insert Name("SomeName") + ComponentA // component without a value will use default + ComponentB(0.0) // passing a value, other fields will use default Node { height: px(0.1) // same with named fields, unmentioned ones stay default } - on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer - let mut b = query.get_mut(evt.entity).unwrap(); // unwrap since we're sure this entity always has ComponentA + on(|evt: On, mut query: Query<&mut ComponentB>| { // add an observer + let mut b = query.get_mut(evt.entity).unwrap(); b.0 += evt.value; }) - Children [ // spawning multiple related entities using a RelationshipTarget component - #Child1 ComponentA // whitespace doesn't have to be newlines - , // entities are comma-separated - (:other_scene #Child3), // parentheses around a single entity optional - Link(#SomeName), // passing a entity reference to a component as `Entity`, component has to implement FromTemplate - @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from - @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments - normal_field: 5 // while normal fields are the actual fields of the component + Children [ // spawning multiple related entities using a RelationshipTarget component + #Child1 ComponentA // whitespace doesn't have to be newlines + , // entities are comma-separated + (other_scene() #Child3), // parentheses around a single entity are optional + Link(#SomeName), // passing a entity reference to a component as `Entity`, component has to implement FromTemplate + @MySceneComponent { // components which derive SceneComponent have scenes and can be inherited from + @some_prop: 3, // props, look like fields prefixed with @ but end up passed to the components scene as arguments + normal_field: 5 // while normal fields are the actual fields of the component }, Node { - width: some_variable + width: some_var // you can directly use variables without {} } - ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} + ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} @Container { @items: { - bsn_list![ // sometimes you may need to nest macro calls + bsn_list![ // sometimes you may need to nest macro calls #item1 SomeComponent, // note: the name #item1 here is in its own scope - :some_scene #item2 + some_scene() #item2 ] } } From 462c533a2621bcec853478e306eeaa07b290b322 Mon Sep 17 00:00:00 2001 From: laund Date: Mon, 1 Jun 2026 19:56:58 +0200 Subject: [PATCH 08/14] a lot more docs improvements --- crates/bevy_scene/src/lib.rs | 154 +++++++++++++++++++++++++++++------ 1 file changed, 131 insertions(+), 23 deletions(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 37552c7734a06..3d59d6f7120e3 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -131,7 +131,6 @@ //! #Parent //! Children [ //! #Child1 -//! ComponentA //! ComponentB, //! #Child2 //! ComponentA @@ -139,7 +138,6 @@ //! #GrandChild1 //! ComponentA, //! #GrandChild2 -//! ComponentB //! ] //! ] //! } @@ -153,12 +151,10 @@ //! Children [ //! ( //! #Child1 -//! ComponentA //! ComponentB //! ), //! ( //! #Child2 -//! ComponentA //! Children [ //! ( //! #GrandChild1 @@ -166,7 +162,6 @@ //! ), //! ( //! #GrandChild2 -//! ComponentB //! ) //! ] //! ), @@ -191,7 +186,7 @@ //! an [`Entity`] field should likely derive [`FromTemplate`] instead of [`Default`]. //! //! ```ignore -//! bsn!{ +//! bsn! { //! needs_last_child_scene(#Last) //! Children [ //! #First, @@ -201,6 +196,29 @@ //! } //! ``` //! +//! ### Limitations +//! +//! Its not possible to use expressions or variables to assign entity names, yet this is sometimes desired and useful. +//! +//! You could of course insert the `Name(variable)` directly, but then you cant use entity references. +//! +//! This can be worked around by doing both: +//! ```ignore +//! let i = 0; +//! bsn! { +//! Name("My Root") +//! needs_last_child_scene(#Last) +//! Children [ +//! Name("First Entity"), +//! #Second, +//! #Last +//! Name(format!("foo{i}")) +//! ] +//! } +//! ``` +//! Adding `Name("desired name")` after the `#SomeName` reference will patch over the `Name` component created by the reference to give it a custom name. +//! Since the named entity references just need to be unique identifiers when the macro executes, this does not cause any issues. +//! //! ### Scope rules //! //! Each [`bsn!`] invocation creates its own name scope. A name is visible to the root @@ -233,7 +251,7 @@ //! } //! ``` //! -//! ## Composition and Patching +//! ## Patching //! //! When you insert a component into an [`Entity`] in normal ECS code, the entire pre-existing value is replaced. //! If a scene sets `Button { width: 100, height: 300 }` and a caller wants to @@ -242,7 +260,27 @@ //! **Patching** avoids this. When you write `Button { width: 200 }` in [`bsn!`], it creates //! a *patch* that sets only the `width` field. Unmentioned fields keep their existing values //! (from a included scene, an earlier patch, or the type's defaults). Multiple patches to the -//! same component and its values are applied in order rather than overwriting each other. +//! same component and its values are applied in order, only overwriting the fields they changed. +//! +//! The following scenes all end up with a button which is 200 wide and 300 high. +//! ```ignore +//! impl Default for Button { +//! fn default() -> Self { +//! Button { width: 100, height: 300 } +//! } +//! } +//! +//! bsn! { Button { width: 200, height: 300 } } // fully specified +//! bsn! { Button { width: 200 } } // only changing width, height defaults to 300 +//! +//! bsn! { +//! Button // inserts defaults +//! Button { width: 200 } // changes width +//! Button { height: 300 } // changes height +//! } +//! ``` +//! +//! ### Required Traits //! //! To make a component available in [`bsn!`], derive either [`Default`] + [`Clone`], or [`FromTemplate`]. //! Both support patching: unmentioned fields keep their values from earlier patches or the @@ -262,8 +300,21 @@ //! You still have access to a default constructor of sorts though: the derive generates a companion struct //! for `YourType` named `YourTypeTemplate` which implements `Default`, so `YourTypeTemplate::default()` serves the same purpose. //! -//! You compose scenes by writing functions that return `impl Scene` and calling them -//! inside [`bsn!`]: +//! #### Enums in bsn +//! +//! Enums are special cased due to their complexity regarding [`Default`] or similar traits: [`bsn!`] needs to have defaults for *all* variants accessible. +//! +//! For this reason, theres a custom "Derive" which isn't actually a Trait, called [`VariantDefaults`](bevy_ecs::VariantDefaults) +//! which creates a impl block with one static method for each variant, in the schema `default_{variant_lower}`. +//! +//! [`bsn!`] will use these when encountering a Enum instead of [`Default`]. Alternatively, [`FromTemplate`] also works. +//! +//! ## Composition +//! +//! Composition relies on patching to work nicely, allowing you to include other scenes in the current ones. +//! All of their patches will be applied at the position they're included. +//! +//! Example: //! //! ``` //! # use bevy_app::App; @@ -288,9 +339,9 @@ //! bsn! { Health { current: 100, max: 100 } } //! } //! -//! // Compose `enemy()` and patch just the `max` field: +//! // Include `enemy()` and patch just the `max` field: //! world.spawn_scene(bsn! { -//! :enemy +//! enemy() //! Health { max: 200 } //! }); //! ``` @@ -299,13 +350,56 @@ //! while `current` retains the value from `enemy()`. Tuples of [`Scene`]s also implement //! [`Scene`], so patches from multiple sources merge into a single [`ResolvedScene`]. //! -//! For programmatic patching outside of [`bsn!`], see the [`PatchFromTemplate`] and +//! For programmatic patching outside of [`bsn!`], see the [`PatchFromTemplate`] and //! [`PatchTemplate`] traits. //! +//! #### bsn expansion +//! +//! This is a rough schema of the code [`bsn!`] expands to, based on the example above. +//! Its pseudocode intended to help you build a mental model, not as a fully accurate representation. +//! +//! +//! ```ignore +//! fn enemy() -> impl Scene { +//! SceneScope(({ +//! let health = this_scene.get_or_default::(); +//! health.current = 100; +//! health.max = 100; +//! })) +//! } +//! +//! world.spawn_scene( +//! SceneScope(( +//! enemy(), // executed first, calls Health::default and sets the values +//! { +//! let health = this_scene.get_or_default::(); // this sees that Health was already inserted, and returns the existing version +//! health.max = 200; +//! } +//! )) +//! ); +//! ``` +//! +//! As you can see, its ends up as setting the individual values of the components. +//! That means order matters and by changing the order in which you list scene entries affects the outcome. +//! For example, if the order for the spawn was reversed, and `Health` came before `enemy()`, the `get_or_default` in `enemy()` +//! would return the existing Health and set both values, overwriting what came before. +//! +//! This overwriting works with multiple levels, bsn will happily generate multiple +//! levels of field access to overwrite whats in that specific field. +//! +//! //! ## Scene Caching //! -//! You may have noticed the `:enemy` syntax above. Thats a feature called "scene caching" which allows you -//! to cache a scene or scene component included into another scene if its the first one and if it does not take any arguments. +//!
+//! +//! Note: Caching is not yet fully implemented. All of the structure is there, but its not fully wired up yet. +//! +//!
+//! +//! Scenes can be cached, improving performance. Since this can change the semantics in some cases, its an explicit opt-in. +//! Caching works by resolving the included scene and storing the resulting [`ResolvedScene`] as an asset. When the outer scene is spawned again, +//! it will not need to resolve the included scene again, instead patching on top of a clone of the cached version. +//! This means caching can only be used if the scene is the first scene entry. //! //! No caching, calling without `:` //! ```ignore @@ -315,7 +409,7 @@ //! } //! ``` //! -//! Caching, no call, with `:` +//! Caching, no call, with `:` (This is what doesn't work currently) //! ```ignore //! bsn! { //! :enemy @@ -324,7 +418,7 @@ //! ``` //! //! BSN assets always need to be cached using the `:` prefix. -//! Note that the `.bsn` file format is not yet released. +//! Note that the `.bsn` file format is not yet released. (This should already work, assuming theres a loader for the asset format) //! ```ignore //! bsn! { //! :"enemy.bsn" @@ -332,8 +426,6 @@ //! } //! ``` //! -//! Note: Caching is not yet fully implemented. All of the structure is there, but its not fully wired up yet. -//! //! ## Loading Assets into Scenes //! //! Without the use of scenes, loading an asset requires referencing the [`AssetServer`] explicitly: @@ -443,7 +535,7 @@ //! A [`Template`] value, like a instance of a Component, cannot be directly returned from a curly bracketed `{ ... }` expression. //! For this, `template_value(...)` has to be used, which returns a [`Template`] version of the given value. //! -//! ```rs +//! ```ignore //! fn enemy(translation: Vec3){ //! let transform = Transform::from_translation(translation); //! bsn! { @@ -454,6 +546,22 @@ //! } //! ``` //! +//! ### Using ad-hoc template functions +//! +//! Sometimes you need custom behavior or world access to create a [`Template`]. +//! If this is the case, you can use [`template`](fn@template) instead of a custom [`FromTemplate`] or [`Template`] implementation. +//! In [`template`](fn@template) you get access to a [`TemplateContext`](bevy_ecs::template::TemplateContext) which +//! contains the [`EntityWorldMut`] and a collection of named entity references. +//! +//! ```ignore +//! bsn! { +//! #Foo +//! template(|ctx| { +//! Foo(ctx.resource::().get("generated_asset_name")) +//! }) +//! } +//! ``` +//! //! ### Expressions as scenes //! //! You can insert a [`Scene`] or [`SceneList`] in another Scene using curly-bracketed expressions: @@ -894,7 +1002,7 @@ use bevy_ecs::prelude::*; /// | `~MyType`
`~MyType {name: var}` | Type implementing [`Template`], the prefix is used to distinguish it from Components which use [`FromTemplate`]| /// | **Including Scenes** | | /// | `scene()`
`scene(val)` | Include the result of a `impl `[`Scene`] function | -/// | `{ expr }` | Composition with the result of `expr`, which should be a [`Scene`] | +/// | `{ expr }` | Include the result of `expr`, which should be a [`Scene`] | /// | `@MySceneComp` | Include a [`SceneComponent`]. Fields, if any exist, will be default | /// | `@MySceneComp { @prop: val }` | Include a [`SceneComponent`] with a `prop` field, passed to this components scene function | /// | `@MySceneComp { name: val }` | Include a [`SceneComponent`] with a normal field, works the same as it does for normal components | @@ -2659,7 +2767,7 @@ mod tests { some_prop: u8, } fn scenecomponentscene(props: Props) -> impl Scene {} - let some_variable: f32 = 0.; + let some_var: f32 = 0.; #[derive(SceneComponent, FromTemplate)] #[scene(scenecomponentscene2(Props2))] struct Container; @@ -2704,7 +2812,7 @@ mod tests { Node { width: some_var // you can directly use variables without {} } - ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} + ComponentB({some_var + 3.}) // values can be expressions, when wrapped in {} @Container { @items: { bsn_list![ // sometimes you may need to nest macro calls From a4c776846645e0cb8dfef8d9364bfb6fccdc3151 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 1 Jun 2026 17:13:13 -0700 Subject: [PATCH 09/14] Editorial pass 1 --- crates/bevy_scene/src/lib.rs | 263 +++++++++++++++-------------------- 1 file changed, 114 insertions(+), 149 deletions(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 3d59d6f7120e3..0363359f9a271 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -76,18 +76,18 @@ //! //! - **[`Scene`]**: Describes what a spawned [`Entity`] should look like, created using [`bsn!`] or, //! in the future, `.bsn` asset files. Conceptually, a [`Scene`] contains a list of "entries" to apply to an [`Entity`]. +//! - **[`SceneList`]**: A list of scenes, returned by [`bsn_list!`]. +//! Each [`Scene`] in the list produces one [`Entity`]. //! - **Scene Composition**: Composition works by including scenes in other scenes. The included scenes "entries" will be //! treated as if they were written in the outer scene. -//! - **[`SceneList`]**: A list of scenes, returned by [`bsn_list!`]. -//! Each of the scenes in this still produces one [`Entity`]. -//! - **[`Template`]**: Represents a kind of "change" in a [`Scene`]. -//! This is how [`Components`](Component) and a few other things are added to a [`Scene`]. -//! [`Templates`](Template) allow per-field overrides, [see below for details](#composition-and-patching) -//! When spawning, they get access to the [`Entity`] and the ECS [`World`], allowing for -//! some more complex behavior. [`Template`] is automatically implemented for types which have -//! [`Default`] + [`Clone`] (preferred) or [`FromTemplate`] (when fields implement [`FromTemplate`]). +//! - **[`Template`]**: A [`Template`] is something that, given a spawn context (target [`Entity`], [`World`], etc), can produce some output. Think of it +//! as a "superpowered ECS-aware constructor" for a type. In the context of scenes, [`Template`]s are used to produce [`Component`]s and [`Bundle`]s. This +//! enables defining scenes without needing to pass in a bunch of their dependencies (such as assets). The [`FromTemplate`] trait is used to associate some +//! final output type (ex: a [`Component`]) with a canonical [`Template`] that produces it. [`FromTemplate`] / [`Template`] is automatically implemented for +//! types that implement [`Default`] + [`Clone`], which is generally preferred. You should manually derive [`FromTemplate`] when a type needs custom template logic +//! (ex: one of its fields is an "asset handle", which has custom template logic). //! - **[`RelatedScenes`]**: These add a [`SceneList`] as related to this [`Scene`] by a specific [`relationship`](bevy_ecs::relationship::Relationship). -//! This is the kind of "change" added to the [`Scene`] when using a [`RelationshipTarget`] component like [`Children`]. +//! This kind of change is added to the [`Scene`] by specifying a [`RelationshipTarget`] component like [`Children`], followed by a [`SceneList`]. //! //! ## Spawning Scenes //! @@ -98,8 +98,9 @@ //! Returns an error if any asset dependencies are not yet loaded. //! - **Queued**: [`World::queue_spawn_scene`] and [`Commands::queue_spawn_scene`] //! register the scene's dependencies and wait for them to load before resolving and spawning. -//! If nothing needs to be loaded, this will spawn during this frames [`SpawnScene`] schedule, -//! between [`Update`](bevy_app::main_schedule::Update) and [`PostUpdate`](bevy_app::main_schedule::PostUpdate). +//! When the dependencies are loaded (or there are no dependencies), the scene will spawn during +//! that frame's [`SpawnScene`] schedule, between [`Update`](bevy_app::main_schedule::Update) and +//! [`PostUpdate`](bevy_app::main_schedule::PostUpdate). //! //! In all cases, your `*_spawn_scene` method call should wrap an invocation of the [`bsn!`] macro, //! or call a function which returns a [`Scene`]. @@ -114,14 +115,14 @@ //! add multiple components to the same entity by listing them without a comma: //! //! ```ignore -//! // Spawns one child entity with A, B an C -//! bsn! { #Parent Children [ComponentA ComponentB ComponentC] } +//! // Spawns one child entity with components A, B and C +//! bsn! { #Parent Children [A B C] } //! //! // Spawns two child entities, one with A and B, the other with C, due to the added comma -//! bsn! { #Parent Children [ComponentA ComponentB, ComponentC] } +//! bsn! { #Parent Children [A B, C] } //! //! // Spawns two child entities, but more clearly separated due to parentheses. -//! bsn! { #Parent Children [(ComponentA ComponentB), ComponentC] } +//! bsn! { #Parent Children [(A B), C] } //! ``` //! //! These invocations can be nested to build deeper hierarchies. @@ -130,13 +131,11 @@ //! bsn! { //! #Parent //! Children [ -//! #Child1 -//! ComponentB, +//! #Child1 SomeComponent, //! #Child2 -//! ComponentA +//! SomeComponent //! Children [ -//! #GrandChild1 -//! ComponentA, +//! #GrandChild1 SomeComponent, //! #GrandChild2 //! ] //! ] @@ -151,14 +150,14 @@ //! Children [ //! ( //! #Child1 -//! ComponentB +//! SomeComponent //! ), //! ( //! #Child2 //! Children [ //! ( //! #GrandChild1 -//! ComponentA +//! SomeComponent //! ), //! ( //! #GrandChild2 @@ -176,66 +175,53 @@ //! ## Named Entity References //! //! The `#Name` syntax assigns a [`Name`] to an entity and registers it for cross-referencing within the same macro invocation. -//! In a few others places in the same invocation, its possible to refer to a named entity by its `#Name`: -//! - `my_scene(#Name)` -//! - `Component(#Name)` -//! - `Component { entity: #Name }` -//! -//! All of these will initially receive a [`EntityTemplate`], which for [`Components`](Component) -//! will be resolved to an [`Entity`] using [`FromTemplate`]. This is why [`Components`](Component) which contain -//! an [`Entity`] field should likely derive [`FromTemplate`] instead of [`Default`]. +//! In a few others places in the same bsn! invocation / scope, its possible to refer to a named entity by its `#Name`: //! //! ```ignore //! bsn! { -//! needs_last_child_scene(#Last) +//! #Name +//! my_scene(#Name) +//! ComponentA(#Name) +//! ComponentB { entity: #Name } //! Children [ -//! #First, -//! #Second, -//! #Last +//! ComponentC(#Name) //! ] //! } //! ``` //! -//! ### Limitations -//! -//! Its not possible to use expressions or variables to assign entity names, yet this is sometimes desired and useful. +//! Notice that the "child entity" was able to access the parent entity via `#Name`. It is also possible for ancestors to access +//! their descendants: //! -//! You could of course insert the `Name(variable)` directly, but then you cant use entity references. -//! -//! This can be worked around by doing both: //! ```ignore -//! let i = 0; //! bsn! { -//! Name("My Root") -//! needs_last_child_scene(#Last) -//! Children [ -//! Name("First Entity"), -//! #Second, -//! #Last -//! Name(format!("foo{i}")) -//! ] +//! #Root +//! ComponentA(#Child1) +//! Children [ +//! #Child1, +//! #Child2, +//! ] //! } //! ``` -//! Adding `Name("desired name")` after the `#SomeName` reference will patch over the `Name` component created by the reference to give it a custom name. -//! Since the named entity references just need to be unique identifiers when the macro executes, this does not cause any issues. +//! +//! Using `#Name` as a value in [`bsn!`] will result in an [`EntityTemplate`], which is a [`Template`] that resolves to an [`Entity`] +//! [`Component`]s with [`Entity`] fields should generally derive [`FromTemplate`], because [`Entity`] uses [`FromTemplate`] to map to [`EntityTemplate`]. //! //! ### Scope rules //! //! Each [`bsn!`] invocation creates its own name scope. A name is visible to the root -//! entity, its children, and any deeper descendants in the same call. +//! entity, its children, and any deeper descendants in the same call. The reverse is also +//! true: descendants can "look up" the hierarchy. //! Composed scenes (via `my_scene(#Name)`) or [`SceneComponents`](SceneComponent) //! each contain their own [`bsn!`] invocation and therefore their own scope, //! so re-using the same name across multiple different scenes is fine. //! However, the results of a named entity reference, the [`EntityTemplate`], -//! can be passed to other scenes. Its valid only during the spawning of a scene. +//! can be passed to other scenes. It is valid only during the spawning of a scene. //! That means [`Components`](Component) should never store [`EntityTemplate`] fields, //! they should store the resolved [`Entity`] instead and //! derive [`FromTemplate`] to convert [`EntityTemplate`] automatically. //! //! If both a parent and a composed child define the same name (e.g. both use `#X`), //! each scope's `#X` resolves to its own entity, avoiding conflicts or potentially unintuitive shadowing. -//! The same way, individual executions of the [`bsn!`]-generated block will also resolve to different entities, -//! even during the same spawn. This allows, for example, loops containing [`bsn!`] to create different entities. //! //! In a [`bsn_list!`], all root entities share a single name scope, so sibling scenes //! can reference each other by name. This is useful for wiring up relationships between @@ -251,6 +237,23 @@ //! } //! ``` //! +//! ### Dynamic Name Values and Entity References +//! +//! `#SomeName` syntax will set `Name("SomeName")` in addition to making the entity reference-able in [`bsn!`]. `#Name` syntax is _always_ scoped and +//! doesn't support "dynamic" names. If you would like to _both_ reference an entity in [`bsn!`] _and_ provide a dynamic name, you can do this: +//! +//! ```ignore +//! let i = 0; +//! bsn! { +//! #Root +//! Name({format!("Entity {i}")}) +//! Children [ +//! Reference(#Root) +//! ] +//! } +//! ``` +//! Adding `Name("desired name")` after the `#SomeName` reference will patch over the `Name` component created by the reference to give it a custom name. +//! //! ## Patching //! //! When you insert a component into an [`Entity`] in normal ECS code, the entire pre-existing value is replaced. @@ -289,23 +292,22 @@ //! The distinction is about what values a field can hold at spawn time: //! //! - **[`Clone`] + [`Default`]** (e.g. `#[derive(Component, Default, Clone)]`): covers the simple case, and should be your default choice. -//! The blanket [`Template`] impl handles patching automatically with no scene-specific derives needed. -//! //! - **[`FromTemplate`]** (e.g. `#[derive(Component, FromTemplate)]`) is needed when a field requires spawn-time context. //! Examples include [`Handle`] fields which need [`AssetServer`] to resolve asset paths, or [`Entity`] //! fields which resolve [`EntityTemplate`]s from named entity references. If any of your fields' types -//! implement [`FromTemplate`], you must derive it for the parent type as well. +//! implement [`FromTemplate`] manually / have custom template logic, you should derive it for the parent type as well if you want your type +//! to use that logic. //! //! Deriving [`FromTemplate`] and [`Default`] on the same type is not allowed, as both would supply a [`FromTemplate`] impl and conflict. -//! You still have access to a default constructor of sorts though: the derive generates a companion struct +//! [`FromTemplate`] derivers still have access to a default constructor of sorts though: the derive generates a companion struct //! for `YourType` named `YourTypeTemplate` which implements `Default`, so `YourTypeTemplate::default()` serves the same purpose. //! //! #### Enums in bsn //! //! Enums are special cased due to their complexity regarding [`Default`] or similar traits: [`bsn!`] needs to have defaults for *all* variants accessible. //! -//! For this reason, theres a custom "Derive" which isn't actually a Trait, called [`VariantDefaults`](bevy_ecs::VariantDefaults) -//! which creates a impl block with one static method for each variant, in the schema `default_{variant_lower}`. +//! For this reason, there is a custom "derive" which isn't actually a Trait, called [`VariantDefaults`](bevy_ecs::VariantDefaults) +//! which creates an impl block with one static "default" method for each variant, in the schema `default_{variant_lower}`. //! //! [`bsn!`] will use these when encountering a Enum instead of [`Default`]. Alternatively, [`FromTemplate`] also works. //! @@ -353,55 +355,21 @@ //! For programmatic patching outside of [`bsn!`], see the [`PatchFromTemplate`] and //! [`PatchTemplate`] traits. //! -//! #### bsn expansion -//! -//! This is a rough schema of the code [`bsn!`] expands to, based on the example above. -//! Its pseudocode intended to help you build a mental model, not as a fully accurate representation. -//! -//! -//! ```ignore -//! fn enemy() -> impl Scene { -//! SceneScope(({ -//! let health = this_scene.get_or_default::(); -//! health.current = 100; -//! health.max = 100; -//! })) -//! } -//! -//! world.spawn_scene( -//! SceneScope(( -//! enemy(), // executed first, calls Health::default and sets the values -//! { -//! let health = this_scene.get_or_default::(); // this sees that Health was already inserted, and returns the existing version -//! health.max = 200; -//! } -//! )) -//! ); -//! ``` -//! -//! As you can see, its ends up as setting the individual values of the components. -//! That means order matters and by changing the order in which you list scene entries affects the outcome. -//! For example, if the order for the spawn was reversed, and `Health` came before `enemy()`, the `get_or_default` in `enemy()` -//! would return the existing Health and set both values, overwriting what came before. -//! -//! This overwriting works with multiple levels, bsn will happily generate multiple -//! levels of field access to overwrite whats in that specific field. -//! -//! //! ## Scene Caching //! //!
//! -//! Note: Caching is not yet fully implemented. All of the structure is there, but its not fully wired up yet. +//! Note: Caching is currently only implemented for scene assets. It hasn't yet been wired up for "function scenes" or [`SceneComponent`]s. Attempting to use +//! it in those cases will result in a compile error. //! //!
//! //! Scenes can be cached, improving performance. Since this can change the semantics in some cases, its an explicit opt-in. -//! Caching works by resolving the included scene and storing the resulting [`ResolvedScene`] as an asset. When the outer scene is spawned again, -//! it will not need to resolve the included scene again, instead patching on top of a clone of the cached version. +//! Caching works by resolving the included scene and storing the resulting [`ResolvedScene`] for future use. When the outer scene is spawned again, +//! it will not need to resolve the included scene again, instead patching on top of the cached version (using copy-on-write semantics for each [`Template`]). //! This means caching can only be used if the scene is the first scene entry. //! -//! No caching, calling without `:` +//! This scene includes an uncached "enemy" scene: //! ```ignore //! bsn! { //! enemy() @@ -409,7 +377,7 @@ //! } //! ``` //! -//! Caching, no call, with `:` (This is what doesn't work currently) +//! This scene caches the "enemy" scene by adding the `:` prefix (however caching scene functions like this is not currently supported) //! ```ignore //! bsn! { //! :enemy @@ -417,8 +385,8 @@ //! } //! ``` //! -//! BSN assets always need to be cached using the `:` prefix. -//! Note that the `.bsn` file format is not yet released. (This should already work, assuming theres a loader for the asset format) +//! Scene assets always need to be cached using the `:` prefix. +//! Note that the `.bsn` file format is not yet released. (This already works, assuming theres a loader for the asset format) //! ```ignore //! bsn! { //! :"enemy.bsn" @@ -435,8 +403,8 @@ //! commands.spawn(Sprite { image: handle, ..default() }); //! ``` //! -//! This was particularly frustrating when defining helper functions for spawning entities, -//! requiring you to pass [`AssetServer`] or handles through multiple layers of function calls. +//! This can be particularly frustrating when defining helper functions for spawning entities, +//! which require you to pass [`AssetServer`] or handles through multiple layers of function calls. //! //! In [`bsn!`], asset paths work directly as field values. When a component field is a //! [`Handle`], the [`bsn!`] macro accepts a string literal in its place. Under the hood, @@ -450,7 +418,7 @@ //! }); //! ``` //! -//! A [`Component`] has to also derive [`FromTemplate`] to accept asset paths: +//! A [`Component`] must also derive [`FromTemplate`] to accept asset paths: //! //! ```ignore //! #[derive(Component, FromTemplate)] @@ -472,20 +440,22 @@ //! //! ## Observers //! -//! Use [`on(func)`](on) inside [`bsn!`] to attach an entity [`Observer`]. Entity observers are closures or +//! Use [`on()`](on) inside [`bsn!`] to attach an entity [`Observer`]. Entity observers are closures or //! functions which fire when a given [`EntityEvent`] is triggered and targets this entity. //! The first parameter's type determines which event is observed. Multiple observers can be added to //! the same entity, and the observer has full access to the ECS via [system parameters](bevy_ecs::system#system-parameter-list): //! //! ```ignore //! #[derive(EntityEvent)] -//! struct Damage(u32); +//! struct Damage { +//! entity: Entity, +//! amount: u32, +//! } //! -//! fn is_dead(ev: On, query: Query<&Health>){ -//! let health = query.get(ev.event_target()).unwrap(); -//! if health.current == 0 { -//! info!("{} is dead", ev.event_target()); -//! } +//! #[derive(EntityEvent)] +//! struct Heal { +//! entity: Entity, +//! amount: u32, //! } //! //! fn player() -> impl Scene { @@ -493,12 +463,17 @@ //! Health { max: 100, current: 100 } //! // Each `on(...)` attaches a separate observer. //! on(|damage: On, mut query: Query<&mut Health>| { -//! let mut health = query.get_mut(damage.event_target()).unwrap(); -//! health.current = health.current.saturating_sub(damage.0); +//! let mut health = query.get_mut(damage.entity).unwrap(); +//! health.current = health.current.saturating_sub(damage.amount); //! }) -//! on(is_dead) +//! on(on_heal) //! } //! } +//! +//! fn on_heal(heal: On, query: Query<&Health>){ +//! let mut health = query.get_mut(heal.entity).unwrap(); +//! health.current = (health.current + heal.amount).min(health.max); +//! } //! ``` //! //! This is useful for self-contained logic like click handlers, damage reactions, @@ -514,12 +489,11 @@ //! //! ```ignore //! fn enemy(hp: u32, name: &str) -> impl Scene { -//! let sprite_path = name.to_string(); -//! +//! let name_string = name.to_string(); //! bsn! { //! #{name} //! Health { current: {hp / 2}, max: hp } -//! Sprite { image: {sprite_path + ".png"} } +//! Sprite { image: {name_string + ".png"} } //! } //! } //! @@ -530,10 +504,11 @@ //! Braces are required when the macro would otherwise misparse the expression //! and for complex expressions like `{hp * 2}`. //! -//! ### Using dynamic template values (component values) +//! ### Dynamic template values (component values) //! -//! A [`Template`] value, like a instance of a Component, cannot be directly returned from a curly bracketed `{ ... }` expression. -//! For this, `template_value(...)` has to be used, which returns a [`Template`] version of the given value. +//! A [`Template`] value, like a instance of a Component, cannot be directly passed in to a `bsn!` block, as `bsn!` +//! expects "scene variables" in that position. Instead use `template_value(...)` which accepts a given component [`Template`] value +//! and returns a [`Scene`] implementation for it. //! //! ```ignore //! fn enemy(translation: Vec3){ @@ -546,7 +521,7 @@ //! } //! ``` //! -//! ### Using ad-hoc template functions +//! ### Ad-hoc template functions //! //! Sometimes you need custom behavior or world access to create a [`Template`]. //! If this is the case, you can use [`template`](fn@template) instead of a custom [`FromTemplate`] or [`Template`] implementation. @@ -583,7 +558,7 @@ //! //! ### Conditional values //! -//! There is no `if`/`match` syntax inside the [`bsn!`] grammar, but you can embed +//! There is no `if`/`match` syntax inside the [`bsn!`] grammar (yet!), but you can embed //! conditionals via `{...}` blocks or handle them outside the macro: //! //! ```ignore @@ -613,6 +588,8 @@ //! } //! ``` //! +//! We plan on making "conditional scenes" easier to define in future releases. +//! //! ## Scene Components //! //! A [`SceneComponent`] is a specialized type of [`Component`] that has an associated [`Scene`]: @@ -620,7 +597,10 @@ //! ``` //! # use bevy_scene::prelude::*; //! # use bevy_ecs::prelude::*; -//! +//! # #[derive(Component, Default, Clone)] +//! # struct Sword; +//! # #[derive(Component, Default, Clone)] +//! # struct Shield; //! #[derive(SceneComponent, Default, Clone)] //! struct Player { //! score: usize @@ -631,8 +611,8 @@ //! bsn! { //! #Player //! Children [ -//! #Sword, -//! #Shield, +//! #RightHand Sword, +//! #LeftHand Shield, //! ] //! } //! } @@ -654,7 +634,6 @@ //! # let mut world = World::new(); //! world.spawn_scene(bsn! { //! @Player { score: 0 } -//! Camera3d //! }); //! ``` //! @@ -682,23 +661,10 @@ //! } //! ``` //! -//! This can sometimes be useful, for example if a scene function needs a generic type or constant: -//! -//! ``` -//! # use bevy_scene::prelude::*; -//! # use bevy_ecs::prelude::*; -//! #[derive(SceneComponent, Default, Clone)] -//! #[scene(player::<5>)] -//! struct Player; -//! -//! fn player() -> impl Scene { -//! bsn! { /* scene using ID here */} -//! } -//! ``` -//! //! ### `SceneComponent` Asset Paths //! -//! (Note: Currently, there is no `.bsn` asset format. This exists to help you understand whats planned.) +//! Note: Currently, there is no `.bsn` asset format. This exists to help you understand what is planned, and what is currenty possible +//! with third-party asset formats. //! //! Alternatively, a scene asset path can be specified: //! @@ -799,8 +765,9 @@ //! ``` //! //! Notice the `@field` syntax, which specifies that a prop is being set instead of a field. -//! Props are evaluated "immediately" when the scene is included in another. -//! This means that they are not "patchable" since they can be used for any purpose. +//! Props are evaluated "immediately" when the scene is included in another scene. +//! This means that they are not "patchable", as at that point they have already been evaluated, +//! and they _produce_ "patchable" outputs. //! //! You can set _both_ props and normal fields at the same time: //! ```no_run @@ -877,9 +844,6 @@ //! They are functionally quite different however. It is worth understanding the differences and //! tradeoffs: //! -// TODO: I (laund) don't like this section, and would like to consider removing/reworking it. -// it feels overly verbose and compares scene components and required components somewhat -// holistically, which feels out of place in these docs. //! - **Required Components**: Context-less (ex: Default constructors), non-hierarchical, can always //! be applied immediately, not dependency aware, automatically enforced at runtime as components //! are added, not patchable, pretty low overhead, not a lot of features / functionality @@ -898,6 +862,7 @@ //! - Is spawn performance a very high priority? Use required components. //! //! ## .bsn Asset Format +//! //! Bevy does not currently have support for `.bsn` files, //! but intends to offer a `.bsn` asset format in future releases. //! @@ -913,7 +878,7 @@ //! For now, you should use existing non-Bevy asset formats like glTF, //! search for ecosystem implementations or stick to `bsn!` macro calls. //! -//! The surrounding architecture in the macro and other systems to implement such an asset format already exists, +//! Note that the architecture to support an asset format already exists, //! allowing community implementations/experimentation until an official version exists. An example of how to go about this //! can be found in the [scene benchmarks]() //! From 87c3297ac28336abcbc0cee6990790d2f41d8f21 Mon Sep 17 00:00:00 2001 From: laund Date: Tue, 2 Jun 2026 02:49:14 +0200 Subject: [PATCH 10/14] Update crates/bevy_scene/macros/src/lib.rs Co-authored-by: Carter Anderson --- crates/bevy_scene/macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index d251f26eda952..8189fac75c2a5 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -30,7 +30,7 @@ use syn::{parse_macro_input, DeriveInput}; /// normal_field: 5 // while normal fields are the actual fields of the component /// }, /// Node { -/// width: some_var // you can directly use variables without {} +/// width: some_var // variables can be assigned to field values /// } /// ComponentB({some_variable + 3.}) // values can be expressions, when wrapped in {} /// @Container { From 5eed7a1e1889de2961dfc22fbb27ca2514ffb7cf Mon Sep 17 00:00:00 2001 From: laund Date: Tue, 2 Jun 2026 02:49:36 +0200 Subject: [PATCH 11/14] Update crates/bevy_scene/macros/src/lib.rs Co-authored-by: Carter Anderson --- crates/bevy_scene/macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index 8189fac75c2a5..af686023ca9b6 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -11,7 +11,7 @@ use syn::{parse_macro_input, DeriveInput}; /// bsn! { /// some_scene() // include a scene function /// #SomeName // entity name, will insert Name("SomeName") -/// ComponentA // component without a value will use default +/// ComponentA // component without fields: will use the default field values /// ComponentB(0.0) // passing a value, other fields will use default /// Node { /// height: px(0.1) // same with named fields, unmentioned ones stay default From 36aefdffe2d42862a159e7ab83821fa43a1332bc Mon Sep 17 00:00:00 2001 From: laund Date: Tue, 2 Jun 2026 02:49:49 +0200 Subject: [PATCH 12/14] Update crates/bevy_scene/macros/src/lib.rs Co-authored-by: Carter Anderson --- crates/bevy_scene/macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index af686023ca9b6..0266d81f94079 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -12,7 +12,7 @@ use syn::{parse_macro_input, DeriveInput}; /// some_scene() // include a scene function /// #SomeName // entity name, will insert Name("SomeName") /// ComponentA // component without fields: will use the default field values -/// ComponentB(0.0) // passing a value, other fields will use default +/// ComponentB(0.0) // when setting a field, unmentioned fields will use defaults /// Node { /// height: px(0.1) // same with named fields, unmentioned ones stay default /// } From 49cc434c14b09d881c51aedf034dc6298bce1449 Mon Sep 17 00:00:00 2001 From: laund Date: Tue, 2 Jun 2026 02:50:15 +0200 Subject: [PATCH 13/14] Update crates/bevy_scene/macros/src/lib.rs Co-authored-by: Carter Anderson --- crates/bevy_scene/macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index 0266d81f94079..d824fff8180e0 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -36,7 +36,7 @@ use syn::{parse_macro_input, DeriveInput}; /// @Container { /// @items: { /// bsn_list![ // sometimes you may need to nest macro calls -/// #item1 SomeComponent, // note: the name #item1 here is in its own scope +/// #Item1 SomeComponent, // note: the name #Item1 here is in its own scope /// some_scene() #item2 /// ] /// } From c3f642581d27fb3956e8f4238aa804aa0482873d Mon Sep 17 00:00:00 2001 From: laund Date: Tue, 2 Jun 2026 02:50:34 +0200 Subject: [PATCH 14/14] Update crates/bevy_scene/macros/src/lib.rs Co-authored-by: Carter Anderson --- crates/bevy_scene/macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_scene/macros/src/lib.rs b/crates/bevy_scene/macros/src/lib.rs index d824fff8180e0..88b31e4dedff1 100644 --- a/crates/bevy_scene/macros/src/lib.rs +++ b/crates/bevy_scene/macros/src/lib.rs @@ -37,7 +37,7 @@ use syn::{parse_macro_input, DeriveInput}; /// @items: { /// bsn_list![ // sometimes you may need to nest macro calls /// #Item1 SomeComponent, // note: the name #Item1 here is in its own scope -/// some_scene() #item2 +/// some_scene() #Item2 /// ] /// } /// }