#Next Generation Scenes
1 messages Β· Page 10 of 1
I'm thinking that it would be possible to do something like:
SceneListIf::new(condition, bsn_list!(...))
Where SceneListIf implements the SceneList trait.
This would check the condition and either forward the methods to the inner list, or ignore them
I briefly explored this space here: https://github.com/bevyengine/bevy/pull/23536#discussion_r3020038308
We came to essentially the same conclusions
For now, I got this to work:
Children [
{props.label},
{
if props.arrow {
Box::new(bsn_list!(
Node {
flex_grow: 1.0,
},
:icon(icons::CHEVRON_DOWN),
)) as Box<dyn SceneList>
} else {
Box::new(bsn_list!()) as Box<dyn SceneList>
}
}
]
But yeah, an if_scene and if_scene_list would be useful.
Is there any way we can make bsn! Do the boxing automatically? (As an outside observer just dropping into this conversation)
Its hard for me to justify spending time on this now when time before 0.19 is limited and I could be investing in BSN landing better there. I can try to do a quick pass to assess the general strategy, but this is the type of thing that normally takes me time to sort out whether its "on the right track" or not
No rush at all!
Note the downside of my workaround is that you pay the cost of a box even in the empty else branch
tyty
I'm swamped as usual with plenty of other things too. I just wanted to get your feedback early before I do a bunch of work, that's all
Another review on this would be nice π
https://github.com/bevyengine/bevy/pull/23699
I don't suppose I could convince you to add:
"editor.rulers": [100],
to your vscode settings π
I'm largely a Zed guy now. I will investigate the implications of this on my workflow
Enabled the equivalent setting. Probably better than just vibing out whatever feels like "too long" π
We should talk about zed in another channel, I definitely am z-curious.
Feel free to ping me in #off-topic with questions π
Support TextFont / FontSource template (see bsn.rs example)
We should probably change the hex input field for the feathers color picker example to use FiraMono
Yeah that makes sense to me. Having text input on main is so wonderful β€οΈ
In any case, it sounds like if_scene / if_scene_list wouldn't be hard to implement - where would it live?
Probably bevy_scene
Changes requested :p
Resolved
And merging π
Currently investigating treating ResolvedScene like a dynamic bundle to avoid archetype moves
Can I ask what the plan is for the gltf asset loader? Is it going to migrate to loading a bsn Scene, and is that planned for 0.19, or some future cycle?
Both Sprite and ImageNode have an OptionTemplate with an Option property, but I don't know how to use it in bsn!, and I haven't found any examples that include it. Does anyone know how?
It'll load a BSN scene, but that needs the asset BSN infrastructure, and that's planned for 0.20
Speaking of asset BSN, I did a bunch of work on the prerequisite patch to add ReflectConvert
I don't think it needs any asset bsn stuff? The gltf loader would just construct a scene instead of a world asset
There's no real reason we can't do this right now (afaik), but I don't think we should since it's the end of the cycle and bsn is still fresh - we should wait for it to stabilize before putting it in the "required" path for users
Has making bevy_feathers the default style for bevy_ui_widgets been considered?
The last time I spoke with @rare whale, I was told that Feathers is opinionated, to force consistent style for tooling.
It also wasn't possible back then without BSN, which allows patching hierarchies.
But maybe it's more reasonable now? I feel that the lack of any styling hurts the user experience. Every time user spawns a button, they need to style it just to make it visible.
In game development, it's very uncommon to design UI from the start. Usually, you prototype first and make it look good later.
It's possible to just use Feathers for it at the prototype phase, but that would require migrating to bevy_ui_widgets on later phases. And their API quite different from what I saw.
I was told that the plan is to develop separate styling for user widgets, but why not reuse the existing style? UI is one of the weakest points of Bevy, and I think developing separate styling will delay fixing one of its biggest inconveniences π’
Yeah I don't think the loader needs to (or should) use asset BSN infra, when it could skip the slow / dynamic path in favor of the in-memory path
The gltf file is already filling the role of the "slow / dynamic" path
I do see the appeal here / I've thought about this a number of times. From my perspective the big "problem" with treating feathers as the default widget "theme", intended to be used by arbitrary apps and games, is that it isn't themable. Its design benefits substantially from the assumption that people won't need to reach in and arbitrarily style everything.
If we present Feathers as "intended for abitrary use cases", it will immediately create design pressure to make every single aspect of it style-able and customizable in arbitrary ways, on a per-widget-instance basis. I don't think our current approach to styling is flexible enough for that (global Key->Value pairs, with top level root-of-widget-only BSN patching). With the current system, it would require significantly more parameterization of internal styles, exposed at the root.
I think this is largely an issue of features. Ultimately, we will have a more capable theming system. And BSN will be able to patch at arbitrary levels of the hierarchy
By having a separate set of "themed widgets designed for arbitrary use cases", we also buy the ability to be incremental. Less "nested" widgets like buttons could probably exist today without major limitations on styling, as style happens at the root
From a "build the editor with hundreds of volunteers" perspective, the fact the Feathers is not very themeable is a feature. I don't want to end up like Steam
Additionally, if the "editor theme" is the default theme for every widget, then everything will look like the bevy editor. I'm not sure thats a good thing for perceived quality
Ex: design fatique, many games (especially things like jam games) won't change the theme, spotting a "bevy game" gets easier, but only in the context of people too lazy to create their own style
I think the default widget theme should have almost no character. And perhaps be square / wireframey / canvas-ey by default
Or rather, I think we should seriously consider this. I haven't made up my mind on it
With the goal being that people don't need to "undo" design choices, but rather layer their choices on top
easy solution, make it look like a default unity game
I am fine with a very raw, editor-like looking default one can modify. The more it looks like Windows Forms, the less likely people will use it unchanged, even in jams
Iβm not sure this is necessarily a bad thing though, one of the good(?) things of dear imgui for example is that itβs instantly recognisable - just take a look at Tears of the Kingdom. You immediately know itβs imgui here because of its default theme
Is that a good thing though? Or just a neat thing that people in the know will recognize?
Fair point, i think there is a genuine βughβ feeling when people see the default theme around egui, imgui being used from a game dev standpoint
To be clear, I do appreciate when I recognize imgui or egui when used in devtools and I don't even think it's ugly but it feels weird when it's used for software intended for non devs.
Godot and Unreal both have default style that is immediately recognizable π€
In both engines editors themed a bit differently, but they use the same subsystem.
Seems like it's a pretty hard goal to design things that are plug-and-play for people who want to iterate on an idea quickly AND avoid developing some kind of visual identity. Can you have a "no-theme" theme? I think people will start to pick bevy games out of a lineup even if you make plain looking defaults.
I guess I was thinking it would make a BSN AST so it would be editable
Even with insanely high quality defaults (i.e., UE5's metahumans or whatever it's called) the defaults just start to look cheap / recognizable. UE5 managed to make photorealistic assets look like cheap asset flips because it's the "default" in that engine.
But I see your point on everything else you said.
I'm just a bit frustrated with the current UI usability and I feel like it will be like this for a while as a user π’
I think it's probably better to just make things look "nice" for people who want to iterate quickly and easily customizable for people who want to do that. Prioritizing having "no visual style" seems like a near impossibility. Even the imgui stuff proves that. It's extremely plain but is still instantly recognizable if you know it.
I shouldn't put "no visual style" in quotes because no one actually said that I think, but it's how I read some of the goals here.
Don't want to put words in peoples mouths π
I don't think you should really be allowed to edit the scene that a gltf loads? Like the gltf is the source of truth. If you want to "edit" the gltf scene, that should probably just be a patch on the scene
Nothing stopping us from processing glTF into BSN, but that isn't necessary from a functionality perspective (or an editablity perspective).
What I have envisioned is to have a second UI toolkit, a sibling of feathers, which is also built on the foundation of bevy_ui_widgets. This second toolkit will be based around texture slicing / nine-patching, so everything is ready to go, you just have to supply artwork. There will of course be a default set of artwork, but it should be possible to easily replace the images with medieval / cyber / cartoon styles.
At least for the default "patch on top" workflow
Which is what everyone should be using imo π
The same way how it would be kinda weird if you loaded a texture from a gltf and then started mutating it - you wouldn't want to do this in your editor really
(maybe as part of gameplay systems, but that's still not editing the actual underlying data)
This alternate ui toolkit, which I don't have a name for, would have different features than feathers, since it would be aimed at games rather than editors.
It would most likely be simpler, with fewer widgets.
It would however require support in the editor for viewing the resulting templates. But we'll want that anyway in the context of inheriting form scenes defined in memory.
I am also interested in the development of "console-centric" widgets
(ex fn widget() -> impl Scene)
Integrating the feathers style directly into the headless widgets in bevy_ui_widgets would make overriding the styles hard, because in bsn there's no way to delete a component or unregister an observer.
Not everyone wants focus rings, or tab navigation, or hover highlights (many modern ui designs intentionally do not have hover highlights).
I like nine-patching support, provided we don't dictate its usage. Not every game/app UI wants image-based UI elements
Well, I also support the idea of competing third-party widget libraries
I think the default widget toolkit should probably default to simplistic vector rendering. That is easier for developers to tweak
I think a nine-patch-only widget library is a more "specific" thing
(also we should probably move this to #ui-dev)
At the moment Bevy doesn't have "simplistic vector rendering" in ui contexts, something that has been a sore point for multiple users
I was thinking in terms of what the editor workflow looks like: you should be able to take the glTF and be able to mutate the BSN AST just like you mutate your main scene BSN AST. But when you do so it generates overrides. Unless you right click and say "Unpack", then the glTF AST gets copied into your scene.
Just like what happens when you drop an FBX into Unity.
That's not really practical though, because users want to be able to spawn gltf Scenes. It would be pretty bad if they actually need to convert their BSN AST into a Scene so that then they can spawn it
It does in the sense that it can draw borders of arbitrary thickness and roundness in a shader.
Can we not generate the AST from a Scene? Like can we add something to the Scene trait to make this work?
I'm confused, why couldn't you do that? That's how my PR does it
Which is all that is needed in the context of a default UI theme, in my mind. This is how most of the web's widget libraries are made
I explicitly don't go straight from the LALRPOP productions to templates because I want the AST sandwiched in the middle there for the editor to use
I agree that arbitrary vector graphics would be nice though
Maybe I just don't understand what the BSN AST is for lol. I'll stop arguing haha
I agree that this should be an option. But I think the in-memory data model should drive it, especially in the context of loading these things without asset processing at runtime (which is what the GltfLoader is)
To be clear, do you mean going back from Templates to AST?
Note that the BSN AST is in-memory too.
In fact, the BSN AST is driven by the ECS: AST nodes are entities.
Yeah this is what I'm talking about.
Note that the BSN AST is in-memory too.
I'm aware of this. However it has the distinct disadvantage of requiring lots of dynamic reflection / mapping / constructing of real types. The AST is not an ideal final representation to spawn from
Whereas a ResolvedScene is
Which is what I think the glTF loader should output by default
Ok, I'm fine with that for performance reasons
This issue is a collection of a bunch of screenshots from various games that illustrate the kind of flexibility that we would eventually like to support: https://github.com/bevyengine/bevy/issues/22345
As long as you can go back to AST.
And yeah, you're right that forcing the AST in the middle would add a lot of use of Reflect where this isn't necessary.
Yup I think we'll want to support in-memory-to-AST, which should be pretty trivial in the context of ResolvedScene, which is just a collection of templates.
This also has the benefit of being a much simpler port, as most of the types we're working with are already templates. So we just swap the World apis for the equivalent ResolvedScene apis
BTW ReflectConvert is mostly ready, which is one of the blockers for asset BSN, since it's needed to convert a String to an AssetPath. I tested and it works great for that purpose.
I found this was the case with jackdaw tbh, there was a lot of βforcedβ reflection happening. Also found it cumbersome when having to edit values in the AST with Components that get created at runtime, or ephemeral βconstructorβ components- i actually wound up wrapping these, or having to register the Type ahead of time.
@split harness I have a design for reactive-BSN worked out, but it's missing one critical flaw: Currently bsn transforms values into (value).into(). What I need is some way of having BSN do (value).my_custom_into(<foo>) where foo is something I can either add a template to, access &mut World, whatever. How feasible is this?
I think some real-world-usage context would be helpful here. If you need direct access to World prior to applying the generated scene, that is pretty antithetical to the whole bsn premise (that it is a description of a thing that you feed to a World to produce the final thing)
So what I'm trying to get working is this:
fn ui_root() -> impl Scene {
let (text, set_text) = signal("Hello World");
bsn! {
Text(text)
}
}
I want that to turn into:
fn ui_root() -> impl Scene {
bsn! {
// Entity 1
Text("Hello World")
// Entity 2
SignalState("Hello World")
// Entity 3
Observer(<Watches SignalState, and updates Text when changed>)
}
}
(I forget if this syntax is entities or components on the same entity, but you get the idea)
There's no way to push additional templates or spawn entities based on value usage like this, though
Yeah, this is my understanding too: You can't access the world during bsn construction; but you can access the world during the resolution phase, which happens when you submit the scene for spawning.
How would it know (from a type system perspective) that the text variable should be split up into those pieces, but another variable like let text = "some value" would just be passed in directly
That's why I want a custom trait, where into() -> T gets replaced with custom_into() -> (T, extra_templates)
CustomInto would have a blanket impl on all types that also implement Into<T>
So it would be backwards compatible
Perhaps possible. Some concerns:
- A blanket impl for Into<T> feels like it might conflict with some of the custom impls we want to add. Maybe fine, but worth verifying this pattern works for what you want.
- I think this might be possible, but it would generate a lot of "garbage" in the generated code. Every variable reference would be another
()appened to the outputted bsn entries
extra_templates would just be a vec, and then we'd call resolved_templates.extend(extra_templates)
Which should hopefully compile into a no-op when it's an empty vec
This would be way worse than the typed version of this I think. There would be a ton of empty "extra templates", which would each be a new vec. Why not just make it an associated type on the CustomInto trait?
Aka why take the dynamic path, generate a bunch of garbage, and hope the compiler optimizes this out, when we can just take the static path (which everything else is already using)
an empty vec wouldn't allocate anything, it would basically be two u64's, but hmm
The other issue I'm realizing I have is that I need to track how many times custom_into() is called
Ah wait this is part of the "patch" infra, as into() is evaluted when resolving the scene
Because the first time you need to do extra work
So its going to be dynamic anyway
yeah it needs to be
Unless we do FirstCallTemplate and OtherCallTemplate as two seperate types
This is feeling pretty roundabout to me. Definitely feel free to explore the space though
I'm kinda lost on the whole patch/template/resolved scene stuff tbh
Do you mind giving me a short overview? I read the code and your PR description, but im still lost π
I'm not sure I understand everything myself. These reactive lifecycles are hard to understand; the only reason why I feel like I know the internals of Solid.js is because of the extensive blog posts on "how to write a reactive system from scratch".
So far my understanding:
Template: basically a factory, produces a type given the world.
FromTemplate: a "hack" to let BSN pick the "default" template for a type - cannot be used by actual Rust code.
I wrote the description with the intention to define each term and build the whole picture for people. I'd just be rephrasing what I already said. Perhaps you should ask questions about where you are confused? Or try defining the system from your current perspective and we can clear things up?
I get those two parts, I think
I don't get the patch or resolved scene
But template and get template make sense to me
Like why does bsn make a patch?
It's not making templates apparently
When I expand the macro
- Template: a "fancy constructor" that you feed into a World/Entity context to produce a specific type (ex: SpriteTemplate becomes a Sprite component)
- ResolvedScene: A tree of related entity descriptions, where each entity has a collection of Templates
- Scene: A composable, repeatable action that somehow contributes to a ResolvedScene. This is essentially a collection of write operations to be applied on top of a given ResolvedScene. This might do something like patch fields on an existing template if it already exists / create a new template if it doesn't, add a new related entity to the current root of ResolvedScene, add a new template, inherit from a scene asset / function, etc.
The idea being, you create a new empty ResolvedScene, then layer Scenes on top of each other to create your final ResolvedScene, which you can then spawn one or more instances of
Ok that makes sense. And where is into() being called by the BSN macro?
I think what I could do is somehow every time you read the signal, BSN tells it what number of times it's been called, and then it adds patches to either create a new entity to hold the signal data, or on second call onwards, patch it to add additional relationships pointing towards the entity reading from the signal
If we want to add some kind of incrementalization into BSN, the very first that has to be settled is: what parts of the template stick around, remaining connected to the spawned instance of the resolved scene?
- In bevy_reactor, we embed these pieces into the final entity tree as hidden nodes (either special components or ghost nodes), which means that there's no persistent connection to the resolved scene, they are entirely separate once constructed
- alternatively, you could have a permanent connection between the resolved scene and the thing that it spawns
The way I was trying to get work, it's neither
The signals are seperate entities that hold state
And then bsn spawns observers or the signal itself does this, and every time the signal state changes it updates the watched entities
But the scene needs a way to despawn those signals when it's despawned itself, right? So there is an ownership connection.
Some way observers work, relationships
I'm just having trouble figuring out how to thread all this through BSN's codegen
Consider this:
let fill: Val::Percent(100.0);
bsn! {
Node {
width: fill,
}
}
Which becomes this scene:
<Node as bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
value.width = (fill).into();
})
The value is the Template being patched. Node is currently the template for itself (it doesn't need custom templating logic), so value is &mut Node.
The patch() function returns a TemplatePatch, which is a Scene. (which is why we accept an impl Scene).
The TemplatePatch Scene impl looks like this:
fn resolve(
&self,
context: &mut ResolveContext,
scene: &mut ResolvedScene,
) -> Result<(), ResolveSceneError> {
let template = scene.get_or_insert_template::<T>(context);
(self.0)(template, context);
Ok(())
}
Aka, it just gets the template in the scene ResolvedScene (or creates a default instance of it if it doesn't exist), then calls the function on it
Does BSN always turn I into PatchFromTemplate? How does bsn know what to do with the input?
What is I?
What is "the input" in this context?
Sorry it*
Take my Text(variable) example
How does bsn know to turn that into a PatchFromTemplate?
There's this whole BsnEntry thing in the codegen I see
I can't figure out how it gets determined though
In BSN, any "struct / type" init syntax is semantically a patch. Therefore PatchFromTemplate is implied
Aka: determine the template using FromTemplate, then patch the given fields on the template
We also have @Text(variable) syntax, which would instead use PatchTemplate, which does the same thing, but assumes the given type is a template (aka FromTemplate is not used).
Ahh I see
It's seeing a struct and assuming it's a patch
Then uses get template to get the template
Well, the type T of the template
And then making a PatchFromTemplate using that T
Which gives you access to &mut T
And then the patch modifies the template to set whatever fields you specify, and call into()
Ooook
Does BSN's codegen have a "leaf" type, such that I can, in the codegen, say "Hey, if this is type T, do this thing on it"
We do also support "full value patches", which write a whole struct on top, via the template_value(MyTemplate::new()) function (which returns a Scene that writes that whole value on top)
Because I think the right way to do this is conetsuct additional templates in the BSN list whenever the macro sees a signal read
Like some sort of "right hand side" / value type?
Or uh, idk if templates is the right word
Yes
Some kind of adding extra stuff to the BSN scene produced
Which I guess is an additional patch on the scene? Idk what the thing that takes a scene and produces a modified scene is called
When parsing, we have the BsnValue type:
#[derive(Debug)]
pub enum BsnValue {
Expr(TokenStream),
Closure(TokenStream),
Ident(Ident),
Lit(Lit),
Type(BsnType),
Tuple(BsnTuple),
Name(Ident),
}
However it has no direct access to, say, the type information of the Ident
But this would leave room for having some special ~signal syntax, which adds additional contents to the output using the signal ident
I was hoping to have it be type based
You call signal() and get a ReadSignal and WriteSignal
And then whenever read signal is used in a BSN scene, it creates th observers/whatever
Without you have to add explicit entities to the tree
Yup then we're probably back to a special Into function
and the associated jank
I guess I could start there, and have the user add something to the BSN tree
No harm in experimenting
And then we could figure out how to make it automatic in the macro
Just wrapped up an initial "spawn ResolvedScene as a dynamic bundle":
ui_raw_bundle_no_scene changing is very surprising to me, given that I haven't touched that path
But that is consistent across many runs both before and after
Probably due to the benchmarks getting compiled together / optimizations being applied?
The strategy is essentially "use a bump allocator and BundleScratch" (like we do in the clone infra), pass that in to ResolvedScene::spawn which passes that to ErasedTemplate::apply, which writes to the bundle scratch instead of inserting the final component directly
Once the scene (and its inherited scene, if it exists) has applied all of the erased templates, we insert the final dynamic bundle
It reuses scratch bundle space across each spawn, although it currently keeps "bumping" the allocator for each spawn
Resetting the cursor after the spawn is the move / should yield some perf wins
As a reminder, these are all tests that produce the same UI scene hierarchy through different means:
immediate_function_scene: a test of going through the whole "scene building" process, then spawning. this is the cost of ad-hoc scenes that don't reuse work from inherited scenesimmediate_loaded_scene: a test where we instantiate a bunch of inherited scene instances, where the inherited scene has already been computed / cached.raw_bundle_no_scene: just spawning the raw bundle directly
These are relatively "small" entities, so this is more of an "overhead" measure. Testing now with a few extra Mat4 wrapper components to help illustrate the archetype move wins
(baseline: a few extra Mat4 wrapper components on main / current: a few extra Mat4 wrapper components with ResolvedScene-as-dynamic-bundle spawning)
Interestingly, raw_bundle_no_scene is stable across the changes with those extra Mat4 wrapper components, making the "weird optimizer schennanegans" more likely I think
I think with some elbow grease we could avoid the need to write to "scratch space" at all
Which would probably help close the gap further. Ex:
- Compute the final archetype and allocate space there before building the final component
- Build each component and write it directly to the archetype's table / the sparse sets
Aka currently we must do Template->Component (stack) -> Component (Scratch) -> Component (Archetype Table), but I think we can probably get away with Template-> Component (stack) -> Component (Archetype Table)
But it will require reworking some of our entity insertion code. There are some concerns that might get in the way, such as the fact that Template->Component(Stack) requires mutable World access and can perform arbitrary edits. So pre-allocating an entity before then is a bit "hairy"
Because if it exists in the archetype, it is queryable
But it wouldn't be initialized yet, which is bad / illegal
And I really don't want to go to jail
After sleeping on it, I don't think signals are the way to go
I think it would be possible to get working, but would involve some changes to the BSN macro that idk if it makes sense
But also I don't know how you would do things like reactive for loops on queries...
For that reason, and because it makes BSN asset hot reloading easier, I feel like template diffing makes more sense (which I kinda already knew, but I wanted to try signals)
@split harness btw any thoughts on improving BSN ergonomics with queries/resource access? Something like automatically threading system params through nested BSN functions
@topaz ginkgo / @steel oak did you have something that goes from entity tree -> ResolvedScene?
Not entirely sure what you mean, but I'm pretty sure I don't have it
Like going from entities spawned into the world, into a BSN scene
Ah, I think I'm thinking of https://github.com/bevyengine/bevy/pull/23639
If you are thinking of ECS -> bsn AST yes thatβs it
ResolvedScene is what you get when you want to spawn in the ECS AFAIK
What does your thing generate?
you get a String from the serialization in that PR
Ah :/
it's basically seriliaze_ecs() -> String -> DynamicBsnLoader -> ScenePatch.resolve() -> ResolvedScene -> World
that's the data flow as per my understanding
so that's basically how you get from a scene, to a serialized scene file, then load it back in
but this is only because i want to generate a scene file to be loaded
My idea to mimic React is:
- #[reactive_bsn] attribute adds a ReactiveBsn component to the root scene, containing a copy of the pointer to the function it's defined on
- A system that queries for ReactiveBsn components, and:
- Generates a BSN thing (dyn Scene?) from the existing entity tree, starting at the queried entity
- Re-runs the stored function, generating a new Scene
- Diffs them to produce a series of changes to make to the entity tree
so i think you would have to change the path of the file writer for world -> bsn for this
because you'd be going ECS -> String -> ScenePatch -> ResolvedScene on every diff
you probably want a ecs_to_resolved_scene() or something, but then how do you handle identity/key? how would you handle component and field-level diffs? ie, do you replace Transform each time a field changes, or patch the specific field - which then moves you back to the ScenePatch
instead of ResolvedScenes for reactivity could you maybe just store the original AST, rerun the function, diff, apply relevant patches, and then resolve the scene once done?
I don't want to do that, because then you can never modify the generated entities outside of a reactive BSN thing
No systems
you would just take a snapshot of the function's last output, not of the ECS
Right, but what if someone modified the ecs via a system?
i believe React has a concept of controlled vs uncontrolled inputs (but it's been a while since i've looked into it)
but ultimately it would mean the "reactive" diff wins here
ie, for that attribute/component the source of truth is the "reactive" function
rather than the ECS
so something like:
- fn() produces Scene1 and spawn that into the ECS, store Scene1 as the last reactive snapshot
- system modifies Transform.y on a reactive entity - this is ok, sets it to 50 in the ECS
- Dependency change triggers rerun and fn() produces Scene2
- Diff Scene1 -> Scene2: reactive function changed Text from "hello" to "world"
- apply only that delta to ECS, the system's Transform.y modification is untouched on the resolve()
- Replace snapshot with Scene2 - ECS still see Transform.y at 50
does that make sense?
in this example, the AST still thinks Transform.y is whatever it was initialized as, because we haven't explicitly changed it in a reactive function
How do you intend to diff? Reflect over components?
there would be limitation to this i think, but yes
ie, closures in on() handlers don't impl Reflect so you might have to exclude these, or always replace
i think nested Vec fields would be problematic as well
That's my concern as well. In the case of React, AFAIK there is special case logic for certain DOM nodes which can't be diffed in the normal way (e.g. text inputs), but this is possible because the DOM is a fixed specification which doesn't change. This is unlike Bevy where new components are being invented constantly.
React provides useCallback(fn, dependencies) to effectively diff a closure by a different key (dependencies) *
(* They do this not because closures aren't comparable but because they are and can change too easily. It's also separate from the actual diff system; it's just a helper for caching fn when dependencies hasn't changed)
Though getting dependencies wrong in React is a very common footgun
Some escape hatch for non-Reflect would probably be necessary, not just for closures/on()
i think you can kind of solve this with registering some new type data, ie ReflectReactive that components can register to control how they're diffed/applied
ie, you could have something like ReflectReactiveDiff for diffing asset contents, and not the handle ID (just an idea)
At the risk of making Jasmine bored (since we've talked about this endlessly) I will describe my approach, which is to take inspiration from Solid.js rather than React.js. Solid doesn't use diffing or a VDOM, instead it uses fine-grained micro-reactions. That is, rather than regenerating an entire React component and then comparing the new version with the previous version, Solid has the ability to have reactions that modify a single DOM node, or even a single property of a DOM node. This is extremely performant - if you want to write something like a 120fps audio spectrum analyzer in TypeScript, Solid is a good choice. Because the reactions are so narrowly targeted, only updating the parts of the DOM that depend on the particular inputs, you don't have to spend time deciding what parts of the DOM haven't changed.
Applying this to Bevy, what I do is to insert special hidden entities and components (using the OwnedBy relation) into the output hierarchy that are responsible for managing reactions and mutating the tree. These hidden components are created by the bsn scene but are entirely independent of them once created, meaning that the bsn scene can be disposed afterwards.
That being said, my scheme is not perfect, because we still have to fit updates into the world of Bevy systems and schedules, and we still have to deal with Rust lifetimes in a way that is not applicable in JavaScript.
My system of reactions depends heavily on "poor man's trait queries", using component hooks to build an index of components that impl a trait so that we can iterate over all the components that have that trait.
And because my reactions run as part of the ECS schedule, it means that we only get to run reactions at a certain point in Update, not before or after. It's not like observers where the reactions happen immediately.
However, running as part of the schedule and using regular Bevy change detection does have the advantage that I don't have to worry about The Diamond Problem, which is something that any observer-based approach would have to address.
"The diamond problem" is where you have some entity D, that depends on inputs B and C, both of which depend on A. So the dependency graph is the shape of a diamond. The problem is that updating A causes an update on B and C, but you don't want D to update until both B and C have finished updating. This can lead to incorrect behavior because D might see a world in which B and C are inconsistent. This can cause some nasty bugs, something I have run into in my own work.
Yeah, that's workable.
Cool! Iβm not familiar with Solid.js
I would argue that reactivity should not run in the ECS, but rather on the bsn AST
It just gets synced to the ECS via Patch
That would mean you'd lock yourself into a slow(er than necessary) mechanism for doing updates. In theory you could generate a function (from BSN or not) that transforms the ECS directly based on some sort of signal.
That is certainly a choice, but it's not the only choice. Also, since I am a pedant, I want to point out that what we are talking about is not really reactivity but incrementalization (that is the ability to update a hierarchy in place without rebuilding it from scratch). You can have incrementalization without reactivity, for example if you manually trigger a patch update by supplying new input parameters to the template.
In any case, I'm pretty happy overall with what this stuff does to the ergonomics of writing ui code. Feathers, which doesn't use a reactive framework, is pretty horrible internally, it's a mess of ECS systems and observers. In bevy_reactor I'm able to replace 100 lines of ECS systems with a 12-line reaction closure.
Depends on what the intent / context is there. I think queries in the context of reactivity make some sense. I think its important to note that Scenes are structural / declarations / context free, so in most cases they probably shouldn't be doing any world access as part of their initial declaration. Imo it should be when they are spawned that a query is evaluated
I do think @rare whale's approach in bevy_reactor does fit pretty well into the BSN paradigm (reactivity is something that exists inside the ECS and BSN just initializes the reactive pieces).
There's a third option I would like to point out, which is for bsn to generate two trees: one containing the dynamic update plumbing, and one which is the actual visible output. That is, instead of embedding the dynamic stuff in hidden nodes in the output tree, instead put it in its own separate tree which points to the output. This means that spawning a scene would have to return some kind of handle which points to the dynamic nodes.
This idea has some challenges, but it means that you wouldn't have to resort to trickery to ensure that the dynamic plumbing remains invisible.
Popping up a few levels, I want to talk about the current state of things at the ecosystem level. We have 4 or 5 different reactive frameworks that I know of:
- My stuff (bevy_reactor, quill, and thorium)
- @cobalt stone 's bevy_dioxus work, plus more recent experiments
- Avi's jonmo and haalka (There are several Avis here, not sure which one to link to)
- @queen oak's thing, forget what it's called
- @timid leaf 's cobweb_ui
All of these have interesting ideas and none should be dismissed out of hand; unfortunately, no one really has the time to become fluent in each of these frameworks to the degree needed to make a useful comparison, because they are all radically different in approach.
However, having looked briefly at all of them, I would say that what they all have in common is that they more or less diverge from the idiomatic "bevy experience" - to what extent that is important depends on how you feel about the parts of bevy that they are diverging from.
Also, there has not been much experimentation in compile-time data dependency analysis of the kind seen in frameworks like Svelte.js. Most of what we see is dependency graphs being built at runtime.
Modeling my stuff on Solid has an additional shortcut: there's an excellent series of blog posts that explain how Solid works internally, which is how I learned a lot of this stuff: https://dev.to/ryansolid/building-a-reactive-library-from-scratch-1i0p
yeah if we're talking about reactivity i have a pretty controversial take on it, based around using async to make it explicit and procedural, you .await button presses, and well anything, and using tools like select!{}, breaks and labels you can build complex machinery in a few lines of code that expresses some pretty complex behavior
but it very much doesn't feel like bevy, it feels like a strange cross-breed of bevy and async
but it doesn't use the ecs's systems to store ui functionality, ( while it does store part of the state in the ecs itself, the other part just exists in the closures inline, like boolean flags, inline vecs, etc ) , it builds the ui state procedurally, it's like immediate mode in some ways, in terms of it's explicit procedural nature, but because it's using .await it doesn't actually incur the standard costs of immediate mode in terms of cpu consumption
Reusing space in the bump allocator by resetting after each bundle insert does save a bit of time:
(baseline is still relative to main)
It required writing a new, less safe BundleScratch impl that ignores the lifetime. This is due to how collections with lifetimes work
The borrow checker can't know if there are any references currently present in the collection, meaning theres no way to "clear" the idea that the collection is holding borrows. This blocked the ability to reset the bump allocator
We either need to move to allocating new bundle scratch space on each insert, or erase the bump allocator lifetime
Very hard to ensure this is safe globally. But in this context where the accesses are "local", we can make it pretty safe validate the code easily without much fear of future developers invalidating an assumption
The next big win is skipping scratch space entirely, but that will need to wait
Utilizing BundleWriter for the Relationship component insert and avoiding duplicate insertions for inherited scenes with components that use copy-on-write also yields some solid wins
I've also inverted the BSN spawn order to be "bottom up" instead of "top down". This means that when the root component is added, the entire scene is available.
Ex: it makes this possible:
bsn!{
:"player.gltf#Scene0"
Player { held_item: #Item }
Children [
(#Item Item),
]
}
impl Player {
fn on_add(add: On<Add, Self>, players: Query<&Self>, items: Query<&Item>) {
let player = players.get(add.entity).unwrap();
let item = items.get(player.held_item).unwrap();
// the whole inherited glTF scene is also available here
}
}
Does that make the reverse not possible? (child component with parent reference) also, with respect to hooks/observers are the events now in reverse order, if so would there be some way to cache them and emit them in correct order? maybe it doesnt matter though.
They still have access to the parent entity via the ChildOf relationship, but there won't be the final components there yet.
The alternative is to add another set of bottom-up SceneReady events, but spawn top-down
The downside there is incurring the overhead of triggering a bunch of additional hooks / observers
/ adding more complexity to the system
Is the intention for users to know whether or not their components are used in a scene or not, to be able to uphold whatever external invariants they may have? I suppose that is orthogonal though, and whether or not hooks/lifecycle events should be used for external invariants.
Yeah the intention is to enable reliable structural abstraction-building, tied to the Component type. Ex: a top levelPlayer component can have init logic (ex: the On<Add, Player> logic above) that builds on the behaviors of its child entities.
I believe this is generally what people want / it is a crucial piece that Bevy is missing that pretty much every other engine supports
Oh that's interesting. Reminds me of SceneInstanceReady patterns. I don't mind it.
So the entities for parents/child/relation components are filled in, but the other components outside of the current relation scope dont exist yet, right? or no.
I think the choice isn't really "should we do this" but rather how:
- Leave spawn order (and therefore
Addevents) as-is: They are still top down. We add another set ofReadyevents, triggered bottom up.
- Pros: not a breaking change, people can choose top-down or bottom-up
- Cons: For a scene with N entities, we trigger an additional N entity events. We have one more event that people need to learn about.
- Make spawning bottom-up. This would ultimately be both BSN and bundle-spawning.
- Pros: Simpler (no new events) and more efficient (we don't trigger more per-entity lifecycle events)
- Cons: The "component reaches into its parent on Add and reads a parent Component" pattern is no longer supported.
Yeah this would ultimately replace SceneInstanceReady
So the idea is to wait to change WorldAsset spawning then (since it could have this same change, without bsn), and eliminate WorldAsset long-term?
Correct
bsn!{
:"player.gltf#Scene0"
#Player
Player { held_item: #Item }
Children [
(#Item Item, EntHolder(#Player)),
]
}
#[derive(Component)]
struct Item;
impl Item {
fn on_add(...) {
let entity = ...; // Parent entity from ChildOf
world.component::<Player>(entity); // Check that Player Component exists on the Parent
}
}
Does this work? (psuedo-code of course)
P.S: As I posted kek, I should just wait
I would prefer 2. I think it's the better default. You can always play games with deferred setup of children values that need to do that
This would not work, as Item would be Added before its parent's Player component. It would fail on the world.component::<Player>(entity)
The EntHolder would still work though? I assume all the Entities are pre-alloced or something like that?
Correct
Godot does notably have both (_enter_tree() / top-down vs _ready() / bottom-up) analgous to our current Add behavior and the proposed Ready behavior
2 is similiar in logic, I think, to tail recursion an seems to be able(?) to take advantage of the same perf optimization, so that should probably be the path.
That (prior art) combined with the flexibilty to do both, and being non-breaking for the current ecosystem is a pretty compelling argument to do both and just eat the overhead
You can also do a second event with 2 right? if that is needed.
When the root component is added, the scene is ready and you send the ready event down the scene.
I think the Con of "component reaches into its parent on Add and reads a parent Component" being unsupported has an additional twist in that it "is only supported outside of scene usage", which is something people run into today: They write on_add as if the component was the last component being inserted, which breaks when introducing scene usage
I think the choice may be whether we want to be able to have access to parent data or child data in the 'default' hook path, where 'default' means supported by both scenes and normal spawning.
Consistency would make sense to me. If we go the Ready route, we'd almost certainly want to fire that as part of normal bundle spawning too
Although that does complicate things, as the bundle-spawn Ready would trigger early in the context of BSN, which inserts one entity at at time
So maybe it should be a scene-only thing
I guess we could have a spawn_no_ready variant
Hmm, what if you split hooks/observers, no external access in hooks, if you need external access you use observers π
Then spawn order doesn't matter for hooks, and then queue/cache the events somehow, so that the entities will be fully spawned by the time observers are run. /j
what about a different second class of hook intended for relationship based behavior instead of entity, that waits until the whole scene has loaded and then runs the hook on every entity with every component on the scene
on_scene_ready
this would also work in normal spawning
where can i find more bsn example? (it's to make bsn example ironically)
The tests are probably the best. Search bsn!
Here's an example of the 3d_scene example recreated in BSN https://github.com/pcwalton/bevy/blob/bbc8b57ef45059b57633d30d9a18958bcad1dca4/assets/scenes/example.bsn
Fixed up #[template(built_in)] (#23699), trying to get it merged for y'all
will next gen scenes and new ui system be available for 0.19 ?
are .bsn files hot reloadable ?
.bsn will be 0.20, in 0.19 we get bsn! so things will be defined in-code still while the asset format is hashed out but transferring them to files should be trivial-ish afterwards.
pcwalton already has a PR up for the asset format and everyone's after design tooling based on BSN, so it's not like it's a loose promise.
I have a PR up for the serialization as well to a file, this will essentially allow jackdaw to fully migrate to BSN
And thereβs some other tweaks and improvements Iβd like to make for the ergonomics the dynamic scene stuff (ie, exposing an API for jackdaw to use)
Yeah you two have been knocking it out of the park
so bsn syntax is finalised ?
aye, though more things may end up being added on (i.e. reactivity in the bsn macro)
are ui widgets not going to be behind experimental_bevy_ui_widgets in 0.19?
bevy_ui_widgets is marked as non-experiemental now. We're still debating whether to do the same with feathers
if nobody can stop jackdaw it started a month-ish ago
if the 1st party editor is something else it likely will pick up learnings from that, so it either way is already ongoing
oof so we can't use the #Name entity within expressions
Is this blocked on some issue?
I think i see why
Hmm, how are we intended to use tuples of values inside our components?
Doing MyComponent((#Name1, #Name2)) does not work
@steel oak just reviewed + approved the ReflectConvert PR / made a minor addition:
https://github.com/bevyengine/bevy/pull/23742#pullrequestreview-4101586458
One of the important features of BSN is that supplying a value of type T where a value of type U is expected is allowed if a From conversion exists from T to U. This is what allows HandleTemplates ...
Into the merge train she goes...
This is roughly the same problem as supporting #Name in scene functions like button(#Name) (which we also don't do). The issue is that we currently just parse rust expressions directly, rather than doing our own expression parsing logic so we can insert our own logic in arbitrary places.
Not a particularly hard problem, just work that hasn't been done yet
I c I c, nice thing is you can work around it because the default init stuff
So I have a component thats just 16 entities
And the init patching makes it all work out
Now, I am getting panics on using names inside entities, is this a ordering issue?
The entities name doesn't exist yet because the child needs to be spawned first?
It would be nice if we spawned the associated entity on first access of the #Name and then inserted components on it afterwards when we encounter it?
Hmm, the example on the original PR actually shows exactly the behavior i'm panicking on rn
One second will link
#[derive(Component, GetTemplate)]
struct UiRoot(Entity);
#[derive(Component, GetTemplate)]
struct CurrentButton(Entity);
bsn! {
#Root
CurrentButton(#MyButton)
Children [
(
#MyButton,
UiRoot(#Root)
)
]
I'm getting a panic in ScopedEntities::get when I try to run something ~ equivalent
@split harness I actually think this is a straight up bug?
Thank you!
This bug didn't exist in the pr after the feathers -> bsn port
So its somewhere between then and now that it slipped in
Experimenting with react-in-bevy, I mostly have something working
Hard part is I haven't figured out a good equivalent to useState yet
There's no good way to say "determine observer system params based on captured closure data"
I have my async ui stuff working with bsn ( and older version of it to avoid the panic bug with names ) and it is great
Bsn having the names allowing me to reference entities across the hierarchy makes the whole thing so much better
I so want to nuke my project's glXF from orbit
Seeing complex Hanabi VFX built in BSN will be lovely
very confused as to why i'm not triggering the bug here, it might have something to do with the level of nesting? I am going to try to get a minimal repro done tomorrow
Doing this exactly doesn't trigger it, but something else more complex does
This seems like a pretty hard problem, i'm attempting to thread it through by converting all expressions to closures that must be FnMut and clone-able
we'll see how much success i have
then we can resolve the entities ahead of time for each actual template resolution, and then pass them through to each closure during that, and evaluate the closure
Yeah happy to investigate once theres a repro
I think for now, I'm going to go with (1):
- It doesn't break the existing lifecycle for non BSN users
- Supporting both lifecycle events (top down and bottom up) enables useful patterns
- Switching just BSN to bottom up spawning for 0.19 would be bad, as it would create lifecycle inconsistencies between bundle spawning and bsn spawning. The lifecycle should be the same in both contexts
Sadly this does complicate the BSN impl when it comes to dynamic bundle spawning in the context of inheritance / I'll need to do a bit of work.
(coupling the lifecycle change to dynamic bundles was no coincidence ... I saw that flipping the order would work nicely with my current approach. I knew I wanted to consider flipping the order anyway, so I took my chance)
Alrighty I've wrapped up moving back to "top down" spawn order in the context of dynamic bundles. Weirdly, that performs worse than "bottom up". Perhaps something to do with needing to keep spawn context from "parent" entities on the stack?
Note that raw_bundle_no_scene also regressed significantly (despite not being touched). Might be weird micro optimization BS that we wouldn't actually see in a larger context?
Very consistently different though. Not much "noise" in these benchmarks between runs (both before and after)
I'm now adding empty RelationshipTarget components to the dynamic bundle for "parent entities". This is pre-allocated with space to hold each related entity. This also avoids archetype moves in the parent after spawning the first related entity.
Sadly this isn't moving the needle on the benchmark, which is even more surprising than the regression introduced by moving back to top-down-spawning
Anyway, I think this is good enough for 0.19 / the big expenses have been mitigated
I'll wrap up quality work tomorrow morning / get this out
I'm actually not sure this is possible at all with the cureent infrastructure, purely from macro rewriting, making it possible to get an Entity in the expression value
Passing the scoped entity to a function is trivial
But while we can convert the expressions we care about to FnMut closures, there's no mechanism to modify their scoped entities to real entities before running the closure / passing the real value, i think
Oh yeah getting the actual Entity in the expression value is a no-go. But the EntityTemplateEntityReference should be possible

I want to be able to reference #Name as an entity inside my async closure, cause it would make it so neat
I could make an exception inside the bsn macro itself to make it work tho, for that specific case, in theory
And implement template manually
I think?
This is the plan for supporting #Name in observers. Ex: have a template that takes the EntityReferences, resolves them to Entity, passes that into the final closure and returns that
Would something like a special exception for a piece of syntax that only shows up in a third party crate be supported by the bsn macro? Similar to how we Derive Serialize in 3rd party crates, or is that out of scope
I guess I could always fork bsn syntax and make my own thats 1-1 with my exception for #Name in async stuff
That's not awful, its a little bad but its whatever
Extending the macro with new syntax is not supported / im not sure how that would even work in the context of a proc macro
Yeah I was thinking like just, direct support, as in the same way you carve out an exception for on
We could definitely try to build a general purpose feature that 3rd party crates can use
π₯Ί
It would be a significant ux improvement
Rn I have an entity that is called Entities with 16 fields that are all entity
And you have to match up that with the closure which takes in entities as parameters lmao
@split harness I have figured out a minimal reproduction for the regression!
This didn't used to crash but now it does
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
))
.add_systems(Startup, |world: &mut World| -> Result { world.spawn_scene(demo_root())?;
Ok(())
})
.run();
}
#[derive(Component, FromTemplate)]
struct MyEntity(Entity);
fn demo_root() -> impl Scene {
bsn! {
MyEntity
}
}
Hmm, shouldn't that crash just because it's not been given a reference? Is that the same error as the name error?
fn demo_root() -> impl Scene {
bsn! {
Node {
width: percent(100),
height: percent(100),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
}
ThemeBackgroundColor(tokens::WINDOW_BG)
Children[(
Node {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
}
Children [
(
#Minus button(ButtonProps::default())
Children [ (Text::new("-1") ThemedText) ]
),
(
#Counter Text::new("0") ThemedText
Node {
margin: UiRect::horizontal(px(10.0)),
}
),
(
#Plus button(ButtonProps::default())
Children [ (Text::new("+1") ThemedText) ]
)
]
)]
async |world: AsyncWorld| {
let text_system = world.system_state::<Query<&mut Text>>();
let mut number = 0;
loop {
futures::select! {
_ = world.on::<Activate, ()>(#Minus).await.fuse() => { number -= 1 }
_ = world.on::<Activate, ()>(#Plus).await.fuse() => { number += 1 }
}
text_system.bridge(AsyncUi, |mut query| {
query.get_mut(#Counter).unwrap().0 = format!("{}", number);
}).await.unwrap();
}
}
}
}
By modifying bsn macro a little I can automatically spawn my async closures, pass AsyncWorld to them, and have #Name work as entity values within it!
( this is the feathers button example rewritten )
This should fail, but currently its failing on an unwrap because it is trying to resolve an entty reference that doesn't exist. I suspect the difference you're observing is the result of removing the EntityReference::Path variant, which was the default before it was removed
I think we should add an EntityReference::None variant, which returns an "entity doesn't exist" template error
Or alternatively we could squeeze in the Path variant again, but I think the EntityPath impl is controversial enough / important enough to not rush it in
Yeah, with my forked macro i'm less concerned about having the ability to stuff arbitrary potentially wrong entities into a component
Did this use the strategy we discussed? (template that returns the final closure each time it is instanced?)
Yep
Good to know that works
Its awesome, and we could even make it generic
Here me out
What if we allowed people to inline add an async block, and whatever parameters they put in must be Resource + Clone
And we spawn the async task and detach it
why not Option
manually adding None variants feels like a code smell
rust has niching
A top-level wrapper likeOption<EntityReference>(ex: FromTemplate<Template = Option<EntityReference>> for Entity) doesn't feel great to me, as it means people assigning an EntityReference to an Entity template need to wrap it in Some. And I personally consider EntityReference::None to be cleaner than EntityReference::ScopedEntityIndex(Option<ScopedEntityIndex>), as None isn't really tied to a particular variant of EntityReference
BSN does have implicit into(), but that means we lose out on inner into() for things like ScopedEntityIndex and a future EntityPath
Relying on into() for EntityReference and co into Option<EntityReference> would also incur complexity / overhead on the Reflect side for a "high traffic" type (as we'd need to use ReflectConvert registrations for each into() impl)
Also aesthetically flatter == better. Some(EntityReference::Path(path)) is less fun to read and type than EntityReference::Path(path)
okay
When will there be a vscode extension for bsn ?
VSCode uses TextMate grammar syntax for the pattern matching for syntax highlighting, it's not too bad but does have a learning curve. I'm sure someone will come up with this eventually.
what would a vscode extension accomplish?
isn't rustanalyzer already providing error handling and autocomplete?
for the .bsn file format probably
Ultimately this would be nice, but I see it as significantly less pressing. Syntax highlighting shouldn't be too bad to wire up though
We'll want the extension to add bsn formatting capabilities as well
(both for .bsn files and for bsn! macros)
I don't think it's the right time to discuss extensions but when we do we should be very careful to not make it vscode specific
oh
I was thinking about the macro, yeah makes sense
doc nit: https://dev-docs.bevy.org/bevy/ecs/prelude/trait.Template.html
It enables define types
A Template is something that, given a spawn context (target Entity, World, etc), can produce a Template::Output.
Note that my LALRPOP grammar and nom lexer is quite easily translatable to editors π
Though honestly it might be better to rewrite it in Logos for this purpose?
Personally, I use neovim and I'll want treesitter support. My point was mostly that any kind of tooling should be built as editor agnostic as possible. Like, if people want LSP like feature it should be implemented as an LSP and not something bespoke that is vscode only.
@split harness I'm attempting to revive bevy_reactor with the latest main, and I got all the examples working except for the propedit one, which fails with the following:
thread 'main' (57002876) panicked at /Users/talin/.cargo/git/checkouts/bevy-50d7e162b728c6c6/250a7c0/crates/bevy_scene/src/spawn.rs:467:14:
Requested resource bevy_scene::spawn::QueuedScenes does not exist in the `World`.
Did you forget to add it using `app.insert_resource` / `app.init_resource`?
Resources are also implicitly added via `app.add_message`,
and can be added by plugins.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_scene::spawn::spawn_queued`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
It sounds like someone just forgot to init a resource π
A Resource that tracks entities / scenes that have been queued to spawn.
Yeah, but none of the other examples have this problem
Is that example the only one that uses queue_spawn_scene / queue_spawn_scene_list?
I suspect the problem is that you aren't adding the ScenePlugin?
I'm adding DefaultPlugins
I got the same error when spawning a scene. I opened an issue for it.
Issue #23731
Have you disabled default features?
bevy = { git = "https://github.com/bevyengine/bevy.git", version = "0.19.0-dev", features = [
"dynamic_linking",
"ghost_nodes",
"experimental_bevy_feathers",
"file_watcher",
"debug",
] }
Hmmm that should be fine
Spawning a scene that triggers a component hook or observer that itself spawns a scene causes a panic.
Just read your issue. Your suspicion there seems like its on the right track
Oh I bet this is a hokey-pokey problem
This is where the hokey pokey happens
I wonder if we can have an explicit API here like remove_hokey_pokey (bike shed needed) which leaves behind a tombstone that accessing a resource would give us a nice error. I ran into the same issue trying to hokey-pokey the Schedules resource. If the error said "hokey pokey" it would have been clearer what was going on
Then again maybe this situation is too niche lol
I've run into this problem before with one-shot systems π
The ideal thing would be if that tombstone isn't just an error tool, but instead creates a duplicate copy that is then reconciled
The main thing that is different about this particular example (the one where I am getting the panic) is that it's calling queue_spawn_scene_related based on a traversal of reflected types (it's a reflection-based property editor). The other examples work fine.
Doing this silently risks introducing subtle correctness bugs
Really depends on the context. Simple queues might be fine, but even that depends on the context
Yeah. I don't think it's the right choice by default, but it's a pattern that we should at least document
Am I missing something or is not currently possible to spawn bsn as a child using EntityCommands? As in, doing something like commands.entity(e).with_children(|c| c.spawn(bsn! {})); I'm not sure if it's an oversight or a limitation of bsn.
I was expecting to see a spawn_scene in this context but it doesn't exist
Maybe queue_spawn_related_scenes?
Sure, I don't really mind the name, I just don't know if it's actually possible π
For context, I was doing something where I'm despawning all the children of a node then I want to spawn a new scene under that node. Before bsn I was spawning things directly and now while trying to migrate to bsn I kept this pattern and that's how I ended up here
No, I mean that is already a method on EntityCommandsSceneExt
Oh, I tried searching for it and didn't found it so I assumed you were suggesting a name for the api π. I must have made a typo in my search
That worked π it wasn't super intuitive that I couldn't just spawn a scene on a RelatedSpawnerCommands though
Left some thoughts and clarifying comments on Allow the creation of handles from runtime values in bsn!.
TL;DR: neat feature to have, not a blocker for 0.19. I have some ideas on how to best accomplish this but it's non-trivial.
Responded. This is something I've already put a solid chunk of thought into:
https://github.com/bevyengine/bevy/issues/23822#issuecomment-4263077966
Oh lovely, I was hoping you might have!
Ohohoho project management toy
Correct me if i'm wrong, but the cost of grabbing an uncontested mutex is around 20 nanoseconds or so
Last I checked spawning happens one by one*
Maybe thats going to change?
But I think the mutex thing might be best benchmarked first as an easy path before we get assets as entities
Not any time soon, given the &mut World requirement.
Yeah if we can prove it isn't significant I'm down. I'll put my branch out there
Kicking off a merge for https://github.com/bevyengine/bevy/pull/23808
Just resolved @random river's feedback on https://github.com/bevyengine/bevy/pull/23808
The type state changes to BundleWriter were really nice
Quick on the draw π
It's nice to see this cleaned up some more π
Just put out my "asset value templates" PR (draft / needs benchmarking):
https://github.com/bevyengine/bevy/pull/23839
@queen oak
Awesome! Will do so!
I have left review comments but I fear I'm growing predictable π
I do quite like this: I think this is a Good Enough solution for now.
Is there any reason build_template shouldn't just take a &mut? This could also allow templates to be used once by storing an Option that they take - when you wanna insert not-clone-able stuff (could be considered a bug or a feature lol)
Maybe because this would require a &mut ResolvedScene (or just Scene) too which is also cursed
Largely for this reason. Applying/spawning scenes involves accessing arbitrary ScenePatch assets (for inheritance). If we need mutable access to them, we'd need arbitrary multi-mut access to the assets in Assets<ScenePatch>
For awhile it did actually take &mut
(Back when there was no such thing as cached inheritance)
One restriction I am currently looking into removing is requiring clone everywhere. Ex:
let mesh = meshes.add(SOME_MESH);
bsn! {
Mesh3d({mesh.clone()})
}
I've managed to (largely) remove the "repeatability" constraint on Scene (ex: change Scene::resolve(&self, ...) to Scene::resolve(self, ...).
I have this compiling:
let mesh = meshes.add(SOME_MESH);
bsn! {
Mesh3d(mesh)
}
I know a number of people have hit this / like me have been annoyed by it ( @rare whale, @magic belfry )
The only major tradeoff I'm seeing here is a loss of impl Scene for Box<dyn Scene>, which is a pretty massive loss
If we can sort out a way to support that, I think we're in the clear
The other major tradeoff is that it does mean function-style scenes can only be "applied" once, so you need to call them again if you want to re-apply directly. In practice we're doing this anyway, and we can still cache the ResolvedScene for inherited function scenes.
The repeatability (now that we have inheritance caching implemented) is really just a theoretical performance win that we won't use in practice.
I wonder if we can do whatever hacks Rust does to make Box<dyn FnOnce> work
As Scene is really behaving like FnOnce now
In this context, even with &mut in build_template we'd still want the Arc/Mutex approach, as we want inherited scenes to share the same asset
Eh wait actually this case would perhaps be fine, as we only clone when we write on top, and if we're writing on top, its a different asset π
@split harness So, I was going to test out the QueuedScenes fix with bevy_reactor, but unfortunately it no longer compiles:
`()` is not a `Component`
the trait `bevy::prelude::Component` is not implemented for `()`
This is expected. Switch your Scene impl to use push_bundle_template
This is from https://github.com/bevyengine/bevy/pull/23808
Gotcha, will do. Basically all my reactive primitives work like on().
OK I can confirm it's working
How would this work with shared assets? Ie, say i have a Scene 1 that needs asset A and asset B, and Scene 2 which needs asset A and asset C - but i donβt want to duplicate asset A
(Directly related to my proposed asset catalog) π
Not in this form. This is just a temporary / easy win. This comment has the longer term plan: https://github.com/bevyengine/bevy/issues/23822#issuecomment-4263077966
What problem does this solve or what need does it fill? You will still need to pass a handle from outside or add it manually in template(...). pub fn demo(atlas: Handle<TextureAtlasLayout>) -...
Which could ultimately support cross-scene asset sharing
Ok yea, makes sense! I think the only thing stopping jackdaw from a full bsn migration now is @steel oak dynamic BSN changes. We can use the asset catalog i wrote, and the .bsn file format serialization i wrote as well directly in jackdaw for the time being
Yep and the asset BSN is waiting on review from @split harness (no rush at all, by the way! I'm completely swamped myself)
Just benchmarked the "handle template values". Not too bad!
https://github.com/bevyengine/bevy/pull/23839#issuecomment-4271508184
I've transitioned this out of draft mode / I think its ready for final review:
https://github.com/bevyengine/bevy/pull/23839
Objective
Temporary simple / suboptimal solution to #23822
It would be very nice to be able to define assets inline in BSN.
Solution
Add a new HandleTemplate::Value variant, which contains an Arc&...
Came up with some quick "spawn scene" system ergonomics helpers:
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, scene.spawn())
.run();
}
fn scene() -> impl SceneList {
bsn_list![Camera2d, ui()]
}
Given that the pattern is very often just "spawn this scene on startup". This will also play nicely with state transitions I think
Although maybe not, given that we often want to despawn on exit
@split harness I've been wanting to discuss my suggestion about using the markdown page break token (---) as a divider between entities in BSN. However, I have a difficulty in that the advantages of my approach are more obvious in large code examples with lots of nesting levels - too large to be posted in a discord message (with my permission level anyway).
However, you can sort of do this today: within a Children [] block, you don't actually need the parentheses, and you can put the comma on its own line:
Children [
ComponentA
ComponentB
ComponentC
,
ComponentA
ComponentB
ComponentC
]
Although this is syntactically valid, this placement of the comma is likely to rub many users the wrong way. So my idea was to pick a different token as a separator, one which wouldn't trigger this kind of punctuational uncanny valley. I don't really care what that separator is, I just picked some pre-existing token that is most often placed on a line by itself, which happened to be the markdown page break.
At first glance it seems that you might be trading horizontal space for vertical: that is, you are making the lines shorter, on average, with fewer nesting levels, at the cost of making the code somewhat taller; however when you factor in the extra lines needed for the ending paren (which often occurs on a line by itself), the result is a wash.
The issue with the comma positioning can also be brought up in reverse, no?
Children [
ComponentA
ComponentB
ComponentC ---
ComponentA
ComponentB
ComponentC
]
I don't think there is a better and/or more recognizable token then , for separating a set of things.
It is true that the parser doesn't prevent this, we would create usage guidelines that discourage this. At some point we have to rely on users having reasonable aesthetic sensibilities.
I know that you weren't in favor of my original proposal, in fact I think yours was the single negative vote on my post. (I don't mean this as a dismissal).
I do agree though, that for the easiest view of separation, having a new line with a token, whatever it may be, will provide that the best.
It would give you rough ideas about a file at a glance too.
Horizontal space = hierarchy/relation complexity
Vertical space = entity/data complexity
Trivia: the same token that markdown uses for page breaks is used in yaml as a document separator
You could also represent this as app.add_systems(Startup, scene.pipe(scene_spawner)) which reuses piping, instead of adding a bespoke method to any system that happens to return a scene?
Nvm I don't think the piping would support that generic function.
I'm very in favor of the goal to reduce indentation / improve terseness + legibility, and I think the --- proposal is decent at accomplishing this. I'd like to table the syntax convesation until after 0.20 is out, as there are a number of (potentially) related issues to solve:
#Namesyntax ... inner(#Name, OTHER_COMPONENTS)vs outer#Name(OTHER_COMPONENTS)- Top level "shared scene entity" syntax
- Top level "flattened scene" syntax (which might just be (2))
- Inline asset syntax (which might just be (2))
- Child scene ergonomics (
[], my<>proposal, etc)
My only real issue with --- is that it feels like syntax overkill. Feels too much like "drawing a picture in ASCII" to me, which violates my aesthetic sensibilities.
But I do think it does a better job than , in this context
for 2 I do hope whatever it ends up being, doesn't only support Assets. (Even if that is the main use case for the moment π )
My goal is for this to be harmonious / for inline asset support to derive from it
For some more context, this is what I'm working on. I was annoyed at the default ui of CKAN which is a mod manager for KSP so I decided it would be a good opportunity to experiment with bsn and feathers to make a wrapper for CKAN
Each row is a child of a container inside the table
I have my whole hierarchy working with bsn, except the spawning rows part because they are generated at runtime and I can't figure out how to do it inside bsn
I will say, other than this minor annoyance it's been pretty nice so far
Could you instead just spawn the parent entity, spawn all the children scenes with ChildOf baked in, and then apply_scene to setup the rest of the parent?
I guess that might not work if your parent entity is not the root of the BSN actually...
It's probably because I'm tired but I honestly don't understand what you mean π
I'm just thinking of something like this when you're not using BSN:
let parent = commands.spawn_empty().id();
let child = commands.spawn((Blah, ChildOf(parent))).id();
commands.entity(parent).insert(MoreStuff);
Just like manually spawning each individual child
Ah, well, yeah, I can do this without bsn, the issue is doing it with bsn. I had it working kinda like that before trying to use bsn
I have essentially this thing:
let mut ui_root = commands.entity(*ui_root);
ui_root.despawn_children();
let rows = result.list.clone();
ui_root.queue_spawn_related_scenes::<Children>(bsn_list! {(
Node {
flex_direction: FlexDirection::Column,
flex_grow: 1.,
}
Children[
// Header
(
Node {
height: Val::Px(LINE_HEIGHT),
width: Val::Percent(100.),
}
:table_header
),
// Row + Scroll
(
Node {
flex_grow: 1.,
flex_direction: FlexDirection::Row,
min_height: Val::Px(0.),
}
Children[
(
ContentPane::new(rows.clone())
Node {
flex_grow: 1.,
height: Val::Percent(100.),
flex_direction: FlexDirection::Column,
overflow: Overflow::scroll_y(),
}
),
:scrollbar_track
]
)
]
)});
And the only way to make it work is with an On<Add, ContentPane> observer
I was expecting to be able to do some kind of SpawnIter(rows). Where rows would be a Vec<Scene>
Or something like that
My prior suggestion was to call queue_spawn_scene in a loop, get the IDs of those, and the somehow pass that into something like Children to just add those Entitys
So don't have BSN do the spawn. Spawn all your rows, and then just have BSN glue it together
That might also be non-trivial though lol
Also, in the process of working on this I found an issue with how tuple struct are handled. I have no idea if it's already a known limitation though.
struct Foo(f32);
let number = 42.0;
// This leads to a compilation error on the .clone() saying it's expecting a parenthesis
bsn!( Foo(number.clone()) )
Of course that specific example is pointless, but it's just a minimal example
I think my observer trick is simpler because I can use bsn almost everywhere except for the glue part. It's not that hard to setup either. Anyway, the reason I was talking about it isn't because I couldn't find a solution and more because I was surprised there didn't appear to be a way to do it while inside bsn. I think there's probably a way to make something relatively generic that could work here
I think I had a way to do this with icbinbsn, and the template structures are not too different at this point
I do think a way to run arbitrary code that produces a scene list inside of a bsn macro seems quite useful
The virtual_keyboard.rs widget in feathers does "grid spawning" in BSN. Does that meet your needs?
It essentially does this
Currently for Scene repeatability reasons it requires the collect() at the end. But I believe I'm at the point now where my "run once Scene" impl is viable, which (among other things) should allow iterators directly (provided they are owning iterators)
We dont yet have sugar for iterators / conditionals in BSN, but thats pretty high on my list in 0.20
"Run Once" scenes draft is out. I'm specifically looking for feedback from @rare whale (how this relates to bevy_reactor) and @steel oak (how this relates to .bsn)
https://github.com/bevyengine/bevy/pull/23880
I'm working on "hoisting out" expressions from the patch closures, which further reduces the constraints (ex: Send + Sync + 'static is no longer required for inputs, ex in the feathers label widget).
/// Workaround 1 (used in Feathers)
fn label(text: impl Into<String>) -> impl Scene {
let text = Text::new(text.into());
bsn! {
template_value(text)
}
}
/// Workaround 2
fn label(text: impl Into<String> + Send + Sync + 'static) -> impl Scene {
bsn! {
Text(text)
}
}
/// This now works! No workaround necessary!
fn label(text: impl Into<String>) -> impl Scene {
bsn! {
Text(text)
}
}
I have an initial working impl. Just need to do some cleanup
@rare whale you will probably like this π
This is because arbitrary rust expressions aren't supported yet in field-patch-value-position. Its something I believe we can/ should support, but it will require some additional parsing smarts
In the interim, you can resolve this category of problem using rust-expression syntax: Foo({number.clone()})
Just confirmed you can now do this / parameters now don't need to be owned:
fn label(text: &str) -> impl Scene {
bsn! {
Text(text)
}
}
That is excellent
This will help going forward, but I probably won't have that much effect retroactively, as I've already added clone in a bunch of places, and it won't be easy to find them after the fact.
I just looked and yeah, that's exactly what I needed
Iβve done a pass in Feathers and thereβs pretty much nothing. I can do a pass in bevy_reactor too. Iβm most curious about whether bevy_reactor relies on repeatability for anything
Ex: FnMut vs FnOnce
@split harness Something else: we've discussed the boilerplate generated by the need to impl both Scene and Template in all of bevy_reactor's primitives. This causes a lot of repetition - the same code, with minor variations, appears about 8 or 9 times. However, all of that boilerplate is inside bevy_reactor's core, and doesn't affect the DX of callers, so it's not a huge deal. Still, it would be nice to have a more concise way of doing those sorts of things.
Yeah it will be awhile before reducing that boilerplate is my priority. Lots of more important things atm π
Are we going to try to get the #Name in arbitrary rust expressions working before 0.19, or is that a 0.20 aim
I don't think it does currently. For example, look at switch:
switch(|cx: &Cx| *cx.resource::<State<GameState>>().get(), |cases| {
cases
.case(GameState::Play, || bsn_list![Text("Playing")])
.fallback(|| bsn_list![Text("Not Playing")]);
}),
All of the bsn_lists are in closures, so they are strictly run once.
It might be worth experimenting to see if we can pull them out of the closure
It's a trade off: what resources are we willing to spend for branches not taken?
You know where I stand on this generally. If you init it all structurally, we can surface the dependencies to the root
Ok I've wrapped up hoisting. Once we merge "run once scenes" I'll put it out, as it relies on that to actually be useful
@steel oak looking at the .bsn impl I do think it will need some minor changes. The BSN ast is repeatable, so the big loss with the current "consuming" approach is that it unnecessarily consumes the loaded AST scene.
In practice I'm not sure if this is a problem. If it is, we can always just add a variant to ScenePatch
OK, that's fine
With the introduction of BSN, do we still need BundleEffect? It was used to spawn hierarchies as part of a bundle, but thatβs handled by scenes now, right?
Or are there any other use cases for BundleEffect?
I've gone back and forth on this. I think BSN reduces the need for bundle effect / it does solve the same problem, and it would certainly clean things up a bit to remove it (including making nested dynamic bundles much easier to implement). However I suspect a lot of people would still miss the ability to define children![] in their bundles. I think it really depends on how prolific BSN usage becomes / if anyone still uses children![] in practice
Or are there any other use cases for BundleEffect?
Yes, but not ones we want to entertain. Things like theobserve()bundle are alluring to developers, but I've intentionally pumped the breaks on that pattern because it confuses what a bundle is (asobserve()is decidedly not a component)
(and the plan is to deprecate observe())
In other words: BundleEffectexists solely to enable spawning related hierarchies in Bundle, it was largely created to fill the void of bsn!, I'd love to deprecate it, but I'm worried it would be disruptive to peoples' existing workflows
We should just deprecate it IMO
It will suck more to do later
Probably in 0.20
is it expected that the majority of spawn/etc usage, even for single-level tuples/small bundles, becomes bsn! in the future?
for ex:
commands.spawn((A, B, C))
vs
commands.spawn_scene(bsn! { A B C })
I think this is the big question. I suspect that bsn! will increase in prevalence, both because it is more capable and because we are likely to adopt it broadly in our learning material. But I'm not sure aggressively pushing people into it is the right move
I'm not sure this is true. It will get less painful for people as bsn! adoption increases and as the bsn! workflow becomes more capable and bulletproof
yeah, makes sense. It feels more on the viral side of things at the moment, and will be interesting to see adoption through 0.19's cycle
Thanks for the clarification!
I was just curious what to use as a user. I'll probably use scenes for hierarchies.
Right, that's very fair. I would expect rapid bsn! uptake, but there's definitely a hill to climb first
It mainly blocks the internal cleanups and the "dynamic bundle of bundles" work, both of which are aren't critical
just nice
aren't critical?
yup π
I think my vote is lets defer the decision until 0.21 when we've improved BSN a bit and people have started to use it in practice
I've resolved feedback on "Run Once Scenes" and transitioned it out of draft state (as both @dreamertalin and @pcwalton have said this pattern is fine)
https://github.com/bevyengine/bevy/pull/23880
@thick slate I also resolved your comment
Ah wait back to draft I forgot about the internals cleanup I wanted to do π
Ok the cleanup is done. Now it is ready for review / merging π
@thick slate
BSN example ports are starting to look mighty tempting once everything in flight merges π
RA seems to have a seizure every startup
however the error clears on a rerun.
Weird I don't hit this. It looks like this is a BevyManifest+macro related issue and is very likely unrelated to bsn! itself.
Presumably you are consuming Bevy from the bevy crate?
And not piecemeal crates?
so far its only occured with the bsn usage
yes
im in a top level bin folder on main for the launcher work
And it is looking for bevy_scene, implying that RA is somehow not finding the bevy crate in the current cargo.toml file when it hits that error
Perhaps i need to add required deps to the bin definition?
No bevy_scene vs bevy::scene imports are handled at a level above feature resolution (parsing the cargo.toml file)
Can you send a link to the cargo.toml file for the bevy_launcher code?
https://github.com/Atlas16A/bevyEditor/tree/launcher/bins/bevy_launcher
I just updated my branch with current code feel free to take a look
run command is
cargo run -p bevy_launcher
I suspect that this is a product of the[[bin]] pattern / this is somehow throwing off BevyManifest
I bet if you treated bevy_launcher as a standalone workspace it would work as expected every time (after a cargo clean)
Opening VScode directly on the bevy_launcher folder and running it with cargo run might be enough to test this
the question then is can it be added to main like that? i imagine ci makes assumptions about it being a single workspace
If it compiles and runs then this is just a Rust Analyzer problem
interestingly the actual import doesnt have the resolve error
I don't find that particularly interesting, given that you have imported the bevy crate
This is a "macro resolution type path hack" issue, not a "rust import issue"
More specifically it is likely a "Rust Analyzer macro resolution type path hack issue"
When the macro is being expanded by Rust Analyzer, it thinks it is operating in an environment that does not have access to the bevy crate / it assumes it must rely on bevy_scene
And actually, I believe this is in-line with how all the bevy examples run. Notice the #[dev-dependencies] imports of crates like bevy_ecs and bevy_scene, despite the fact that the examples all use bevy
I believe this was a tradeoff we made to make the BevyManifest logic work as expected everywhere else, with the Bevy project itself eating the "boilerplate" of providing manual bevy_x imports to make the examples work
Before you blame us for this nonsense, instead blame Rust's proc_macro implementation for not giving us a $crate equivalent.
wasnt really blaming anything i simply dont know enough about the resolution to guess at a cause
should i add dev-deps to my cargo or just ignore it but running cargo within just the launcher folder?
Yeah sorry didn't think you were. Just trying to educate in a dramatic fashion π
ahh ok
It looks like you're hitting this in the laucher code itself (not a launcher example). So in that case, you might be able to suppress it by adding bevy_scene to the normal cargo deps in the launcher
I believe that would largely be a no-op from a compilation perspective, and it would probably trick RA
Although this feels like an RA bug, provided that cargo run --bin bevy_launcher works as expected
intially that command was failing on feathers crate resolution lol
And then you added the required feature and it worked, presumably?
mhm
Then thats unrelated. If the cargo run --bin bevy_launcher builds, then this is an RA issue
And if this is reproducible for other people, I'd be cool with adding a bevy_scene import to the launcher cargo.toml to fix mitigate it
Just add a comment on it explaining the situation. And ideally file a bug on the RA repo explaining the situation / providing a link to the offending code
(and link to the bug in the comment)
adding that import doesnt seem to stop it, i think i will file a issue with RA since we've exhausted the ideas for fixes
Make sure you do a cargo clean and restart VSCode
i really should have a auto clean going my poor ssd
Yeah doing upstream Bevy development is definitely not nice to your SSD
I probably write something like 100 GB / day from bevy builds (maybe more)
how would i get the contents of a resource deep into a bsn! ?
without passing it through every layer
env!("CARGO_PKG_VERSION");
trying to use this to make a text node show the launcher version but ran into issue of not being able to have the macro in it so i tried storing in a resource but now i dont see how to access that resource
nvm
Yep def a RA bug it just changed to all bevy proc macros failing to be expanded by RA
Is scene inheritance with generics (:scene::<T>) not supported?
are the examples going to be ported to bsn in 0.19? Also, will there be any examples on the bsn usage itself?
Pretty sure the answer is no atm. However I have some changes I'm working on that might (accidentally) add support for that
This would be very easy to fix yourself if you're feeling inclined. Just a path parsing thing
yeah, I'll take a look then
bevy_scene/macros/src/bsn/parse.rs is the spot
Can i (if yes, how can i) use Sprite in bsn? Can you show me any example?
Just this?
bsn! {
Sprite{
image: "test.png",
custom_size: vec2(100.0, 100.0),
}
};
Or do you mean something else?
Thank you. It looks easy(?). I might have missed something. I'll retry. Can i get back to you?
Just replaced Ident with Path: https://github.com/bevyengine/bevy/pull/23902
The trait bound on the generic for the function ends up being quite complicated though... Works well for generic on
Oh i thought I should assign Some(vec2(100.0, 100.0)) to custom_size for some reason. Thanks a lot!
You can do that using {Some(vec2(100.0, 100.0))}, but in this case Into transforms Vec2 into Some(Vec2), so it works without it.
This needs another review / is blocking my "expression hoisting" PR:
https://github.com/bevyengine/bevy/pull/23880
Approved!
given how close we are to release i actually would rather not merge any functionality changes to bsn π, but this is an ergonomics win so...
but repeatability might come in handy in some ways that we can't quite expect, i think the repeatability of scenes was actually very forward looking and idk how i feel about removing that. Could you elaborate on what you mean by inheritance caching making this a moot point?
I think that this is semantically important to get right from the start
Yeah as @rare whale pointed out, the implications of removing the constraint are hard to identify and remove after the fact
This should land in 0.19
π ig so
@split harness reviewed
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, scene.spawn())
.run();
}
fn scene() -> impl SceneList {
bsn_list! {
Camera2d,
Sprite {
image: "branding/bevy_bird_dark.png"
},
}
}
Sprite example is so minimal π
Yeah that removes a lot of friction
There's a saying that "quality beats quantity, but sufficient quantity has a quality all it's own". Similarly, I think that if you remove enough friction, you eventually get a qualitative shift in how patterns get used. An example is the difference between Python and JavaScript: lambda functions in Python have extra syntactical friction, so you don't see a lot of callback-based APIs, whereas in JavaScript they are everywhere.
@rare whale christmas is coming early for you π
:FeathersButton {
caption: {bsn_list![
(Text("Center") ThemedText),
]},
corners: RoundedCorners::None,
}
Things to note:
- Struct-style scenes
bsn_list!doesn't require an explicit Box anymore
I'd also like to get those pesky {} out of the way for bsn_list! but we'll see
bsn_list! doesn't require an explicit Box anymore
This isn't any specialbsn!magic. Just realized that while we can'timpl<L: SceneList> Into<Box<dyn SceneList>> for Ldue to rust constraints, we can implementimpl<L: SceneList> Into<Box<dyn SceneList>> for SceneListScope<L>, which is the typebsn_list!returns π
you could pretty easily carve out an exception for anything that is an ident followed by an exclamation point, and assume it's an expression
any macro -> expression automatically
For sure. Also solved implicitly if we add support for arbitrary rust expressions inline
But the carve-out is certainly easier
is the latter on the table? owo
Yup. Should be possible ... just a challenging parsing problem
i c i c....
oh, btw, for the "use #Name in expressions" stuff, aren't we going to have to modify the FromTemplate derive to add some sort of datastructure to keep track of the ScopedEntities?
we need some place to store them, we can't just put them in a trait π€ but this would break normal FromTemplate on data structures which we can't easily control
OH wait, hm, if we took every auto-generated expression, and instead made it not only a closure, but a tuple of closure and our entities we want to resolve later..... ?
we won't actually be able to do this
Anything involving adding "state" to FromTemplate is almost certainly a dead end
if we want to use #Name
yeah π€
i can't concieve of how else to store the ScopedEntities for later resolution
i cheated and just added a field alongside my closure to allow for it
In my mind this problem is basically "do normal rust expression parsing for everything but #Name, which will resolve to the relevant EntityReference in the expression"
Also for "patches", although we might actually be able to defer to the Rust expression parser for those, then operate on the outputs retroactively
pub struct AsyncClosureWrapper2<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + Clone + 'static>(
pub T,
pub Vec<usize>,
);
pub struct AsyncClosureWrapper<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + Clone + 'static>(
pub T,
pub Vec<ScopedEntityIndex>,
);
impl<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + 'static + core::clone::Clone> Scene
for AsyncClosureWrapper2<T>
{
fn resolve(
&self,
context: &mut ResolveContext,
scene: &mut ResolvedScene,
) -> bevy_ecs::error::Result<(), ResolveSceneError> {
let closure_wrapper = AsyncClosureWrapper(
self.0.clone(),
self.1
.clone()
.into_iter()
.map(|e| ScopedEntityIndex {
scope: context.current_entity_scope(),
index: e,
})
.collect(),
);
scene.push_template(closure_wrapper);
Ok(())
}
}
impl<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + Clone + 'static> Template
for AsyncClosureWrapper<T>
{
type Output = AsyncTaskThing<T>;
fn build_template(
&self,
context: &mut TemplateContext,
) -> bevy_ecs::error::Result<Self::Output> {
let mut entities = vec![];
for scoped_entity_index in &self.1 {
entities.push(context.get_scoped_entity(*scoped_entity_index));
}
let async_ctx = context.resource::<Ctx>().clone();
let mut closure = self.0.clone();
let (tx, rx) = async_channel::bounded(1);
AsyncComputeTaskPool::get()
.spawn_local(async move {
let mut future = closure(async_ctx, entities);
futures::select! {
_ = rx.recv().fuse() => {}
_ = future.fuse() => {}
}
})
.detach();
Ok(AsyncTaskThing::<T>(PhantomData, tx))
}
}
this is how i did it to allow for the async |world: Ctx| {} with #Name there π€
yeah π€
that makes tentative sense to me
Yeah this impl is what I expected to see
How are you planning to get around it π
No real getting around it. Its more about standardizing the pattern
Which I haven't fully taken to its conclusion yet
let me know when you've finished cooking
we could wrap every FromTemplate a second time, with the entities it needs, and dispose of that during resolution
Cart's a big fan of perpetual stew π
Nice! Should we migrate the feathers widgets before 0.19?
Also, does this permit struct fields to be initialized to #Name values?
That's what is holding back scrollbars, since we need a way to tell the scrollbar which entity it's controlling
Closed that one as well. Thanks!
Yeah I'd like to. I've migrated some of them already. Still some missing features, such as :{self.some_scene_to_inherit}
^ needed for things like tool_button
We can work around the tool_button case if needed
I can think of 3 different ways to avoid that particular syntactical construction
Children [
(#Minus
button(ButtonProps::default())
Children[(Text::new("-1") ThemedText)] ),
(#Counter
Text::new("0")
ThemedText
Node { margin: UiRect::horizontal(px(10.0)) } ),
(#Plus
button(ButtonProps::default())
Children[(Text::new("+1") ThemedText)] )
]
I've been messing around with different ways of formatting bsn, and i've quite come to like a lisp-style #Name as identifier, then the rest of the sibling components indented, i'm honestly glad we don't have auto-formatting yet, it's letting these experiments here percolate.
Here's how it would look with my proposal:
Children [
#Minus
button(ButtonProps::default())
Children [
Text::new("-1")
ThemedText
]
---
#Counter
Text::new("0")
ThemedText
Node { margin: UiRect::horizontal(px(10.0)) }
---
#Plus
button(ButtonProps::default())
Children [
Text::new("+1")
ThemedText
]
]
I do like the dashes
Children [
#Minus
button(ButtonProps::default())
Children [
Text::new("-1")
ThemedText
]
---
#Counter
Text::new("0")
ThemedText
Node { margin: UiRect::horizontal(px(10.0)) }
---
#Plus
button(ButtonProps::default())
Children [
Text::new("+1")
ThemedText
]
]
Here's dashes plus tabbing
idk if it does much
OH wait
very reminiscent of the form #Name(components) which i think has been suggested before as well (iirc it's basically what flecs does)
Which format you prefer may depend on what you are using BSN for - UI tends to have deeper hierarchies, which means that you get a lot of indentation, and things get crowded up against the right margin, whereas for typical 2d and 3d work, you don't generally get that many nested levels.
Children [
---#Minus
button(ButtonProps::default())
Children [
Text::new("-1")
ThemedText
]
---#Counter
Text::new("0")
ThemedText
Node { margin: UiRect::horizontal(px(10.0)) }
---#Plus
button(ButtonProps::default())
Children [
Text::new("+1")
ThemedText
]
]
But idk π€
i feel like there might be something here
yeah, that's part of why i really like the dashes
i like how it's flat, but i also just like it asthetically a lot
@split harness So there's a bit of friction around asset handles in nested structs. The magical conversion that you get with FromTemplate only extends to properties of that type. For something like TextFont (a common case) the handle is inside the FontSource enum:
pub fn label_small(text: impl Into<String>) -> impl Scene {
bsn! {
Text(text)
template(|ctx| {
Ok(TextFont {
font: FontSource::Handle(ctx.resource::<AssetServer>().load(fonts::REGULAR)),
font_size: size::EXTRA_SMALL_FONT,
weight: FontWeight::NORMAL,
..Default::default()
})
})
PropagateOver<TextFont>
ThemeTextColor(tokens::TEXT_MAIN)
}
}
Here we have to use template(|ctx|) so that we can acquire the handle
Iβm pretty sure you donβt need that wrapper anymore
At the very least itβs easy enough to slap FromTemplate on TextFont if itβs missing
It already has FromTemplate
However, the handle is inside of FontSource.
Which isn't a component
Children [
---#Minus---
button(ButtonProps::default())
Children [
Text::new("-1")
ThemedText
]
---#Counter---
Text::new("0")
ThemedText
Node { margin: UiRect::horizontal(px(10.0)) }
---#Plus---
button(ButtonProps::default())
Children [
Text::new("+1")
ThemedText
]
]
oooo it would be interesting to have the option to surround it
So you could either surround it with dashes, or just have 3 dashes.
i also ran into this issue earlier
I think that runs afoul of cart's ASCII art complaint

This came up because I'm experimenting with a dynamic style component that contains a vector of ImageNode:
/// A single rule in the list of style rules.
#[derive(Clone, Default)]
pub struct StyleRule {
pub pattern: StatePattern,
pub background: Option<ImageNode>, // None = render nothing for this state
}
/// A component which updates the style of an entity based on the widget states (disabled, pressed,
/// hovered, and so on)
#[derive(Component)]
pub struct StatefulStyle {
pub source: StateSource,
pub rules: Vec<StyleRule>,
}
That is, I can convert a path to a handle when the ImageNode component is used in component position in the BSN syntax. But I'm guessing that won't work if I am using ImageNode as a sub-field of a larger component.
(The basic idea here is a component which looks at the widget states, and then updates the background image in response to changes in those states. It's doing what feathers does, but in a much more data-driven fashion rather than using bespoke conditional logic.)
This is laying the foundations for my "bring your own artwork" widget collection idea.
feel free to adapt/experiment, although I wouldn't recommend coming up with a special formatter, Id stick to what works and possibly add rules/config later.
No need to reinvent the wheel imo. Experimentation happens naturally with a usable state and accessible api imo (both consumers and devs), i.e. tooling gets created because people use something and the code is extendable/accessible, not because its hidden and encapsulated away and doesn't have formatting. In my experience it has been the opposite typically.
Nesting FromTemplate impls should absolutely work
Check out the FontSource usage in examples/bsn.rs
Notably, the only reason the wrapper is necessary is because the Into conversion doesn't exist. I remember hitting this before and not adding the Into for the asset handle template because for non-template FontSource, it has From<&str> for FontSource::Family, not the asset
So it would be weird for strings to be "asset paths" for templates and "families" for non-templates
This is not the underlying problem
I would like to stress that nesting templates does indeed work as expected
Yeah we definitely want a solution to the nesting, and I don't think formatting rules will be enough. I think a round of syntax refinement will be in order
(but im still not ready to embark on that quest)
Ah, I see how it works, thanks π
OK I've refactored the label code: https://github.com/bevyengine/bevy/pull/23927
Removes the complex template call and replaces it with the simpler BSN asset helper template.
What will happen to the old bevy ui system /syntax ?
there's not really a "old" vs "new" one, it's largely the same types and principles. It's just the scene format is designed around making composing them easy & compact.
.spawn(/* Bundle */).with_children(|parent| { /* procedural spawning logic */ }) is sticking around. It's just very gnarly syntax once things get complex.
yes i ment this
template_value + FromTemplate types caused a bit of issue on this PR btw https://github.com/bevyengine/bevy/pull/23924
@split harness Once again I'm pushing the envelope, I'm trying to figure out the best idioms for embedding a bitfield in a bsn scene. I have something that looks like this:
bitflags! {
/// Bitflags representing the possible states of a widget
#[derive(Clone, Copy, Default, PartialEq, FromTemplate)]
#[repr(transparent)]
pub struct WidgetState: u32 {
const DISABLED = 1 << 0;
const HOVERED = 1 << 1;
const PRESSED = 1 << 2;
const FOCUSED = 1 << 3;
const CHECKED = 1 << 4;
}
}
The intended use case is something like the following:
bsn! {
Node {}
Button
Hovered
EntityCursor::System(SystemCursorIcon::Pointer)
FocusIndicator
StateSource::SelfState
States [
StyleRule {
pattern: StatePattern {
mask: WidgetState::DISABLED,
required: WidgetState::DISABLED,
},
background: ImageNode {
},
},
StyleRule {
pattern: StatePattern {
mask: WidgetState::PRESSED,
required: WidgetState::PRESSED,
},
background: ImageNode {
},
},
]
}
So essentially the widget has a relation which enumerates the various visual states, and has a bitmask pattern which is matched against the current state of the widget
However, I get the trait bound WidgetStateTemplate: std::convert::From<WidgetState>is not satisfied
@split harness OK so I'm confused about a bunch of things. My main problem is that while I have a bunch of new tools (Template, FromTemplate, etc.) I don't have a good sense of which tool to apply for a given problem.
In addition to the above issue I mentioned (templating for bitflags), I'm also seeing some other things that I don't understand:
- In the above example, you can see how I assign an
ImageNodeto thebackgroundProperty, however the actual type ofbackgroundisOption<ImageNode>. I don't know why this compiles; I would think that I would have to wrap it in aSome(). Does BSN automaticaly coerce options? - It still thinks that the type is
Option<ImageNode>, I can't add any properties to it:ImageNode { color: Color::WHITE }doesn't compile becauseOptionhas noColorproperty. - Explicitly wrapping it in
Somedoesn't compile, the error I get is "try using the variant's enum:core::option::Option,std::option::Option" - Changing it to
OptionTemplate::Some(ImageNodeTemplatedoesn't work, because when I try to add..default()I getexpected identifier:
background: OptionTemplate::Some(ImageNodeTemplate {
color: Color::WHITE,
..default()
}),
I'm basically just flailing around without really understanding what I am doing.
Did you read the crate docs I added?
I've just looked at the docs for FromTemplate and Template.
Definitely recommend the crate docs. Not exhaustive, but I've made it my goal to help provide that sort of map.
One quick question: can a type be its own template? Like if I have a primitive newtype like Velocity(f32), and I don't want to use the auto-derive for some reason, do I need to define a VelocityTemplate?
The reason I ask is because, if I have a bitfield with a bunch of constants, I'd have to duplicate all those constants in the template type.
Related to this: I have no idea whether reflection works with bitfields, I'm guessing it probably doesn't.
I think for the latter the answer is "not very well"
It probably just serializes as an int
aren't bitfields already part of bevy's core dependencies? then it could just do a proper reflection
Note that I don't have to use a bitfield here - but it's the most efficient representation for what I am trying to do. The question is, what's the best way, in BSN, to represent a closed set of items?
An enum?
Although I guess an enum doesn't easily support AND semantics like an bitfield does.
Yeah, we could and maybe should improve support here
unless there is a noticeable chance bevy gets rid of bitfields some day
In the above example, you can see how I assign an ImageNode to the backgroundProperty, however the actual type of background is Option<ImageNode>. I don't know why this compiles; I would think that I would have to wrap it in a Some(). Does BSN automaticaly coerce options?
Field values have implicit into(). Same reason "some string" works for String.
It still thinks that the type is Option<ImageNode>, I can't add any properties to it: ImageNode { color: Color::WHITE } doesn't compile because Option has no Color property.
Explicitly wrapping it in Some doesn't compile, the error I get is "try using the variant's enum: core::option::Option, std::option::Option"
This is an unfortunate corner case. ImageNode has custom template logic, meaning you need an OptionTemplate, not the normal Option: https://github.com/bevyengine/bevy/pull/23699
Changing it to OptionTemplate::Some(ImageNodeTemplate doesn't work, because when I try to add ..default() I get expected identifier:
You shouldn't use ..default() in "field value" position in BSN. Although perhaps we should add support for it just to make migration easier.
It still thinks that the type is Option<ImageNode>, I can't add any properties to it: ImageNode { color: Color::WHITE } doesn't compile because Option has no Color property
Hmmm I'll want to investigate this one.
We definitely want bitflags to work in BSN. Ideally you can bring whatever types you want to the party. I'll investigate
Just remove the FromTemplate derive here and rely on the auto-impl for Default + Clone. Bitflags does a lot of codegen to make this all work by default that the template generation code has no knowledge of
This works:
#[derive(Component, FromTemplate)]
struct Widget {
state: WidgetState,
}
bitflags::bitflags! {
/// Bitflags representing the possible states of a widget
#[derive(Clone, Copy, Default, PartialEq)]
#[repr(transparent)]
pub struct WidgetState: u32 {
const DISABLED = 1 << 0;
const HOVERED = 1 << 1;
const PRESSED = 1 << 2;
const FOCUSED = 1 << 3;
const CHECKED = 1 << 4;
}
}
fn widget() -> impl Scene {
bsn! {
Widget {
state: WidgetState::DISABLED
}
}
}
I'm going to undo a lot of this. Manual FromTemplate derives are unnecessary for anything that doesn't require custom template logic. Letting a type be a template for itself is less confusing / has fewer corner cases and does less codegen
This is a good sign that we should stress this in the learning material. "Your types should just use Default + Clone in BSN unless they need custom template logic"
Yeah that makes sense, I was just coming up against "these aren't working no matter what I do"
was only after the merge I realised the "basic" criteria.
"FromTemplate is the magic" is maybe a " #1264881140007702558 lurker" brainworm
what if the third party needs custom template logic but the type does not derive FromTemplate? Is that a use case thing?
If that type is a field on a type you own, you can do this:
struct MyType {
#[template(MyCustomTheirTypeTemplate)]
their_type: TheirType
}
bsn! {
MyType {
their_type: TEMPLATE_HERE
}
}
If it is a top-level component that you can't derive FromTemplate for, you can do this:
bsn {
template(|context| {
TheirType {
// init here
}
})
}
oh this is just neat
One rather annoying limitation is that "enum variant shorthand" imports (ex: use MyEnum::Foo) don't work in BSN. This is because we cannot disambiguate between Foo(x) (enum variant) and Foo(x) (tuple struct)
These behave differently / have different requirements when it comes to field access
So Some on the right hand side does not work. It requires Option::Some. We could special case built in types based on their name, but that creates nastiness when there are naming conflicts (ex: a user has a custom Some type/enum variant in scope), and doesn't properly prepare people for the non-hacked cases
The reason you can't skip OptionTemplate::Some is that you don't get implicit into() for anything that is in "patch position", as we aren't assigning a full value. Instead, we are setting a field using the whole type path :
#[derive(Component, FromTemplate)]
struct Widget {
#[template(built_in)]
image_node: Option<ImageNode>,
}
fn widget() -> impl Scene {
bsn! {
Widget {
image_node: ImageNode {
flip_x: true
}
}
}
}
The relevant expanded bsn!:
<Widget as bevy_scene::PatchFromTemplate>::patch(
move |value, _context| {
// image_node is an OptionTemplate, which doens't have a flip_x field
value.image_node.flip_x = true;
}
));
Implicit into() is only for "terminal values", such as literals, rust expressions {}, function results, etc
However you are absolutely hitting something that I think needs "fixing". All of the "rules" I've taught you so far would result in you (and anyone else) expecting this to work in this case (even I did):
background: OptionTemplate::Some(ImageNodeTemplate {
color: Color::WHITE
})
Sadly for reasons (which I will explain in a second) this does not work. The most satisfying workaround at the moment is:
bsn! {
Widget {
image_node: {ImageNodeTemplate {
color: Color::WHITE,
..default()
}}
}
}
However given the already complex interplay of rules, this type of exception to the rules should not exist imo
This "consistent with the rules as expressed" impl doesn't work:
background: OptionTemplate::Some(ImageNodeTemplate {
flip_x: true
})
This is a consequence of a tradeoff being made for enum support, combined some incorrect logic:
- "Root level" enums are expected to implement VariantDefaults, and resolve to "enum patches". VariantDefaults defines a default constructor for each variant type, which enables the patching behavior. This allows enums to "layer on top of each other" and skip fields in BSN in the same way that structs do. If
OptionTemplatewas a "root template" (aka it produced a component), then this would all work as expected. - Anything that isn't a "root level enum" is expected to express every field on the enum / the patching behavior isn't used. The parameter inputs are parsed as a BsnType (aka things like
..default()are not allowed), but the fields are dumped as if they are full expressions of the type. This is flat out wrong. It isn't doing the implicit Default / patch-on-top logic and it isn't letting you express a Rust expression inline (like you might for function parameters)
This is the current (broken) output for the bsn! above:
move |value, _context| {
value.image_node = OptionTemplate::Some(ImageNode { flip_x: true });
}
This should be sorted before 0.19 / I'll start coming up with a fix
@rare whale thanks for catching this case π
A type can be its own template, however Template also requires Default. So the only way you wouldn't have the auto-impl is if you couldn't implement Clone. But Template requires some way to implement Template::clone_template, so if you're doing the manual Template impl you might as will skip that and do the manual Clone impl
(or just derive Clone, which is usually doable)
@thick slate FYI for any future FromTemplate derives: #1264881140007702558 message
Okay π
Doesn't the "per-enum variant default" stuff require FromTemplate specifically?
Yeah for any top-level Component type, it either needs FromTemplate or Default + Clone + VariantDefaults
This is not necessary for value-position enums
Okay, super useful. This should be clearly stated in the FromTemplate docs for sure
However that choice is part of what got us into this mess (edit because I forgot link: #1264881140007702558 message)
However I still think it is probably the correct tradeoff, as many value-position-enums are outside of user control / won't have the ability to derive VariantDefaults
I think theres a best of all worlds solution
@thick slate https://github.com/bevyengine/bevy/pull/23940
So, the problem with this solution is that we lose automagic path conversion:
bsn! {
Widget {
image_node: {ImageNodeTemplate {
color: Color::WHITE,
image: "path_to_image",
..default()
}}
}
}
I also tried ImageTemplate::Handle but apparently that does not exist.
@dapper sky
Yup that would sadly require a top level template(|context| {}) to get the asset_server to load the handle manually. Not satisfying. I'll try to get a true fix out asap
Sounds good.
You can see where I am going with this, I hope π
I sure can π
The thing with the bitfields is similar to what a lot of physics engines do - they have a pair of masks representing collision groups
I'll note that the FromTemplate derive on Widget here is completely unnecessary / Default + Clone is the move
Some small PRs:
@rare whale I've made it possible to treat bsn! as a SceneList, which means we can clean up some single-entity bsn_list parameters:
// before
:FeathersButton {
caption: {bsn_list![ (Text("Normal") ThemedText) ]}
}
// after
:FeathersButton {
caption: {bsn!{ Text("Normal") ThemedText }}
}
Hopefully soon to be:
:FeathersButton {
caption: bsn!{ Text("Normal") ThemedText }
}
Good. I'm trying to maintain a consistent policy that "slots are 0..N relationships", whether we are talking about button captions or if/else blocks.
I try never to assume that the "thing that fits here" is a singular entity
can you design a macro that has both foo!{ } and foo![ ] syntax?
All macros support foo![] / foo!{} / foo!() / its up to the caller to choose
Which is imo the wrong call
I'd like bsn_list![] to only work with []
oh I could have done vec!{1, 2, 3} all the time to confuse reviewers you say
Yes, but fortunately for everyone rustfmt will fix this to vec![]
yes... fortunately
I think this is fine for a "project onto children" type of input. I think the exception should be when you expect a specific kind of "thing", where the logic relies on (ex) a specific component to exist
Ex: if you have an Activator { thing_to_activate: SCENE_TYPE }, where thing_to_activate is expected to have some Activate component
In this case, SCENE_TYPE should be Box<dyn Scene>
Or if you want more type safety (at the price of giving up arbitrary scene definitions / extensions), then you could accept Activate directly (ex: if it has a scene associated with it)
@rare whale @thick slate @steel oak Here is my Structs as Scenes / Scene Components proposal (which is largely already implemented):
https://hackmd.io/@_f9uhmy1TIGjwY7XfeFpLQ/H1flFT8Tbx
Forgot to use props.label in relevant place / that is fixed now
This also great for state serialization/deserialization.
In my game I don't serialize every component to save the state. I serialize only necessary and insert the rest via required components or triggers on deserialization.
This should simplify this.
Or this won't spawn the scene with the regular component insertion? π€
This will not spawn the scene with regular component insertion
I feel like having "components are scenes, but only sometimes - look at the definition" is a bit confusing. I like having structs-as-scenes since they're basically functions with defaults that we're missing in rust today, but tying them to components with special syntax for prop fields ("@") feels a bit too magical. I personally would've preferred a special SystemParam to query scenes instead.
Thanks for the clarification, I just got confused by on_insert.
Makes sense ,actually.
"Components are X, but only sometimes, look at the definition" is true for many different kinds of X. Ex: Components are Resources, Components are Assets, Components are Players, Components are Buttons
I do admit the @ is a certain kind of "magic", but I also think it does a good job of making "scene constructor parameters" visually distinct from "patched component fields"
Yeah, that's true, although I think usually you don't see all of the types too close to eachother mixed on the same entity, which is probably what's throwing me off
Mixing component fields with config props fields together in one block also feels a bit wrong, but that's just my first impression of the proposal
I think the competing proposal is:
#[derive(Component, Default, Clone)]
pub struct MyButton {
clicks: usize,
}
pub struct MyButtonScene {
label: Box<dyn SceneList>,
}
impl IntoScene for MyButtonScene {
fn into_scene(self) -> impl Scene {
bsn! {
MyButton {
clicks: 0
}
Children [
{self.label}
]
}
}
}
Where the tie between the types is implicit. I think this is fundamentally worse, as MyButton (the runtime component) is no longer "logically tied" to MyButtonScene. MyButtonScene is not queryable. MyButtonScene is the "user facing" type that people spawn, but they can not interact with it at runtime / it isn't "real". Theres no enforcement that MyButton has the scene logic, even though it is built to be used in that context.
It is also just more boilerplatey / adds more types into the mix
It also makes the "smaller" / simpler cases nastier:
#[derive(Component, Default, Clone)]
struct MyButton;
#[derive(Component, SceneConstructor, Default, Clone)]
#[scene(my_button)]
struct MyButtonScene;
fn my_button() -> impl Scene {
bsn! {
:"my_button.bsn"
MyButton
}
}
Whereas in my proposal you can just do this:
#[derive(Component, SceneConstructor, Default, Clone)]
#[scene("my_button.bsn")]
struct MyButton;
The extra boilerplate is necessary because we want to ensure MyButton exists on the scene.
You can add MyButton manually in my_button.bsn, but that isn't necessary in my proposal, and won't work in cases like my_button.gltf
Whereas my proposal will support this, and it will behave as expected:
#[derive(Component, SceneConstructor, Default, Clone)]
#[scene("player.gltf")]
struct Player;
This does make sense from the marker component perspective if 1 scene == 1 marker, and that does allow to query for entities containing a scene, I'm just not sure how it fits with all the other systems currently in place - required components and function scenes. Maybe also scenes should have more distinct syntax from components (like prefixing with @ instead)?
It just feels like it's becoming a bit too difficult to parse what's going on in bsn! blocks, but maybe I'm just not used to it yet
That will work
what's the motivation for making scene inheritance do double-duty as also being how you call scene constructors? those feel conceptually unrelated to me but that might just be me not having the right mental model here
it's a little scary to me that MyButton { ... } and :MyButton { ... } mean such radically different things despite both saying "i want this to be a MyButton"
I'll add that even I don't fully grasp the difference
And having the textual difference be so subtle makes it hard to mentally track.
Tho maybe that could be solved with syntax highlights rather than syntax changes
Function scenes could still be used in the same way they were before. I suspect that in practice they would largely be used to apply small pieces of additional reusable patches, rather than to spawn "things", as the "type driven" approach is better for pretty much everything. We have long known that "required components" and BSN intersected with each other from a problem space perspective. It would be interesting to try to unify them, but that would require some fundamental changes to how we spawn things
"Scene components" doesn't really fundamentally change the scene vs required components question. It just makes the question clearer.
From my perspective, the :Scene vs Component syntax difference is already clear, and is the most accurate given that :"my_scene.bsn" behaves the same way as :MyScene. Adding new syntax like @MyScene would make things less clear, not more
Them feeling related makes sense to me. MyButton {} is "directly patch the component" syntax. :MyButton {} is "run the MyButton scene constructor, apply the patch on top of the component, and inherit from that scene" syntax.
They are both "MyButton" component patches. :MyButton just does "scene constructor" work too
Additionally, it will have guard rails. If you use MyButton {} in a scene that doesn't do :MyButton earlier, it will print a warning
Put another way, we should only have one "inheritance" syntax, and that is :THING
I believe we could also add some guardrails to prevent spawning scene components via world.spawn() without the full scene. Ex: add a required component that is only set "properly" when spawned with the scene, and warn when it is not
Why does this need to be a separate trait, compared to Template, couldn't you just add the Props to it?
I think the most important reason we should do this: it is what pretty much everyone wants in practice, in my experience. It is essentially a Bevy right of passage to define a Player component and then be like "ok how do I get all of the other entities / components / scenes necessary for Player to function to spawn whenever I spawn Player"
Also, not being able to spawn a component, is a no go.
You can spawn it on its own, it would just warn if you spawn it without the scene that it needs to function properly
For this, I assume its like, Template is the factory for the component-level, and SceneConstructor is the factory for the scene-level, which some components represent.
Also 1 nit about the Component -> Scene, if two components produce the same scene, they will still not be in archetype together, because the scene-producing components themselves would make them disjoint. (Although I suppose the assumption is not for pure-factorys but rather component+friends)
Dont mean to dogpile, but also I assume required components will stick around as the procedural variant, with bsn components being the declarative variant.
huge fan of the feature itself - i've tried out various different forms of this (on_add hooks, required components, On<Add, Marker> observers, bundle functions, right now im trying scene functions w/bsn). a derive macro that reifies a scene function seems like close to ideal for the purposes of attaching a "canonical form" to marker components at the type level
I started from this perspective and tried to make it work. The "problems":
- Not all Templates are scenes / should produce them
- Template can be patched from "anywhere" in the inheritance hierarchy. This makes it impossible to evaluate scenes incrementally and reuse work / cache it, because they could change in arbitrary ways in the future. Scene constructors notably run at a fixed point in the hierarchy.
- This also creates a weird "chicken and egg" problem. An inheriter could write "on top" of a Template, but that influences what it is supposed to be writing on top of.
I see this as very similar to how inheritance is expressed in languages like C#. CoolCollection: Collection and then in the constructor calls the base constructor.
i think the only thing that feels odd to me is marrying it to scene inheritance syntactically and conceptually, when inheritance isn't really something that comes to mind when i think of a spawner function or even constructor
a constructor is "this work must happen to produce a valid X" (to me)
We see this exact pattern when porting the tool_button widget in Feathers to SceneConstructor:
#[derive(Component, SceneConstructor, Default, Clone)]
#[scene(FeathersButtonProps)]
pub struct ToolButton;
impl ToolButton {
fn scene(props: FeathersButtonProps) -> impl Scene {
bsn! {
:FeathersButton {
@caption: {props.caption},
@variant: {props.variant},
@corners: {props.corners}
}
Node {
padding: UiRect::axes(Val::Px(4.0), Val::Px(0.)),
min_width: size::ROW_HEIGHT,
}
}
}
}
The constructor itself isn't really what is "doing the inheritance"
That is the : piece
In theory you can also do this and "skip inheritance":
bsn! {
{FeathersButton::scene(props)}
}
I just opted not to provide sugar for it because that would be ambiguous with Component { foo }
For the syntax, probably not possible, but could we do something like:
:MyButton {
clicks: 10
}(
MyButtonProps{label: bsn!{ Text("Hello") }}
)
Or maybe even drop the MyButtonProps and just named arguments π€
Feel like I want to try to preserve the fact its a function call, and split the state and input more.
The other forms of inheritance normally are function calls, right?
what's the intended approach for component constructor functions (like MyComponent::new) where the component derives FromTemplate? i have a component with entity references so Default doesn't make sense, but deriving FromTemplate makes it all weird
(for ex: bsn! { Projectile::new_with_range(...) } where Projectile has derive(FromTemplate))
You can implement the constructor for MyComponentTemplate. Sadly not much to do about this one as they are fundamentally different types.
In general, if you have a constructor like new_with_range(SOME_RANGE), then that implies that you could be constructring this type without a TemplateContext, which begs the question "why are you deriving FromTemplate in the first place"
Manual FromTemplate impls (and therefore the MyType vs MyTypeTemplate split) are for things that really need the template. And at that point, it is generally best to just use struct init syntax
the projectile carries an Entity field indicating which entity fired it, hence the FromTemplate
Especially given the automatic into, which is another reason people often reach from constructors
So the full constructor is Projectile::new_with_range(entity: Entity, range: Range)?
it's new_with_range(from: IVec2, to: IVec2, speed: f32, range: f32, origin: DamageOrigin) where DamageOrigin contains two entity fields (the weapon and the actor firing it) but that's mostly extraneous to the example
What are the fields on Projectile?
i tried moving the constructor to ProjectileTemplate but then it wants DamageOriginTemplate which is....bleh
pub struct Projectile {
pub velocity: Vec2,
pub range: f32,
pub origin: DamageOrigin,
}
im probably just gonna split out the origin to a new component to sidestep the issue in this case
but not having easy constructors is unfortunate
In general embracing field-driven init is the "easy win". And that notably plays the most nicely with Asset-driven scenes (as they will generally be composed in an editor as field patches)
But yeah if you are using "template types", the constructors will need to be on those templates
Alternatively, if you really need to use the constructor for the "template output" rather than the template, you can always just use template(|_| Ok(Projectile::new_with_range(....))
But that would also require sorting out the DamageOrigin entities manually. If you don't want to use templates, you give up the benefits of templates π
ah! that does work, cool
in this case the origin stuff is always passed from the caller so it doesn't really need any template involvement
Then perhaps you shouldn't derive FromTemplate on DamageOrigin?
Allowing you to not derive FromTemplate on Projectile?
Then you could still use direct BSN patch syntax
what would that look like?
i thought you need either FromTemplate or Default+Clone
and default doesn't make sense for DamageOrigin
(i could force it of course, with Entity::PLACEHOLDERs)
Yeah it would require placeholders or Options
To get the default impl on DamageOrigin
just feels bad because it's an impl that ideally is never used (the struct should always be fully specified any time it's used)
but it does make for the easiest use in bsn :p
How does from and to factor in to the final Projectile properities?
velocity = normalized_direction * speed?
yeah lol it's not sophisticated or anything, would be fine to init in-place. it's just that for this game virtually every callsite starts with the origin cell and target cell, hence the helper
If so, perhaps the FromTemplate derive + velocity calculation logic is better?
bsn! {
Projectile {
velocity: velocity_between(from, to, speed)
}
}
I think thats clearer anyway
/ resuable across contexts
yeah that seems reasonable
In addition to what I consider to be improved clarity, you also get all of the benefits of bsn patch syntax and templated entities.
/ no need for the placeholder stuff
I think "template contructors" should generally only come into play when theres a lot of internal interrelated logic between inputs and output fields
it makes sense as a design direction - i know a lot of bevy components have gone in this direction, i.e. preferring to be constructed directly rather than having any computed fields (and if computed values are needed they're shunted to another component)
it is a little opinionated though
but nothing wrong with that. anyway i appreciate the attention and guidance, thanks!
How do you envisage this working with dynamic bsn?
Sounds to me like itβs separate right, and can still integrate?
Separate in that you arenβt defining new component types in dynamic bsn. The logic is very reflectable, so you could spawn βscene componentsβ from dynamic bsn
awesome!
my one concern is having to use the deferred spawning method in order to use scene constructors. It makes keeping track of spawned entities much harder, requiring either monolithic spawning functions or keeping track of spawned scenes.
to give a somewhat contrived example, this wouldn't work as there's no way to guarantee that all the items will be spawned by the time spawn_player is ran. While it's not something we're likely to run into a lot those times that we do are going to be very frustrating for end users.
fn main() -> AppExit {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, (spawn_items, spawn_player).chain())
.run()
}
fn spawn_items(mut commands: Commands) {
commands.queue_spawn_scene(bsn! {
#Items
Children [
:Sword { @quality: Quality::Legendary, @damage: 10 }
...
]
});
}
fn spawn_player(mut commands: Commands) {
commands.queue_spawn_scene(bsn! {
Player
Weapon("Items/Sword")
});
}
Will feathers be ported to bsn in 0.19 ?
The core APIs are already ported. We're holding off on moving over all the examples to bsn!
Why require Default for SceneConstructor::Props? Required properties seem like a common use case. Could even allow SceneConstructor on a non-Default Component.
We may not be done with porting: there's a good chance that bsn widgets will be done as SceneConstructors instead of functions
Deferred spawning is only necessary if what you are spawning a scene that has a dependency that isn't loaded yet. Scene constructors don't change the situation at all compared to function scenes. If your scene constructor doesn't have an asset dependency, you can use world.spawn_scene for an immediate / full spawn
doesn't the :scene syntax always require deferred spawning?
:"scene.bsn" does, as it inherits from an asset that may or may not be loaded yet
Same reason we have implicit Default for Templates: if we remove Default, then either every field needs to be specified, or we need to introduce ..Default::default() syntax. I expect the vast majority of "scene construction" cases to benefit from the tighter ergonomics.
"Required" parameters can be expressed as Options that return errors in the final scene
(it's a bit irrelevant; sorry!)
@rare whale I've wrapped up a port of Feathers to SceneConstructor. Still some cleanup + quality to do, but PR should be out soon.
Just for prudence, what if anything would we gain/lose from not requiring Default (1 con was included, typing ...Default::default). I assume Clone would still be required?
Would probably be good to get this written down somewhere in case someone in the future wonders why you need Default and Clone, then you can just point them to a doc or whatever.
Other than that, I'm treating feathers like it's in feature-freeze
Clone is not directly required for Props because they do not require repeat-ability like Templates (every invocation of the constructor results in new Props and a new Scene). So its really just about the ergonomics we get from implicit Default
Do templates still require repeatability? Does the 'run once' scenes not change that? Probably just me confusing Scenes with Templates. (Again π€¦ββοΈ )
It doesn't change that:
- The
Scenetrait is "resolved" once to a hierarchy of Templates - Templates are "built" each time you spawn an instance
Ah okay, so 'resolve once' or 'build hierachy cache by value', but running that cache 'builds'/runs the Templates everytime/repeatably.
@split harness I've finally had a chance to properly digest Scene Components.
It also ensures that the scene can be queried for
This is the big win IMO.
I like this pattern, but I think we're going to need to start thinking hard about how to present the complexity of the scene system to users, and where we can efficiently reign it in
Relatedly, I think we should start talking about renaming Bundle to ComponentBundle now that it's much less prominent
Imo this is the "big win" π
It is essentially a Bevy right of passage to define a Player component and then ask "ok when I spawn Player, how do I spawn all of the other entities / components / scenes it needs to function with it?". It is high time that we had an official answer.
I agree being able to query for scenes is also a big win π
/interelated
Yeah, that's fair from a user perspective π
I'm biased towards library authors these days
I'm not sure we neeeeed to intentionally nerf Bundle ergo. Its still a key piece of the system / perhaps it still deserves a first class name
This is a point upon which I am skeptical, but my skepticism should not be a blocker
Worth discussing
I think that we won't know for sure until we see how things get used "in the wild"
I'm going to be pretty bull headed about "logical units of functionality defined as scenes and identified as components"
It's very prominent in beginner-facing APIs like spawn, insert, remove, and the trait bound in the docs signature is very confusing there. I agree that it deserves a good name due to importance, but people simply aren't typing it much.
From my perspective that was never a question of "if", only "how"
Same here; this echoes my "identifying components" ideas from many years ago
That's fine; the presence of a vestigial organ isn't going to kill me π
Components are the "thing" that drive / opt-in to behaviors in Bevy. Scenes are deeply interrelated with behaviors / cannot be extracted from them
Something I'm considering in the vein of "reigning in user facing complexity" is moving the "scene constructor" derive into the Component derive:
#[derive(Component)]
#[component(scene, on_add)]
struct Player;
impl Player {
fn scene() -> impl Scene {
bsn! {}
}
fn on_add(world: DeferredWorld, context: HookContext) {
}
}
- Lets the user think fully in terms of "what is this component's scene" rather than "what the heck is a SceneConstructor"
- Parity / siblingship with hook config
- More strongly enforces the SceneConstructor / Component tie
- Enables us to add component hooks to enforce Player always being spawned with its scene
- One less trait to manually derive