Next Generation Scenes: Core scene system, bsn! macro, Templates#23413
Next Generation Scenes: Core scene system, bsn! macro, Templates#23413cart merged 48 commits intobevyengine:mainfrom
bsn! macro, Templates#23413Conversation
a660bc5 to
a664977
Compare
a664977 to
cfd3a96
Compare
Co-authored-by: andriyDev <andriydzikh@gmail.com>
|
It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. |
| /// desired template output type. This is used by Bevy's scene system. | ||
| pub trait GetTemplate: Sized { | ||
| /// The [`Template`] for this type. | ||
| type Template: Template; |
There was a problem hiding this comment.
How does the GetTemplate / Template traits fit with FromWorld?
My initial read of them as "ways to initialiaze objects that require World data" puts them in exactly the same niche.
Do you feel that we'll want both in the long-term? Can we better explain subtle distinctions using docs?
There was a problem hiding this comment.
Similar niches, but still quite different / they can and should exist next to each other:
FromWorld: createsTfrom a world referenceGetTemplate: createsTfrom an entity spawn context, which notably includes anEntityWorldMutreference and "scoped entity reference" information from the current scene spawn.
FromWorld fills niches that GetTemplate does not (initializing any type that is not an entity). FromWorld is also still the better pick for Resources. We could in theory try to replace FromWorld in that context, but it would require reworking some of the resource init code to make it efficient. And it would be a breaking change, which I think merits consideration + planning.
alice-i-cecile
left a comment
There was a problem hiding this comment.
Much improved from my initial review! There's been a lot of small cleanup during this review process.
Very happy that we have a workable solution for the Handle::default problem, even if it is a bit of a hack.
Approving: let's iterate more outside of the mega-PR please. Merge at your leisure.
Follow-up:
- Remember to rename
bevy_scene, and rename this crate. - The crate docs are very weak, and do not explain what this crate is actually for. There are a ton of concepts here, and we need to motivate how they fit together and why you need them all.
- I want to do a pass at the docs for
Sceneto see if I can come up with a cleaner introductory framing. - There's a missed unwrap that can be a ? in resolved_scene.rs.
- The excellent explanation contrasting
FromWorldandFromTemplatedid not make it into the docs. - Error message in
SpecializeFromTemplatediagnostic is truncated.
Co-authored-by: copygirl <copygirl@mcft.net>
…evyengine#23413) After much [iteration](bevyengine#20158), [designing](bevyengine#14437) and [collaborating](https://discord.com/channels/691052431525675048/1264881140007702558), it is finally time to land a baseline featureset of Bevy's Next Generation Scene system, often known by its new scene format name ... BSN (Bevy Scene Notation). This PR adds the following: - **The new scene system**: The core in-memory traits, asset types, and functionality for Bevy's new scene system. Spawn `Scene`s and `SceneList`s. Inherit from other scenes. Patch component fields. Depend on assets before loading as scene. Resolve Entity references throughout your scene. - **The `bsn!` and `bsn_list!` macro**s: Define Bevy scenes in your code using a new ergonomic Rust-ey syntax, which plays nicely with Rust Analyzer and supports autocomplete, go-to definition, semantic highlighting, and doc hover. - **`Template` / `GetTemplate`**: construct types (ex: Components) from a "template context", which includes access to the current entity _and_ access to the `World`. This is a foundational piece of the scene system. Note that this _does not_ include a loader for the BSN asset format, which will be added in a future PR. See the "Whats Next?" section for a roadmap of the future. Part of bevyengine#23030 ## Review Etiquette This is a big PR. _Please use threaded comments everywhere, not top level comments_. Even if what you have to say is not anchored in code, find a line to leave your comment on. ## Overview This is a reasonably comprehensive conceptual overview / feature list. This uses a "bottom up" approach to illustrate concepts, as they build on each other. If you just want to see what BSN looks like, scroll down a bit! ### Templates `Template` is a simple trait implemented for "template types", which when passed an entity/world context, can produce an output type such as a `Component` or `Bundle`: ```rust pub trait Template { type Output; fn build_template(&mut self, context: &mut TemplateContext) -> Result<Self::Output>; } ``` Template is the cornerstone of the new scene system. It allows us to define types (and hierarchies) that require no `World` context to define, but can _use_ the `World` to produce the final runtime state. Templates are notably: * **Repeatable**: Building a Template does not consume it. This allows us to reuse "baked" scenes / avoid rebuilding scenes each time we want to spawn one. If a Template produces a value this often means some form of cloning is required. * **Clone-able**: Templates can be duplicated via `Template::clone_template`, enabling scenes to be duplicated, supporting copy-on-write behaviors, etc. * **Serializable**: Templates are intended to be easily serialized and deserialized, as they are typically composed of raw data. The poster-child for templates is the asset `Handle<T>`. We now have a `HandleTemplate<T>`, which wraps an `AssetPath`. This can be used to load the requested asset and produce a strong `Handle` for it. ```rust impl<T: Asset> Template for HandleTemplate<T> { type Output = Handle<T>; fn build_template(&mut self, context: &mut TemplateContext) -> Result<Handle<T>> { Ok(context.resource::<AssetServer>().load(&self.path)) } } ``` Types that have a "canonical" `Template` can implement the `GetTemplate` trait, allowing us to correlate to something's `Template` in the type system. ```rust impl<T: Asset> GetTemplate for Handle<T> { type Template = HandleTemplate<T>; } ``` This is where things start to get interesting. `GetTemplate` can be derived for types whose fields also implement `GetTemplate`: ```rust #[derive(Component, GetTemplate)] struct Sprite { image: Handle<Image>, } ``` Internally this produces the following: ```rust #[derive(Template)] struct SpriteTemplate { image: HandleTemplate<Image>, } impl GetTemplate for Sprite { type Template = SpriteTemplate; } ``` Another common use case for templates is `Entity`. With templates we can resolve an identifier of an entity in a scene to the final `Entity` it points to (for example: an entity path or an "entity reference" ... this will be described in detail later). Both `Template` and `GetTemplate` are blanket-implemented for any type that implements both Clone and Default. This means that _most_ types are automatically usable as templates. Neat! ```rust impl<T: Clone + Default> Template for T { type Output = T; fn build_template(&mut self, context: &mut TemplateContext) -> Result<Self::Output> { Ok(self.clone()) } } impl<T: Clone + Default> GetTemplate for T { type Template = T; } ``` It is best to think of `GetTemplate` as an alternative to `Default` for types that require world/spawn context to instantiate. Note that because of the blanket impl, you _cannot_ implement `GetTemplate`, `Default`, and `Clone` together on the same type, as it would result in two conflicting GetTemplate impls. This is also why `Template` has its own `Template::clone_template` method (to avoid using the Clone impl, which would pull in the auto-impl). ### Scenes Templates on their own already check many of the boxes we need for a scene system, but they aren't enough on their own. We want to define scenes as _patches_ of Templates. This allows scenes to inherit from / write on top of other scenes without overwriting fields set in the inherited scene. We want to be able to "resolve" scenes to a final group of templates. This is where the `Scene` trait comes in: ```rust pub trait Scene: Send + Sync + 'static { fn resolve(&self, context: &mut ResolveContext, scene: &mut ResolvedScene) -> Result<(), ResolveSceneError>; fn register_dependencies(&self, _dependencies: &mut Vec<AssetPath<'static>>); } ``` The `ResolvedScene` is a collection of "final" `Template` instances which can be applied to an entity. `Scene::resolve` applies the `Scene` as a "patch" on top of the final `ResolvedScene`. It stores a flat list of templates to be applied to the top-level entity _and_ typed lists of related entities (ex: Children, Observers, etc), which each have their own ResolvedScene. `Scene`s are free to modify these lists, but in most cases they should probably just be pushing to the back of them. `ResolvedScene` can handle both repeated and unique instances of a template of a given type, depending on the context. `Scene::register_dependencies` allows the Scene to register whatever asset dependencies it needs to perform `Scene::resolve`. The scene system will ensure `Scene::resolve` is not called until all of the dependencies have loaded. `Scene` is always _one_ top level / root entity. For "lists of scenes" (such as a list of related entities), we have the `SceneList` trait, which can be used in any place where zero to many scenes are expected. These are separate traits for logical reasons: world.spawn() is a "single entity" action, scene inheritance only makes sense when both scenes are single roots, etc. ### Template Patches The `TemplatePatch` type implements `Scene`, and stores a function that mutates a template. Functionally, a `TemplatePatch` scene will initialize a `Default` value of the patched `Template` if it does not already exist in the `ResolvedScene`, then apply the patch on top of the current Template in the `ResolvedScene`. Types that implement `Template` can generate a `TemplatePatch` like this: ```rust #[derive(Template)] struct MyTemplate { value: usize, } MyTemplate::patch_template(|my_template, context| { my_template.value = 10; }); ``` Likewise, types that implement `GetTemplate` can generate a patch _for their template type_ like this: ```rust #[derive(GetTemplate)] struct Sprite { image: Handle<Image>, } Sprite::patch(|sprite_template| { // note that this is HandleTemplate<Image> sprite.image = "player.png".into(); }) ``` We can now start composing scenes by writing functions that return `impl Scene`! ```rust fn player() -> impl Scene { ( Sprite::patch(|sprite| { sprite.image = "player.png".into(); ), Transform::patch(|transform| { transform.translation.y = 4.0; }), ) } ``` ### The `on()` Observer / event handler Scene `on` is a function that returns a scene that creates an Observer template: ```rust fn player() -> impl Scene { ( Sprite::patch(|sprite| { sprite.image = "player.png".into(); ), on(|jump: On<Jump>| { info!("player jumped!"); }) ) } ``` ### The BSN Format `BSN` is a new specification for defining Bevy Scenes. It is designed to be as Rust-ey as possible, while also eliminating unnecessary syntax and context. The goal is to make defining arbitrary scenes and UIs as easy, delightful, and legible as possible. It is intended to be usable as both an asset format (ex: `level.bsn` files) _and_ defined in code via a `bsn!` macro. These are notably _compatible with each other_. You can define a BSN asset file (ex: in a visual scene editor, such as the upcoming Bevy Editor), then inherit from that and use it in `bsn!` defined in code. ```rust :"player.bsn" Player Sprite { image: "player.png" } Health(10) Transform { translation: Vec3 { y: 4.0 } } on(|jump: On<Jump>| { info!("player jumped!"); }) Children [ ( Hat Sprite { image: "cute_hat.png" } Transform { translation: Vec3 { y: 3.0 } } ) ), (:sword Transform { translation: Vec3 { x: 10. } } ] ``` Note that this PR includes the `bsn!` macro, but it does not include the BSN asset format. It _does_ include all of the in-memory / in-code support for the asset format. All that remains is defining a BSN asset loader, which will be done in a followup. ### The `bsn!` Macro `bsn!` is an _optional_ ergonomic syntax for defining `Scene` expressions. It was built in such a way that Rust Analyzer autocomplete, go-to definition, doc hover, and semantic token syntax highlighting works as expected pretty much everywhere (but there are _some_ gaps and idiosyncrasies at the moment, which I believe we can iron out). It looks like this: ```rust fn player() -> impl Scene { bsn! { Player Sprite { image: "player.png" } Health(10) Transform { translation: Vec3 { y: 4.0 } } on(|jump: On<Jump>| { info!("player jumped!"); }) Children [ ( Hat Sprite { image: "cute_hat.png" } Transform { translation: Vec3 { y: 3.0 } } ) ), (:sword Transform { translation: Vec3 { x: 10. } } ] } } fn sword() -> impl Scene { bsn! { Sword Sprite { image: "sword.png" } } } fn blue_player() -> impl Scene { bsn! { :player Team::Blue Children [ Sprite { image: "blue_shirt.png" } ] } } ``` I'll do a brief overview of each implemented `bsn!` feature now. ### `bsn!`: Patch Syntax When you see a normal "type expression", that resolves to a `TemplatePatch` as defined above. ```rust bsn! { Player { image: "player.png" } } ``` This resolve to the following: ```rust <Player as GetTemplatePatch>::patch(|template| { template.image = "player.png".into(); }) ``` This means you only need to define the fields you actually want to set! Notice the implicit `.into()`. Wherever possible, `bsn!` provides implicit `into()` behavior, which allows developers to skip defining wrapper types, such as the `HandleTemplate<Image>` expected in the example above. This also works for nested struct-style types: ```rust bsn! { Transform { translation: Vec3 { x: 1.0 } } } ``` Note that you can just define the type name if you don't care about setting specific field values / just want to add the component: ```rust bsn! { Transform } ``` To add multiple patches to the entity, just separate them with spaces or newlines: ```rust bsn! { Player Transform } ``` Enum patching is also supported: ```rust #[derive(Component, GetTemplate)] enum Emotion { Happy { amount: usize, quality: HappinessQuality }, Sad(usize), } bsn! { Emotion::Happy { amount: 10. } } ``` Notably, when you derive GetTemplate for an enum, you get default template values for _every_ variant: ```rust // We can skip fields for this variant because they have default values bsn! { Emotion::Happy } // We can also skip fields for this variant bsn! { Emotion::Sad } ``` This means that unlike the `Default` trait, enums that derive `GetTemplate` are "fully patchable". If a patched variant matches the current template variant, it will just write fields on top. If it corresponds to a different variant, it initializes that variant with default values and applies the patch on top. For practical reasons, enums only use this "fully patchable" approach when in "top-level scene entry patch position". _Nested_ enums (aka fields on patches) require specifying _every_ value. This is because the majority of types in the Rust and Bevy ecosystem will not derive `GetTemplate` and therefore will break if we try to create default variants values for them. I think this is the right constraint solve in terms of default behaviors, but we can discuss how to support both nested scenarios effectively. Constructors also work (note that constructor args are _not_ patched. you must specify every argument). A constructor patch will fully overwrite the current value of the Template. ```rust bsn! { Transform::from_xyz(1., 2., 3.) } ``` You can also use type-associated constants, which will also overwrite the current value of the template: ```rust bsn! { Transform::IDENTITY } ``` If you have a type that does not currently implement Template/GetTemplate, you have two options: ```rust bsn! { // This will return a Template that produces the returned type. // `context` has World access! template(|context| { Ok(TextFont { font: context .resource::<AssetServer>() .load("fonts/FiraSans-Bold.ttf").into(), ..default() }) }) // This will return the value as a Template template_value(Foo::Bar) } ``` ### `bsn!` Template patch syntax Types that are expressed using the syntax we learned above are expected to implement `GetTemplate`. If you want to patch a `Template` _directly_ by type name (ex: your Template is not paired with a GetTemplate type), you can do so using `@` syntax: ```rust struct MyTemplate { value: usize, } impl Template for MyTemplate { /* impl here */ } bsn! { @mytemplate { value: 10. } } ``` In most cases, BSN encourages you to work with the _final_ type names (ex: you type `Sprite`, not `SpriteTemplate`). However in cases where you really want to work with the template type directly (such as custom / manually defined templates), "Template patch syntax" lets you do that! ### `bsn!`: Inline function syntax You can call functions that return `Scene` impls inline. The `on()` function that adds an Observer (described above) is a particularly common use case ```rust bsn! { Player on(|jump: On<Jump>| { info!("Player jumped"); }) } ``` ### `bsn!`: Relationship Syntax `bsn!` provides native support for spawning related entities, in the format `RelationshipTarget [ SCENE_0, ..., SCENE_X ]`: ```rust bsn! { Node { width: Px(10.) } Children [ Node { width: Px(4.0) }, (Node { width: Px(4.0) } BackgroundColor(srgb(1.0, 0.0, 0.0)), ] } ``` Note that related entity scenes are comma separated. Currently they can either be flat _or_ use `()` to group them: ```rust bsn! { Children [ // Child 1 Node BorderRadius::MAX, // Child 2 (Node BorderRadius::MAX), ] } ``` It is generally considered best practice to wrap related entities with more than one entry in `()` to improve legibility. ### `bsn!`: Expression Syntax `bsn!` supports expressions in a number of locations using `{}`: ```rust let x: u32 = 1; let world = "world"; bsn! { // Field position expressions Health({ x + 2 }) Message { text: {format!("hello {world}")} } } ``` Expressions in field position have implicit `into()`. Expressions are also supported in "scene entry" position, enabling nesting `bsn!` inside `bsn!`: ```rust let position = bsn! { Transform { translation: Vec3 { x: 10. } } }; bsn! { Player {position} } ``` ### `bsn!`: Inline variables You can specify variables inline: ```rust let black = Color::BLACK; bsn! { BackgroundColor(black) } ``` This also works in "scene entry" position: ```rust let position = bsn! { Transform { translation: Vec3 { x: 10. } } }; bsn! { Player position } ``` ### Inheritance `bsn!` uses `:` to designate "inheritance". Unlike defining scenes inline (as mentioned above), this will _pre-resolve_ the inherited scene, making your current scene cheaper to spawn. This is great when you inherit from large scene (ex: an asset defined by a visual editor). Scenes can only inherit from one scene at a time, and it must be defined first. You can inherit from scene assets like this: ```rust fn red_button() -> impl Scene { bsn! { :"button.bsn" BackgroundColor(RED) } } ``` Note that while there is currently no implemented `.bsn` asset format, you can still test this using `AssetServer::load_with_path`. You can also inherit from functions that return a `Scene`: ```rust fn button() -> impl Scene { bsn! { Button Children [ Text("Button") ] } } fn red_button() -> impl Scene { bsn! { :button BackgroundColor(RED) } } ``` Note that because inheritance is cached / pre-resolved, function inheritance does not support function parameters. You can still use parameterized scene functions by defining them directly in the scene (rather than using inheritance): ```rust fn button(text: &str) -> impl Scene { bsn! { Button Children [ Text(text) ] } } fn red_button() -> impl Scene { bsn! { button("Click Me") BackgroundColor(RED) } } ``` Related entities can also inherit: ```rust bsn! { Node Children [ (:button BackgroundColor(RED)), (:button BackgroundColor(BLUE)), ] } ``` Inheritance concatenates related entities: ```rust fn a() -> impl Scene { bsn! { Children [ Name("1"), Name("2"), ] } } fn b() -> impl Scene { /// this results in Children [ Name("1"), Name("2"), Name("3") ] bsn! { :a Children [ Name("3"), ] } } ``` ### `bsn_list!` / SceneList Relationship expression syntax `{}` expects a SceneList. Many things, such as `Vec<S: Scene>` implement `SceneList` allowing for some cool patterns: ```rust fn inventory() -> impl Scene { let items = (0..10usize) .map(|i| bsn! {Item { size: {i} }}) .collect::<Vec<_>>(); bsn! { Inventory [ {items} ] } } ``` The `bsn_list!` macro allows defining a list of BSN entries (using the same syntax as relationships). This returns a type that implements `SceneList`, making it useable in relationship expressions! ```rust fn container() -> impl Scene { let children = bsn_list! [ Name("Child1"), Name("Child2"), (Name("Child3") FavoriteChild), ] bsn! { Container [ {children} ] } } ``` This, when combined with inheritance, means you can build abstractions like this: ```rust fn list_widget(children: impl SceneList) -> impl Scene { bsn! { Node { width: Val::Px(1.0) } Children [ Text("My List:") {children} ] } } fn ui() -> impl Scene { bsn! { Node Children [ list_widget({bsn_list! [ Node { width: Px(4.) }, Node { width: Px(5.) }, ]}) ] } } ``` ### `bsn!`: Name Syntax You can quickly define `Name` components using `#Name` shorthand. ```rust bsn! { #Root Node Children [ (#Child1, Node), (#Child2, Node), ] } ``` `#MyName` produces the `Name("MyName")` component output. Within a given `bsn!` or `bsn_list!` scope, `#Name` can _also_ be used in _value position_ as an `Entity` Template: ```rust #[derive(Component, GetTemplate)] struct UiRoot(Entity); #[derive(Component, GetTemplate)] struct CurrentButton(Entity); bsn! { #Root CurrentButton(#MyButton) Children [ ( #MyButton, UiRoot(#Root) ) ] } ``` These behave a bit like variable names. In the context of inheritance and embedded scenes, `#Name` is only valid within the current "scene scope": ```rust fn button() -> impl Scene { bsn! { #Button Node Children [ ButtonRef(#Button) ] } } fn red_button() -> impl Scene { bsn! { :button // #Button is not valid here, but #MyButton // will refer to the same final entity as #Button #MyButton Children [ AnotherReference(#MyButton) ] } } ``` In the example above, because `#MyButton` is defined "last" / is the most "specific" `Name`, the spawned entity will have `Name("MyButton")` Name references are allowed to conflict across inheritance scopes and they will not interfere with each other. `#Name` can also be used in the context of `bsn_list!`, which enables defining graph structures: ```rust bsn_list! [ (#Node1, Sibling(#Node2)), (#Node2, Sibling(#Node1)), ] ``` ### Name Restructure The core name component has also been restructured to play nicer with `bsn!`. The impl on `main` requires `Name::new("MyName")`. By making the name string field public and internalizing the prehash logic on that field, and utilizing implicit `.into()`, we can now define names like this: ```rust bsn! { Name("Root") Children [ Name("Child1"), Name("Child2"), ] } ``` ### BSN Spawning You can spawn scenes using `World::spawn_scene` and `Commands::spawn_scene`: ```rust world.spawn_scene(bsn! { Node Children [ (Node BackgroundColor(RED)) ] })?; commands.spawn_scene(widget()); ``` The `spawn_scene` operation happens _immediately_, and therefore assumes that all of the `Scene`'s dependencies have been loaded (or alternatively, that there are no dependencies). If the scene has a dependency that hasn't been loaded yet, `World::spawn_scene` will return an error (or log an error in the context of `Commands::spawn_scene`). If your scene has dependencies, you can use `World::queue_spawn_scene` and `Commands::queue_spawn_scene`. This will spawn the entity as soon as all of the `Scene`'s dependencies have been loaded. ```rust // This will spawn the entity once the "player.bsn" asset is loaded world.queue_spawn_scene(bsn! { :"player.bsn" Transform { position: Vec3 { x: 10. } } }); ``` There are also `spawn_scene_list` variants for everything above: ```rust world.spawn_scene_list(bsn_list! [ button("Ok"), button("Cancel"), ]) ``` `EntityWorldMut` and `EntityCommands` also have some new functionality: ```rust entity.queue_spawn_related_scene::<Children>(bsn_list! [ (:"player.bsn", #Player1), (:"player.bsn", #Player2), ]); ``` ```rust entity.apply_scene(bsn! { Transform { position: Vec3 { x: 10. } } })?; ``` For scene assets, you can also just add the `ScenePatchInstance(handle)` component, just like the old Bevy scene system. ### VariantDefaults derive `GetTemplate` automatically generates default values for enum Template variants. But for types that don't use `GetTemplate`, I've also implemented a `VariantDefaults` derive that also generates these methods. ## What's Next? ### Must happen before 0.19 - [ ] **Sort out `bevy_scene` vs `bevy_scene2`**: The current plan is to rename `bevy_scene` to `bevy_ecs_serialization`, and remove "scene" terminology from it. That then frees up `bevy_scene2` to be renamed to `bevy_scene`. The current `bevy_scene` will need to exist for awhile in parallel to BSN, as BSN is not yet ready for "full world serialization" scenarios. - [x] ~~**Resolve the Default Handle situation**: Currently, to provide Template support for `Handle`, it implements `GetTemplate`. This of course conflicts with `impl Default for Handle`. This is pretty disruptive to non-BSN users (which is currently everyone). We'll want to sort out a middleground solution in the short term that ideally allows us to keep `impl Default for Handle` during the transition.~~ - Resolved this by using a [specialization trick](bevyengine#23413 (comment)) - [ ] Nested `bsn!` `Scene` tuples to surpass tuple impl limits ### Ideally before 0.19 We likely won't land all of these. The plan is to (ideally) land this PR before Bevy 0.19 RC1, then _maybe_ land a couple more of these before - [ ] **Feathers BSN Port**: Largely already done. Just need to reconcile with current state of main. This will help BSN land well, so landing it alongside BSN is a high priority. - [ ] **ResolvedScene-as-dynamic-bundle**: ResolvedScene should insert all of the components at once as a single bundle, rather than one-by-one, which is really bad from an archetype move perspective. Without this, using `world.spawn_scene(scene)` as a `world.spawn(bundle)` replacement will result in a pretty significant performance reduction. - [ ] **`#Name` references in more places**: The UI eventing scenario _really_ wants `#Name` to be usable in closures. This would functionally be expressed as a template that returns a closure that accesses a specific entity. This unlocks a lot of value for UI devs, so ideally it lands alongside BSN. - [ ] **Top-down vs bottom-up spawn order**: Currently BSN follows the normal bevy top-down spawn order. I think we should heavily consider spawning bottom-up, in the interest of making scene contents available to "higher level" components in their lifecycle events (ex: a `Player` component accessing nested entities like "equipment" when inserted). If we decide to keep things as they are, we probably want to introduce additional "scene ready" entity events that trigger "bottom up". - [ ] **Inline field value expressions**: Support cases such as `px(10).all() - [ ] **Add EntityPath to EntityTemplate**: Support resolving entity paths (ex: `"Root/Child1/GrandChild1"`). This is relatively low hanging fruit, especially if we switch to bottom-up spawning order. - [ ] **Function Inheritance Caching**: Currently only scene asset inheritance is pre-computed / cached. For consistency / predictability / optimizations, function inheritance (ex `:button`) should also be cached. - [ ] **`derive(GetTemplate)` generics ergonomics**: Currently this requires casting spells: `T: GetTemplate<Template: Default + Template<Output = T>>` ### Near Future - [ ] **BSN Asset Format**: Add a `.bsn` parser / AssetLoader that can produce the current `ScenePatch` assets. - [ ] **Struct-style inheritance**: It would be nice to be able to do something like `:Button { prop } ` instead of `:button(prop)`. I'd really like us to explore this being component-tied (ex: associate a scene with a Button component). - [ ] **Descendant Patching**: It should be possible to "reach in" to an inherited scene and patch one of its descendants / children. - [ ] **Optimize Related Entity Spawning**: This currently inserts the relationship component first, then spawns the related scene. This results in an unnecessary archetype move. - [ ] Observers as relationships - [ ] **Scene-owned-entities**: Currently when spawning a `Scene`, every entity defined in the scene is instantiated. Some scenarios would benefit from Scene instances _sharing_ some unique entity. For example: defining assets _inside_ of scenes (this would pair nicely with Assets as Entities) , sharing Observer entities, etc. - [ ] The `touch_type::<Nested>()` approach could be replaced with `let x: &mut Nested` for actual type safety (and probably better autocomplete). - [ ] Fix Rust Analyzer autocomplete bug that fails to resolve functions and enums for `<Transform as GetTemplate>::Template::from_transform()` - [ ] Fix Rust Analyzer autocomplete bug that also suggests function names when type struct field names. This _should_ be fixed by using irrefutable `if let` statements. And it would probably allow us to reuse macro code across enums / structs (and avoid needing to use PathType inference in this case, which has gnarly corner cases). ### Longer Term - [ ] **`bsn!` hot patching via subsecond**: [Proof of concept here](cart#36) - [ ] **Reactivity**: This has been proven out [here](https://github.com/viridia/bevy_reactor/) - [ ] **BSN Sets**: See the [old design doc](bevyengine#14437) for the design space I'm talking about here * This would also allow expressing "flattened" forms of BSN, which makes diffs easier to read in some case - [ ] **World to BSN**: If we can support this, BSN can be used for things like saving Worlds to disk. This might also be useful for building scene editors. --------- Co-authored-by: andriyDev <andriydzikh@gmail.com> Co-authored-by: Nico Zweifel <34443492+NicoZweifel@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: copygirl <copygirl@mcft.net>
# Objective The existing crate docs merged in #23413 were minimalistic. This is not ideal, as there's a ton of complex, interconnected features that are important to explain. In particular, we want to explain how all of these types, traits and concepts relate to *each other* and the broad picture of both "how should you use this crate" and "why does this even exist". Crate docs are the ideal home for this. ## Solution Write 400 lines of crate docs. Easy. I am still relatively new to BSN: I haven't had a chance to use it in my projects yet either! Please keep an eye out for both technical and conceptual errors. Thankfully, the PR description from #23413 was extremely detailed, laying out the architectural context and broader patterns. The docs on each item, the tests, and to a lesser extent the old design discussions #9538 and #14437 were also very useful. ## Notable editorial choices - relatively high on hype for technical documentation - users need to understand *why* the complexity is useful, ideally without tripping over these problems themselves - we really want users to actually use bsn! (and .bsn): the experience is better and standardization is powerful - intro uses the dreaded "object" word: calling things with multiple entities an "entity" is flatly confusing - also very useful for translating the BSN model for users of other engines - drops users immediately into a Quick Start guide - some folks find it extremely useful to see a simple working example before getting the explanation - explicitly calls out the asset format as future work - this is a major, important feature. It's important to acknowledge its absence, and offer workarounds for now. - having some information about how it's likely to work allows people to plan effectively. - I spent a *lot* of time trying to carefully explain the differences between the composition strategies - this mentions asset-format BSN, because it's a primary driver of *why* we need inheritance - explaining the FromTemplate / Default distinction was hard but important - gave an explicit bolded warning about the lack of field overrides - I did not call out horrible specialization hack: far too distracting in the crate docs ## Important omissions and divergences from past discussions - `GetTemplate` was renamed to `FromTemplate` - `Construct` is now `Template` and `FromTemplate` - reactivity (not yet implemented) - scene composition as a styling/composition alternative (belongs in the bevy_feathers crate docs later)
# Objective Now that BSN is on main, we can port Feathers over! Related: #23030 #23413 ## Solution Port Feathers, leaving the bundle functions around (renamed to `x_bundle` and deprecated). This is largely 1:1, other than removing HandleOrPath (which is filling the same role as HandleTemplate). I've also ported the `feathers` and `virtual_keyboard` examples to BSN. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
After much iteration, designing and collaborating, it is finally time to land a baseline featureset of Bevy's Next Generation Scene system, often known by its new scene format name ... BSN (Bevy Scene Notation).
This PR adds the following:
Scenes andSceneLists. Inherit from other scenes. Patch component fields. Depend on assets before loading as scene. Resolve Entity references throughout your scene.bsn!andbsn_list!macros: Define Bevy scenes in your code using a new ergonomic Rust-ey syntax, which plays nicely with Rust Analyzer and supports autocomplete, go-to definition, semantic highlighting, and doc hover.Template/GetTemplate: construct types (ex: Components) from a "template context", which includes access to the current entity and access to theWorld. This is a foundational piece of the scene system.Note that this does not include a loader for the BSN asset format, which will be added in a future PR. See the "Whats Next?" section for a roadmap of the future.
Part of #23030
Review Etiquette
This is a big PR. Please use threaded comments everywhere, not top level comments. Even if what you have to say is not anchored in code, find a line to leave your comment on.
Overview
This is a reasonably comprehensive conceptual overview / feature list. This uses a "bottom up" approach to illustrate concepts, as they build on each other. If you just want to see what BSN looks like, scroll down a bit!
Templates
Templateis a simple trait implemented for "template types", which when passed an entity/world context, can produce an output type such as aComponentorBundle:Template is the cornerstone of the new scene system. It allows us to define types (and hierarchies) that require no
Worldcontext to define, but can use theWorldto produce the final runtime state. Templates are notably:Template::clone_template, enabling scenes to be duplicated, supporting copy-on-write behaviors, etc.The poster-child for templates is the asset
Handle<T>. We now have aHandleTemplate<T>, which wraps anAssetPath. This can be used to load the requested asset and produce a strongHandlefor it.Types that have a "canonical"
Templatecan implement theGetTemplatetrait, allowing us to correlate to something'sTemplatein the type system.This is where things start to get interesting.
GetTemplatecan be derived for types whose fields also implementGetTemplate:Internally this produces the following:
Another common use case for templates is
Entity. With templates we can resolve an identifier of an entity in a scene to the finalEntityit points to (for example: an entity path or an "entity reference" ... this will be described in detail later).Both
TemplateandGetTemplateare blanket-implemented for any type that implements both Clone and Default. This means that most types are automatically usable as templates. Neat!It is best to think of
GetTemplateas an alternative toDefaultfor types that require world/spawn context to instantiate. Note that because of the blanket impl, you cannot implementGetTemplate,Default, andClonetogether on the same type, as it would result in two conflicting GetTemplate impls. This is also whyTemplatehas its ownTemplate::clone_templatemethod (to avoid using the Clone impl, which would pull in the auto-impl).Scenes
Templates on their own already check many of the boxes we need for a scene system, but they aren't enough on their own. We want to define scenes as patches of Templates. This allows scenes to inherit from / write on top of other scenes without overwriting fields set in the inherited scene. We want to be able to "resolve" scenes to a final group of templates.
This is where the
Scenetrait comes in:The
ResolvedSceneis a collection of "final"Templateinstances which can be applied to an entity.Scene::resolveapplies theSceneas a "patch" on top of the finalResolvedScene. It stores a flat list of templates to be applied to the top-level entity and typed lists of related entities (ex: Children, Observers, etc), which each have their own ResolvedScene.Scenes are free to modify these lists, but in most cases they should probably just be pushing to the back of them.ResolvedScenecan handle both repeated and unique instances of a template of a given type, depending on the context.Scene::register_dependenciesallows the Scene to register whatever asset dependencies it needs to performScene::resolve. The scene system will ensureScene::resolveis not called until all of the dependencies have loaded.Sceneis always one top level / root entity. For "lists of scenes" (such as a list of related entities), we have theSceneListtrait, which can be used in any place where zero to many scenes are expected. These are separate traits for logical reasons: world.spawn() is a "single entity" action, scene inheritance only makes sense when both scenes are single roots, etc.Template Patches
The
TemplatePatchtype implementsScene, and stores a function that mutates a template. Functionally, aTemplatePatchscene will initialize aDefaultvalue of the patchedTemplateif it does not already exist in theResolvedScene, then apply the patch on top of the current Template in theResolvedScene. Types that implementTemplatecan generate aTemplatePatchlike this:Likewise, types that implement
GetTemplatecan generate a patch for their template type like this:We can now start composing scenes by writing functions that return
impl Scene!The
on()Observer / event handler Sceneonis a function that returns a scene that creates an Observer template:The BSN Format
BSNis a new specification for defining Bevy Scenes. It is designed to be as Rust-ey as possible, while also eliminating unnecessary syntax and context. The goal is to make defining arbitrary scenes and UIs as easy, delightful, and legible as possible.It is intended to be usable as both an asset format (ex:
level.bsnfiles) and defined in code via absn!macro. These are notably compatible with each other. You can define a BSN asset file (ex: in a visual scene editor, such as the upcoming Bevy Editor), then inherit from that and use it inbsn!defined in code.Note that this PR includes the
bsn!macro, but it does not include the BSN asset format. It does include all of the in-memory / in-code support for the asset format. All that remains is defining a BSN asset loader, which will be done in a followup.The
bsn!Macrobsn!is an optional ergonomic syntax for definingSceneexpressions. It was built in such a way that Rust Analyzer autocomplete, go-to definition, doc hover, and semantic token syntax highlighting works as expected pretty much everywhere (but there are some gaps and idiosyncrasies at the moment, which I believe we can iron out).It looks like this:
I'll do a brief overview of each implemented
bsn!feature now.bsn!: Patch SyntaxWhen you see a normal "type expression", that resolves to a
TemplatePatchas defined above.This resolve to the following:
This means you only need to define the fields you actually want to set!
Notice the implicit
.into(). Wherever possible,bsn!provides implicitinto()behavior, which allows developers to skip defining wrapper types, such as theHandleTemplate<Image>expected in the example above.This also works for nested struct-style types:
Note that you can just define the type name if you don't care about setting specific field values / just want to add the component:
To add multiple patches to the entity, just separate them with spaces or newlines:
Enum patching is also supported:
Notably, when you derive GetTemplate for an enum, you get default template values for every variant:
This means that unlike the
Defaulttrait, enums that deriveGetTemplateare "fully patchable". If a patched variant matches the current template variant, it will just write fields on top. If it corresponds to a different variant, it initializes that variant with default values and applies the patch on top.For practical reasons, enums only use this "fully patchable" approach when in "top-level scene entry patch position". Nested enums (aka fields on patches) require specifying every value. This is because the majority of types in the Rust and Bevy ecosystem will not derive
GetTemplateand therefore will break if we try to create default variants values for them. I think this is the right constraint solve in terms of default behaviors, but we can discuss how to support both nested scenarios effectively.Constructors also work (note that constructor args are not patched. you must specify every argument). A constructor patch will fully overwrite the current value of the Template.
You can also use type-associated constants, which will also overwrite the current value of the template:
If you have a type that does not currently implement Template/GetTemplate, you have two options:
bsn!Template patch syntaxTypes that are expressed using the syntax we learned above are expected to implement
GetTemplate. If you want to patch aTemplatedirectly by type name (ex: your Template is not paired with a GetTemplate type), you can do so using@syntax:In most cases, BSN encourages you to work with the final type names (ex: you type
Sprite, notSpriteTemplate).However in cases where you really want to work with the template type directly (such as custom / manually defined templates), "Template patch syntax" lets you do that!
bsn!: Inline function syntaxYou can call functions that return
Sceneimpls inline. Theon()function that adds an Observer (described above) is a particularly common use casebsn!: Relationship Syntaxbsn!provides native support for spawning related entities, in the formatRelationshipTarget [ SCENE_0, ..., SCENE_X ]:Note that related entity scenes are comma separated. Currently they can either be flat or use
()to group them:It is generally considered best practice to wrap related entities with more than one entry in
()to improve legibility.bsn!: Expression Syntaxbsn!supports expressions in a number of locations using{}:Expressions in field position have implicit
into().Expressions are also supported in "scene entry" position, enabling nesting
bsn!insidebsn!:bsn!: Inline variablesYou can specify variables inline:
This also works in "scene entry" position:
Inheritance
bsn!uses:to designate "inheritance". Unlike defining scenes inline (as mentioned above), this will pre-resolve the inherited scene, making your current scene cheaper to spawn. This is great when you inherit from large scene (ex: an asset defined by a visual editor). Scenes can only inherit from one scene at a time, and it must be defined first.You can inherit from scene assets like this:
Note that while there is currently no implemented
.bsnasset format, you can still test this usingAssetServer::load_with_path.You can also inherit from functions that return a
Scene:Note that because inheritance is cached / pre-resolved, function inheritance does not support function parameters. You can still use parameterized scene functions by defining them directly in the scene (rather than using inheritance):
Related entities can also inherit:
Inheritance concatenates related entities:
bsn_list!/ SceneListRelationship expression syntax
{}expects a SceneList. Many things, such asVec<S: Scene>implementSceneListallowing for some cool patterns:The
bsn_list!macro allows defining a list of BSN entries (using the same syntax as relationships). This returns a type that implementsSceneList, making it useable in relationship expressions!This, when combined with inheritance, means you can build abstractions like this:
bsn!: Name SyntaxYou can quickly define
Namecomponents using#Nameshorthand.#MyNameproduces theName("MyName")component output.Within a given
bsn!orbsn_list!scope,#Namecan also be used in value position as anEntityTemplate:These behave a bit like variable names. In the context of inheritance and embedded scenes,
#Nameis only valid within the current "scene scope":In the example above, because
#MyButtonis defined "last" / is the most "specific"Name, the spawned entity will haveName("MyButton")Name references are allowed to conflict across inheritance scopes and they will not interfere with each other.
#Namecan also be used in the context ofbsn_list!, which enables defining graph structures:Name Restructure
The core name component has also been restructured to play nicer with
bsn!. The impl onmainrequiresName::new("MyName"). By making the name string field public and internalizing the prehash logic on that field, and utilizing implicit.into(), we can now define names like this:BSN Spawning
You can spawn scenes using
World::spawn_sceneandCommands::spawn_scene:The
spawn_sceneoperation happens immediately, and therefore assumes that all of theScene's dependencies have been loaded (or alternatively, that there are no dependencies). If the scene has a dependency that hasn't been loaded yet,World::spawn_scenewill return an error (or log an error in the context ofCommands::spawn_scene).If your scene has dependencies, you can use
World::queue_spawn_sceneandCommands::queue_spawn_scene. This will spawn the entity as soon as all of theScene's dependencies have been loaded.There are also
spawn_scene_listvariants for everything above:EntityWorldMutandEntityCommandsalso have some new functionality:For scene assets, you can also just add the
ScenePatchInstance(handle)component, just like the old Bevy scene system.VariantDefaults derive
GetTemplateautomatically generates default values for enum Template variants. But for types that don't useGetTemplate, I've also implemented aVariantDefaultsderive that also generates these methods.What's Next?
Must happen before 0.19
bevy_scenevsbevy_scene2: The current plan is to renamebevy_scenetobevy_ecs_serialization, and remove "scene" terminology from it. That then frees upbevy_scene2to be renamed tobevy_scene. The currentbevy_scenewill need to exist for awhile in parallel to BSN, as BSN is not yet ready for "full world serialization" scenarios.Resolve the Default Handle situation: Currently, to provide Template support forHandle, it implementsGetTemplate. This of course conflicts withimpl Default for Handle. This is pretty disruptive to non-BSN users (which is currently everyone). We'll want to sort out a middleground solution in the short term that ideally allows us to keepimpl Default for Handleduring the transition.bsn!Scenetuples to surpass tuple impl limitsIdeally before 0.19
We likely won't land all of these. The plan is to (ideally) land this PR before Bevy 0.19 RC1, then maybe land a couple more of these before
world.spawn_scene(scene)as aworld.spawn(bundle)replacement will result in a pretty significant performance reduction.#Namereferences in more places: The UI eventing scenario really wants#Nameto be usable in closures. This would functionally be expressed as a template that returns a closure that accesses a specific entity. This unlocks a lot of value for UI devs, so ideally it lands alongside BSN.Playercomponent accessing nested entities like "equipment" when inserted). If we decide to keep things as they are, we probably want to introduce additional "scene ready" entity events that trigger "bottom up"."Root/Child1/GrandChild1"). This is relatively low hanging fruit, especially if we switch to bottom-up spawning order.:button) should also be cached.derive(GetTemplate)generics ergonomics: Currently this requires casting spells:T: GetTemplate<Template: Default + Template<Output = T>>Near Future
.bsnparser / AssetLoader that can produce the currentScenePatchassets.:Button { prop }instead of:button(prop). I'd really like us to explore this being component-tied (ex: associate a scene with a Button component).Scene, every entity defined in the scene is instantiated. Some scenarios would benefit from Scene instances sharing some unique entity. For example: defining assets inside of scenes (this would pair nicely with Assets as Entities) , sharing Observer entities, etc.touch_type::<Nested>()approach could be replaced withlet x: &mut Nestedfor actual type safety (and probably better autocomplete).<Transform as GetTemplate>::Template::from_transform()if letstatements. And it would probably allow us to reuse macro code across enums / structs (and avoid needing to use PathType inference in this case, which has gnarly corner cases).Longer Term
bsn!hot patching via subsecond: Proof of concept here