#Next Generation Scenes
1 messages · Page 5 of 1
Is what you are asking for:
- Callbacks that are systems, but not registered (like run_cached)
- Callbacks that are not systems at all
||Cart secretly shilling for https://jengottlieb.com/beseen/||
The former, I think? Not sure what the difference is
The difference is with the second you don't get dependency injection (or any overhead).
im really curious about how .bsn assets will work with observers and expression blocks in general
are they gonna just not allow them?
if so, is .bsn a strict subset of bsn! or will it have things that bsn! doesnt
In my earlier reactive experiments, where I used regular closures everywhere, I ran into a lot of pain points with captures - you'd have to do stupid things like declaring temporary variables and initialize them with clones just so that the closure could capture them without borrowing conflicts.
Later, when I moved to using systems, I realize that dependency injection is in many cases substitutable for captures: that is, you could often get the same information that you would have captured, but injected instead, without the borrowing problems. You'd still need to capture a few things, but these are mostly just ids which are copy/clone and have no problems with borrowing.
.bsn is a subset of bsn! that initially won't support script-like stuff
https://github.com/bevyengine/bevy/pull/20158#discussion_r2209735917
but could maybe be supported long-term to some extent
The way I did it in bevy_dioxus is callbacks are boxed functions that take &mut World, and then you can do whatever you want with it https://github.com/JMS55/bevy_dioxus/blob/main/examples/demo.rs#L71-L77
And a system takes care of running them
I'm guessing .bsn assets can be hot-reloaded but bsn! can't yet, though hot patching will probably be possible with subsecond later on
it will always be "bee ess en" to me, if you want me to call it "B-Scene" make it bsc!.
-# i propose we pronounce it "bussin"
oh yeah 2024 me beat you to this proposal https://discordapp.com/channels/691052431525675048/730525730601041940/1297970004293062717
Ok, so we should probably rebase the editor repo onto cart's branch.
Don't forget ||bussin'||
Didn't even realize @autumn bane beat me to it
Putting Rust code in a bsn asset file seems very challenging.
The other approach is to provide Bevy with a set of “hooks” when loading a scene asset that the system can automatically insert. So like “attach this observer to an entity named start button when it’s loaded.”
My hope is that asset files will be able to reference functions that are registered with reflect.
Like JS callbacks, where you pass a function name to your html button.
But… we should probably try to keep the discussion scoped to what we have now
And leave asset format stuff for later.
tbh I don't really see the point of using custom format when you can just use a .rs file with autocomplete type checking auto formatting inline expressions....
The editor will be able to edit the static asset type, having it edit rust files would be much much harder.
There are pros and cons to references in bsn itself, and hooking into assets dynamically. Referencing callbacks in bsn is simple but with the hooks approach you could load the same asset file twice but provide a different set of callbacks if you need to
It’s slightly out of scope right now but asset files are right around the corner
Also there's other benefits to having a file format, like easy hot-reloading or modding.
alright I understand now
Similarly I can only pronounce SQL as Ess Queue Elle
sequel to what?
yeah, it's understandable to narrow in on how much more potent the macro is as something with computational potential, but broader design workflows can't rely on code editing as the main action
And even if we can get the editor to edit Rust files, it would be a massive pain for any other tooling that wants to read or write this
Also uh using .rs for like save files is both cursed and likely to trip malware flags
This is a "public experimentation phase" where we can collectively evaluate my proposed approach and flexibly pivot / iterate / tweak as necessary.
Speaking of the editor, and given that we clearly need to try this out to properly evaluate it, it seems like the next step would be trying to replacebevy_proto_bsnwith this in the editor prototypes repo?
How do we want to handle that?
commentary from the room I'm in "I'd pronounce it boson to be extra nerdy about it"
I'm a bit confused on when expression syntax is needed.
Why does the first code use it and the others work with just the plain variables?
Variables work "inline", but I think if you had something like position + offset or a function call or some other expression with actual logic, then that'd require the expression syntax
No reactivity makes that pretty difficult
I think using {} as the delimiter for expressions is confusing. In rust (and many other languages) it would imply a struct or object instantiation, to me anyway. I would much prefer a more common syntax like {{ }} or ${}.
It was really the only thing just scanning over the examples that wasn’t entirely clear the first time I looked at it in de PR description
Ah that makes sense, thanks!
Rust's own macros like println! use {} for this though (or not full-blown expressions but string interpolation) and {} in general in Rust is just a block
Rust also uses it to define blocks though, so it is still similar
I have to disagree, {} simply delimit a block in rust so this syntax seem very accurate to me
Doesn't this mean we're using capitalization to differentiate between types and variables? Seems a bit on the cursed side imo. Like what happens if the name starts with _?
I suspect we are defering to rust's tokenizer to determine what is a variable and what is a type.
Ah damn haha. {} totally didn’t resonate with me as a block. You are all right… hmm. Still in the context of a template somehow it felt weird looking at the examples…
I didn't think the tokenizer had that information, both are identifiers until the parser right?
Which gets skipped for macros since they get raw token streams
Cart has mentioned that we need to rely on capitalization anyway to disambiguate stuff like enums and constructors https://discordapp.com/channels/691052431525675048/1264881140007702558/1374046746241536050
these are generally linted anyway so imo it's fine
So then _UnusedType and _unused_var aren't going to be valid? Like, obviously you shouldn't be doing that because they only start with _ if they're unused, but in my code it happens with global renames here and there. Just seems odd to me that it would break in this case
this can be supported by just skipping non-alpha characters when checking the case I believe
Forgive my dumb questions, but I have another one.
I don't fully understand yet what inheritance means in this context.
fn button() -> impl Scene {
bsn! {
Button [
Text("Button")
]
}
}
fn red_button() -> impl Scene {
bsn! {
:button
BackgroundColor(RED)
}
}
What is the difference between using :button here opposed to something like {button()}?
probably just that’s it’s more concise
i’d like if all the requirements for macro syntax were outlined for the inevitable bikeshed so we could all be on the same page
you can inherit assets like :"button.bsn" so the same syntax is supported for the function syntax for consistency, probably
Does BSN allow this?
Button [
Text("Button")
]
BackgroundColor(RED)
i.e. adding more components after the child relationship, without inheritance stuff
I would assume yes, but that it's stylistically not recommended, and there'd probably be some lint for it
Or it could just be automatically cleaned up by a formatter.
This actually demonstrates my issue with the children shorthand pretty well. I know this is likely to be a controversial opinion, so I won't really argue it too hard, but this looks to me like Button is a relationship. In my own assets, I'd probably use the full Children [ ... ] syntax for children because:
- it makes it clear what relationship is being defined
Childrenisn't special-cased in the ECS, it's just another relationship component, so treating it special in bsn is imo inconsistent
...but I'll admit this is solidly in bikeshedding territory, so might not be (yet) worth the time it took me to type this 🙂
As a sidenote, how do we differentiate between a relationship and a single component followed by children? Is it name-based again? Because that would mean parsing relies on semantic information, which is something I'd have thought we want to avoid like the plague
SQL is the sequel to SEQUEL. Kind of
I'm personally also not a fan of the [] shorthand given how it changes the parsing tree in an ad-hoc way.
Having a different shorthand like @[] (so @ is effectively a sugar for "Children" in this context) seems better to me.
Sorry for the bikeshed, but I personally don't see any other issues with the proposal than certain syntactical edge-cases.
It’s currently ambiguous, addressing that is first on the list of next steps I think
Ahhhhhhh I'm a fool! I committed to reading the whole thing before commenting and then somehow missed that
See here: #1264881140007702558 message
That could work, tbph I don't think typing Children every time is the worst thing, but I see the appeal of a shorthand obv
there’s a small pile of bikeshed options, def throw yours on as well :)
I agree, but I work with the premise that it makes sense to have a shorthand for Children.
Not having a shorthand makes the syntax imo quite self-explanatory for new users. But I assume the verbosity reduction might be a sufficient win.
That's about my opinion as well!
That would be easy since bevy_proto_bsn is not currently used by any crates, just
it! Jokes aside, it can still be useful if someone wants to try their hand at prototyping bsn asset editing until that is available upstream.
The only BSN-like stuff used for the actual editor UI is i-cant-believe-its-not-bsn. I guess that could be removed if we can hack together a replacement for the incrementalization
I patched in the BSN PR, getting lots of lints
Is it technically correct syntax to do: Children [A B, C D E, F, G H I]?
Yep
does BSN support unlimited number of children or is it capped at 12 like currently ?
Ah right I never got around to fixing that. Currently capped at 12, but thats an easy fix
Not at my computer to check, does the relationship-concatenate behaviour in inheritance work as expected for one-to-one relations? I would expect it to only spawn the last-specified entity for easy override behaviour, but I also see how silently not spawning an entity could be a footgun
If we’re bikeshedding shorthands for Children [ ] then for me at least > [] seems intuitive. It kind of looks like a fold, just like you’d see in a hierarchy UI view
No shorthand for Children 
Is inheritance a shorthand of expression syntax, seems odd that can do both
Okay, I've finally had a chance to sit down and read the BSN proposal in earnest
I'm really impressed by and excited for this @split harness! It solves a lot of active issues nicely, and looks like a great base to build on.
I'm particularly keen on the "split out bite-sized chunks of this and polish them one PR at a time" as an approach to actually shipping this; looking forward to writing some docs!
wow, you read fast nvm I thought you said, finally have :p
I do read fast though :p
I think the inheritance syntax ":base_button" is a bit weird . I would propose "..base_button" which is IMO more rusty and more expressive.
like this, feels more rusty, not sure if it runs into issues with the existing syntax
I don't think it would have a problem, as the existing spread operator is only valid inside a struct initialization block.
Semantically, it's actually closer to the JavaScript/TypeScript spread operator, since it comes at the beginning of a block, not the end, but I don't see that as a deal breaker.
The problem is this is "struct spread syntax" (or whatever its actually called). Those semantics are not the same as inheritance.
I also think it looks weird when used at the top, and when used for multiple inheritance
: is also less noisy
Things like ..base_button [ Node ] feel off to me
Im not sure if its noisy, but ".." is really not great semantically because in Rust it is placed at the end. However as @rare whale noted the spread operator in js land is pretty similar semantically and that is "...", maybe we could use that?
I think 90% of UI people know JS and would instantly recognize it
... seems like it would be more confusing than just using ..
: is pretty universally used as inheritance syntax
what’s the prior art here?
(I also use :)
C++ inheritance, Java inheritance, C# inheritance
I never connectd that symbol with inheritance, but now that you say it I can see it
I just feel that this inheritance is not really that inheritence 😅
Flecs is notably more like those examples, as it uses X: A {} syntax
(not java inheritance, you may be thinking of kotlin)
Is there different uses that I'm missing? 🤔
This way it makes much more sense to me
Haha I wasn't. Just misremembering. I haven't touched Java in many years
~15 years
for good reason 😉
hmm yeah, i guess :button to me feels different than something like class Foo : public Bar and didn't read that way to me but i guess i see it now
The difference here is that we're defining inheritance as a peer of a list of instantiated types (components). Externalizing it is an option, but I think it would affect legibility
:button feels pretty intuitive to me (no experience with .. in js land)
([ Node ]) :button doesn't read nearly as well as :button [ Node ]
Yeah but for this exact reason (that it is a list) does : feel off to me. And... ok
Yeah the big problem here is that this is new semantic territory
We can't be exactly like prior art because prior art isn't DOM-style ECS with multi-inheritance
Question, sorry if I missed this in all the info:
Does the "Inherited Children have lists concatenated" apply to all one-to-many relations?
I do think Flecs manages to feel more natural in this particular case. But I think it comes at the cost of terseness / a "naturalness" tradeoff in other categories
Hmm wait,
:button for overwrite
...button for appending
The former can have related overwritten, the latter appends them 🤔
i guess that's why i liked the .., the js object spread operator feels more semantically similar than oo multi-inheritance
at the cost of terseness / a "naturalness" tradeoff in other categories
Out of curiosity, what other categories are you thinking of?
what if use button
- children are "peers" of template fields and other components, rather than being contained in "list" syntax (ex:
[]or Html's tag wrappers) - Inheritance (to my knowledge) requires defining an entity name
It would be useful to compare the tersest Flecs equivalent to:
:button [
Node
]
children are "peers" of template fields and other components
Do you mean this?
spaceship {
// component disambiguated with ":"
Position: {10, 20}
// no ":" after name, child entity
cockpit {
Position: {1, 2}
}
}
Inheritance (to my knowledge) requires defining an entity name
Not necessary:
// anonymous entity that inherits from button
_ : Button {
}
That'd be
_ : button {
Node // assuming Node is a tag/component
}
Wouldn't that add Node to the button entity?
Yup, what does Node do in your example?
It is a child of :button
Wait - just to be precise: this adds Node to the anonymous entity that inherits from button, it doesn't add it to button
A child with name Node? Or a child that has Node?
A nameless child that has the Node component
Sorry for all of the assumed Bevy context 🙂
Yup sorry thats what I meant
Ah, in that case it'd be:
_ : button {
{ Node }
}
Yup thats my point. I think this is clearer / more natural when it comes to defining UI hierarchies (and scenes more generally)
:button [
Node
]
Not to say that you made the wrong tradeoffs. I quite like where you landed / I think it reads well, especially for the bigger / more functional scenes
But for the "ad-hoc" spinning up of hierarchies I like the BSN syntax more
Hm not sure if I agree it's more natural, that wasn't my initial guess :p If you compare it to HTML it's much clearer what's a child and what not:
<div>
<node>
</node>
</div>
In flecs script each :{ } is a value scope, each { } is a hierarchy scope
Fair, but without intimate Flecs knowledge I would have no clue how to parse this. I think the bsn equivalent is more guessable
But I suppose thats a matter of who you are / where you're coming from
Both use implicit Children, making them both require guessing
Yup. I think in both cases, thats a worthwhile tradeoff.
It's not implicit in my case. { } explicitly indicates hierarchy
Its context you learn once and then you save a lot of noise + typing
So does []. But @silk lava's point is that specifying Children {} would make that even clearer / less "implicit" in terms of "needing to know that in Flecs {} explicitly indicates hierarchy"
(same for Bevy's [])
how do you specify other relation types in the flecs way?
fwiw
// anonymous entity
{ }
// anonymous entity but you need a token
_ : foo { }
// named entity
ent { }
// anonymous child
ent {
{ }
}
// named child
ent {
child { }
}
you can do this:
(IsA, Thing) {
(IsA, Organism) {
(IsA, Plant) {
Tree {}
}
(IsA, Animal) {
Human {}
}
}
}
or if the relationship is not a hierarchy, you can just do
bob {
(Likes, alice)
(Likes, pizza)
}
Notably, Flecs has no notion of RelationshipTarget / everything is driven by the Relationship name. Bevy vs Flecs diverge in that we define RelationshipTarget [ (E1), (E2) ] and Flecs defines relationship {E1} relationship {E2}
Its also worth calling out that BSN currently has no way to express this flecs-style in terms of the Relationship name
Which I personally have no qualms with, because I think it fits the Bevy relationships mental model better
To always have Relationship/RelationshipTarget?
Will this change in a world with fragmenting relations?
Yeah it always has those two. Notably when spawning, the RelationshipTarget [] occupies the same space the RelationshipComponent would occupy anyway. It is defined in "component position" on the parent entity / looks like a component / is that component
Unlikely. This would be an implementation detail. Things get more interesting with many-to-many though (which relates to fragmenting relations)
I think the thing that trips me up here is that there's no delimiter around Node. You're jumping two conceptual levels (child entity -> component list), and yet there's no character to indicate that a child has started (unless you count \n), and there's no delimiter around the component list.
I could live with one of them being implicit, but both requires me to do mental gymnastics
I suggested more explicitly delimiting which things are entities and which things are components here: https://github.com/bevyengine/bevy/pull/20158#discussion_r2211286817
bsn! {
Player,
Sprite { image: "player.png" },
Health(10),
[
#NamedEntity(
Hat,
Sprite { image: "cute_hat.png" },
Transform { translation: Vec3 { y: 3.0 } } ),
),
// still an entity - but without a name
#(:sword, Transform { translation: Vec3 { x: 10. } } ),
]
}
Hmm, so that means ChildOf(e) isnt supported, right? 🤔
We have no special syntax for it, but it would be expressible with ChildOf(#SomeEntityName)
[] starts the list
, delims the list items
Yeah and a newline starts a new child? And then I have to use a ( if I want to use multiple lines for the same child? I think that's a lot to keep in mind :p
The trick is, to do this, you'd need to spawn that entity somehow (using RelationshipTarget syntax)
Which you could do. But functionally something is going to "own" that entity from a hierarchical spawning standpoint
No, a comma denotes a new child
Yeah that's closer to what I do, though with slightly different syntax:
{
Player
Sprite: { image: "player.png" }
Health: 10
NamedEntity {
Hat
Sprite: { image: "cute_hat.png" }
Transform: { translation: { x: 10 } }
}
_ : sword { Transform: { translation: { x: 10 } } }
}
No commas = Component, and commas separate entities. Was confusing to me at first as well
So this creates three childs with Node?
:button [
Node, Node, Node
]
how common is it to create a child with a single component? 🤔
Yeah these are all equivalent
:button [ Node ]
:button [
Node
]
:button [(Node)]
:button [
(Node)
]
From my perspective this isn't confusing. () just behaves like a "scope" in pretty much every language
ic ic
Reasonably common. How often do you spawn a div or button that just does div or button things (in html)?
I would've preferred it if the ( were mandatory so there's a visual indication that a child has started, but taste can't be argued ig
Sadly no optional top-level () wrapping 🥹
What do named entities look like?
An entity with just a name
#Name
A named button
(#Name :button)
This is technically also valid, but generally discouraged unless you're at the root:
#Name :button
#MyName is currently just shorthand for Name("MyName")
But my plan is to make it slightly more magical to allow using it in arbitrary places in a scene
(ex: field assignments)
So a named entity with a named child is this?
#ParentName [
#ChildName
]
Yup!
How is that not confusing 😅 if the name sits in the same place as the component
How do I add components to that child?
I like that entity name is always in the same place in the flecs version
Name is a component and it lives in component position. I don't see how that is confusing
(btw, funny that you use # for names, I use # to indicate that a name string contains an explicit entity id, e.g. #500)
Ah, so #ChildName is short for something like Name { "ChildName" }?
Yup! As I said here 🙂
Gotcha 👍 ok that makes more sense
Yeah names are a first class concept in flecs script, I think that's probably the number 1 reason why they turned out so differently
I don't think that's confusing - but I do think it's confusing that entities and components are kind of mixed together - I think it would be nice if it were more obvious (requiring (...) brackets for entities could be a step towards that.
Fwiw #MyName will be upgraded to a "first class concept" in that it will take on behaviors on top of just saying Name("MyName")
@split harness tho now that I think about it more, if #MyName is short for Name("MyName"), how do I refer to other entities by name in bsn?
But it will still perform the function of filling in the Name component at that position, so this still feels good to me
yeh, like ( Node ), MyName( Node )
Currently you dont 🙂
Once implemented, you could use it in field position, and it would resolve to a Template value:
#Root [
(Thing, Likes(#Root))
]
kk yeah that's a very different direction than the one I'm taking. Every name identifier in flecs script resolves to an entity (including the components, relationships, relationship targets), so it's really ingrained in the language. You can also do things like
for x in 0..10 {
for y in 0..10 {
"e_{x}_{y}" { // create e_0_0, e_0_1, e_0_2, ... e_9_9
Position: {x, y}
}
}
}
and then resolve those names later on in the script
Thats actually kind of how I'm planning on implementing this. Requesting a #entity before it is referenced will spawn a floating version of that entity (and it will later be inserted into when it is "instantiated"). I've also loosely devised #{RUST_CODE_THAT_RETURNS_A_STRING_HERE} syntax.
Referencing an #entity that is never instantiated would probably be an error though.
This feels ambiguous to me - #Root both refers to the root entity, and the Name("Root") component
Having names as a first-class concept seems like the way around that
If used as target of a relationship it can probably be disambiguated, but it would preclude you from having "components as entities"-style adding of entities to entities
It is not grammatically ambiguous, but I do agree that it is semantically ambiguous. I personally think it is a "lie" worth telling
Functionally / grammatically, things in "component position" vs "field position" are parsed differently
for example, in flecs script you could do this:
// define SpaceShip "tag" (really just an entity)
SpaceShip { }
// create my_spaceship entity with SpaceShip tag
my_spaceship {
SpaceShip
}
In bsn you'd do this, but that wouldn't work:
// I don't want to name my entity SpaceShip, I want to add the tag SpaceShip by name
#my_spaceship #SpaceShip
Functionally, I wouldn't build this into BSN on its own, but rather the extended BSN Set, which would allow defining multiple name-addressable "things" at the same time. (syntax / design has not been defined yet).
But it would look something like this:
bsn_set! {
tag Spaceship
(#MySpaceship Spaceship)
}
Right, makes sense from the direction you're taking, but IMO a bit less clean since now you have two different kinds of name identifiers
Heavily into bikeshedding territory, at the end none of this really matters
as long as it works
Yeah I haven't explored that space yet, but it feels resolvable one way or the other
Maybe something like this
scene {#Root [ Node ]}
// spawns an instance of #Root
:#Root
Although this does "feel" better to me somehow:
scene Root {[ Node ]}
:Root
Its worth calling out that we do want to explore struct-style scene inheritance, which kind of intersects with this
The name of the struct would be a "rust type name" which is semantically and functionally different than the Name component in Bevy
Yeah, in flecs those are the same thing
From a representation perspective (and Components as entities) that could be unified. Not the first time this has come up
Of course that spawned from you talking about it 🙂
I'm aware :p
However I think "ECS data types have Name components" does not necessitate "Things with Name components are ECS data types"
I'm not sure I'd go that far personally
Right, the latter doesn't make much sense
It would make it easier to allow things like
// intentionally explicit syntax to make it clear what's going on
Position {
// Struct tag
Struct
// Member children
x { Member: { f32 } }
y { Member: { f32 } }
}
e {
Position: {x: 10, y: 20}
}
The namespace in which the Position component is looked up is the same as the namespace for entities
(because it is an entity)
Funny side effect of this is that entity inheritance can also be used to express component inheritance, because they're the same thing:
Position2D {
// ...
}
Position3D : Position2D {
// ...
}
(though I still have to do a bit of work to make that actually do the right thing)
CART I just want to say, i really think you cooked the perfect syntax and i have no notes. I feel like generally when stuff like this happens only people who disagree with choices make their voices heard so i want to say i love the syntax how it is and would prefer it not be changed at all. That being the commas how they are, using :, etc
it's incredibly clean
very good signal to noise ratio
yeah this has satisfied a lot of contradicting needs and circumstances, afaict.
very clear case of the time being spent squaring people's needs and potential design mistakes/dead-ends and trying to account for them.
I also like it, my biggest question is about the need for { } around expressions, particularly when setting struct fields
I think { } is needed around expressions so that the macro can tell where the expression starts and stops. it's not required for single-variables.
I figured, but I'm wondering why it needs the brackets when : and ,/} will also indicate where it starts/stops
I think it's easier to accept a block. If you wanted to just accept a range of tokens between : and , or } that's a negative lookahead. For macro_rules it would be actually impossible to implement, but for this implementation I imagine it would just add some complexity.
when stuff like this happens
We're just sharing notes 🙂 Worst case it was a fun discussion and we stick to our guns. Best case we learn about different use cases that'll improve the design long term
cart explicitly called out that BSN is still in the iteration phase, so this'd be the time to bring it up
I don't mind having an explicit syntax for expressions though, personally. Both because the serialized format won't support them, and because It seems like they work just like rust blocks so you can do
field: {
let a = foo();
a.bar()
}
(It seemed like this was possible, I would have to double check).
from a user view I'm not personally that fussed by errant { } blocks compared to, say, the xml hegemony of jsx/vue/etc. also.
yeah! I think discussion is good
I also personally care a lot less about the bsn macro than I do the bsn asset format
fully aware it's a subset and a secondary concern, but
I think a runtime parsed format with expression support is going to be a winning combo
Does anyone have any links to how bsn will work wrt. to hot reloading?
For some reason I thought it would be magical like Dioxus' rsx! macro which hot reloads contents inside the macro.
- Is that the goal (later down the line)?
- Or will it just rely on subsecond? (Which isn't specific to
bsn!) - Or will it only work for the subset of
bsn!backed by files, which won't include Rust code "just data" - Something else?
IMO we shouldn't special-case this
For asset-defined BSN, we can rely on asset hot reloading, respawning when invalidated
cart has prototyped this in the past, i just don't think it should be MVP
this in the past
special casing likersx!you mean? I thought I had that from somewhere
For macro / code defined stuff, we should rely on system-origin tagging, and rerun startup / on-enter systems
What's system origin tagging?
I think he has prototyped or designed something that can reload changes to the macro as an asset. it's blocked on the asset system at least, and there may be other blockers (like expressions).
We've talked about it a bit over in #1374187654425481266, but the core idea is to take advantage of our location tracking to tag each entity with "which system generated me" while in hotpatching mode. Then, when a system is hotpatched, despawn all matching entities, then run the system again
Which means that hotpatching would work for any style of entity spawning, not just bsn!
this but opt-in iirc
Really cool. So it sounds like the endgame allows fine-grained diffing of what was changed, so e.g. within some scope it's known that system A has a diff but not system B, so only the "system A tree" needs to go through that cycle you mentioned
Maybe that's already how things are from the outset when using dx idk
Yeah, no strong feelings here 🙂
we can have it all
I actually haven't prototyped this in the context of the macro. The original pre-subsecond idea (which I stole from makepad) was indeed to parse the rust file (specifically the hot-reloaded macro) into BSN asset format form. The macro would record the Rust file line number for correlation purposes. Then we'd just "hot reload" the asset format form like we would with a normal asset.
However with the dawn of subsecond, I think it makes more sense to start with that
The benefit of subsecond is that we can make it handle arbitrary code changes
The benefit of the asset-format approach is that we can make some changes effectively instantaneous
(and it also requires less setup + infrastructure)
We can do both, but I think we should prioritize subsecond, as its the more complete experience
(and because we're investing in that dev experience in other areas of the engine)
reason I said that is not just for hot reloading/developer UX, but because being able to inspect the AST is very useful for reactivity. It lets you trace which parts of a template are related to which inputs
As always, I've found our discussion enjoyable and productive 🙂
same!
yeah, makes a lot of sense. the incompatibilities between the macro and asset format would constantly trip people up the other way.
I also really appreciate the words of encouragement / support. I'm very fortunate to have a community like this to work with.
That makes a lot of sense!
quick merge it in while no one's looking this is why i should never be a project lead 
Makes sense. In theory this information is in the current macro AST. There are just arbitrary Rust ASTs at the leaves. We could grab that at runtime if it is helpful/necessary (if we're willing to introduce the parsing + extraction overhead to get it)
To make it easier, we could also formalize a Rust expression subset (and then add an evaluator to the asset format, and then ...)
Yeah, it'll let you do things like
template Foo {
prop a: 10
prop b: 20
const c: a + b
// creates observer for SomeOtherEntity.OnSet(Position)
const x: SomeOtherEntity[Position].x
// static entity, never needs to be updated
e1 { Position: {10, 20} }
// only updated when a changes
e2 { Position: {a, 20} )
// only updated when b changes
e3 { Position: {b, 20} )
// updated when SomeOtherEntity.Position, a or b changes
e4 { Position: {x, c} )
}
NOW you're speaking my language 🔥
Virtual Machine based on bevy reflect and bevy reflected functions with only three operations
push, pop, and call
👀 _ _
( this would be so bad but also so good but we also should never do it )
getting flashbacks to every time either I've taken a configuration format and made it turing complete or watched someone else do that.
fun exercise that sabotages tooling t_t
Beautiful 🙂
Damn i can't make a thread within a thread so i'll have to post this here.
i do want to note that we have quite a bit of resources at our disposal in bevy, and we could pull of quite a few shenanigans, way more than most other open source projects, BUT if we keep our shenanigans to a minimum ( such as absolutely no actual code in the scene files lmao ) then all that energy can go towards other things. There are a lot of nerd traps that bevy hasn't fallen into yet, such as adding an official scripting language. So just like, yeah, it would be epic, but also damn it would cost so much, so i am not in favor of anything like this lol. We get a lot out of how small our actual weirdness budget is.
Just to share my thoughts
fwiw flecs does have runtime expression support and that's an open source project with (mostly) a single guy
it is doable
I do think that Dhall is really cool and have linked it earlier, is the thing
if only there was a clear idea of who was doing massive amounts of project and scope management to keep things on track and not send everyone down a nerdsnipe choke point 😌
I actually prototyped this, but when subsecond was announced it just felt pointless.
Source files were loaded as assets, scanned for bsn invocations, and diffed against the original to produce scenes that were partially from the static invocation, and then patched over with the changed/reeflectable stuff from the reload. This meant that you could change a value in an invocation heavily reliant on rust code as long as that particular change was reflectable. The algorithm got pretty complex in the end so I was kinda happy to let go.
Very cool nonetheless
I think the trickiest part was matching up children/relations to allow moving them around anywhere in the hierarchy even if they weren’t reflectable
The DSL -> L pipeline
"L" as in language and "L" in the gen alpha sense
For incremental updates, the approach that I have used is not to try and solve the general problem of diffing hierarchies, but to recognize that in practice there are only about a half-dozen control-flow primitives and they all have relatively straightforward diffs. So as long as you have a record of which control-flow primitive was used at a given point in the hierarchy, it's relatively simple to decide what needs to be changed. The most complex scenario is the foreach primitive, but this is just comparing two linear sequences, and the algorithms for this are well understood.
@split harness & others: I've created a PR that includes a number of new Feathers widgets (pane, subpane, etc.) using BSN. If you want to see how BSN might be used in practice, or would like to critique my work, have a look here: https://github.com/cart/bevy/pull/34
cross posting here since y'all talk about reactivity #ui-dev message
aww i thought that would show an embed 😭
ima just repost it here, recently finished up my Bevy reactivity library https://github.com/databasedav/jonmo which is a nearly complete port of the semantics of futures-signals, it's not quite ready for a #crates post but close, hoping to get some -dev feedback 🙏
Merged!
So my original plan was to follow up with work on menus and dialogs. The logic is that menus and dialogs, being composite widgets, would be considerably easier to implement using BSN. However, menus and dialogs don't add anything new to BSN, they don't validate anything that hasn't already been validated; they are merely using techniques that we've already demonstrated with pane and subpane.
Instead, I changed my mind and decided to follow my "anxiety minimization framework", which is to say, work on the thing that I think has the highest risk and feeling of uncertainty, the most likely to discover weaknesses in the BSN design.
In my thorium_ui library, there's a bunch of components that have BundleEffects, so I decided to try and see if I could re-implement these as Templates instead. I haven't quite got it there yet, and I can see now that there's a number of things I don't understand.
Part of what's tripping me up is the way things are named: normally when we name some software concept, we're building a metaphor based on the behavior of some real-world phenomena or thing. However, I kind of feel like some of the things in BSN are taken from industry jargon, but only mildly resemble the things they are named after, and so like the "game of telephone" they are several steps removed from the real-world meaning of the term. And my assumptions about the connection between the real-world thing and its namesake are causing me to make assumptions that aren't true.
Of the various things in BSN, Template is probably the closest to its real-world definition:
- A pattern used to construct a thing, to guide the tools used to shape the thing, and which is isomorphic to the thing being built.
- A template is not a tool (what shapes the work product) and its not a jig or clamp (what holds the work in place while it is being shaped).
However, I'm a bit confused as to whetherbsn!can rightfully be called a "BSN template" since it's actually a collection of objects which make reference toTemplateinstances.
TemplatePatch is another one that I have issue with: I read that as "a patch applied to a template", but in woodworking, one does not normally modify a template - the template is a constant during production, and is only modified when changing the design. I feel like what is being modified here is not the Template but the thing that the template produces - the work product, which might be called the "template instance".
(BTW, here's a beautiful video of a hardwood propeller for a vintage aircraft being carved using a template: https://youtu.be/GXLDg7P5lO8?t=725)
What we're calling "scene" seems like it's really a "scene element builder" or "scene assembler"
Is a template patch a diff?
It's more like property merging. It's not a diff, there's no comparison between states.
That makes sense
Patch seems pretty straightforward in that case, or at least I struggle to find something that better matches the concept
I agree that it's a patch, but what is it patching?
It's like if a professional boxer also happened to know how to fly a plane, would you call them a "fighter pilot"? No, because that would be confusing, even though they are both a fighter and a pilot.
Sure... I'm trying to model this in my head with bsn but the simple stuff I have knowledge of seems to work fine. For instance, I have a button template and a red button template, and red button just patches button. That seems to work fine in my mental model. Does the issue emerge in more complex cases or is the issue already there in the button and I'm not seeing it? Your fighter/pilot example does seem to be problematic, but a button/red button seems fine
Honestly this is already making me angry (joking). Just got done listening to Casey Muratori tell me why inheritance is bad for 2 and a half hours and this seems to belong to the same family of conceptual headaches that had me move away from OOP
Well, if this is annoying I can stop discussing it. I tend to focus on precise naming of things.
No it's not annoying, I am curious what your thoughts are
All right
I was just joking, and noting that this is the same sort of naming problem and "what is what, what goes where" problem that you can avoid a lot of when you leave OOP land
Part of the difficult is that, in programming, we tend to use the same word for both the type and the instance of a type. Most of the time it doesn't matter: you can use the word "Red Button" to mean the type whose methods produce the red button, or you can use it to mean the actual instance of the red button.
However, when we talk about templates we are specifically talking about a process of creation.
In the physical world, a template is used to create a thing, but the thing created is not a template.
But the Template Rust type refers to both the template and the thing the template creates. It makes sense to patch the thing that the template creates, that's what the process of creation entails. But the template is constant, it isn't changed by the process of creation. But we use the same word for both.
(And BTW, OOP has far worse conceptual difficulties than this. One of my favorites is the Platypus Effect - a type which doesn't easily fit into neat hierarchical categories.)
Riiight okay that makes more sense. It seems like it's a conceptual leap that bsn is asking you to take. red_button() is the template, but in your code it behaves like the button. Now that you've pointed it out to me I can't unsee it lol
I managed to get a small reactive demo working in BSN:
commands.spawn_scene(bsn!(
Node {
left: ui::Val::Px(0.),
top: ui::Val::Px(0.),
right: ui::Val::Px(0.),
}
// When the condition is true, insert the component; when it's
// false remove it.
insert_when(
|state: Res<State<GameState>>| *state.get() == GameState::Play,
|| BackgroundColor(css::GREEN_YELLOW.into()),
)
))
@thick slate You were asking as to whether we still needed BundleEffects - this code was effectively ported from BundleEffect to Template/Scene.
I suspect that there's a good chance that we need BundeEffect for performance? But yeah, it's conceptually suspicious to me
Now, this demo is not very sophisticated:
- The mechanism for detecting changes is somewhat brute-force.
- Rather than doing compile-time analysis of which properties are dynamically and incrementally updated, it just creates some small metadata at runtime, using relations, which tells it what to change. Similar to the kinds of hidden satellite entities used by observers.
Code can be browsed here: https://github.com/viridia/bevy_thorium
Ack, just bonked my head against a wall for a while but turns out something is up with spawning bsn scenes
Somewhere in my code this is happening:
commands
.spawn_scene(bsn! {
Node {
width: Val::Px(100.),
height: Val::Px(100.),
}
BackgroundColor(tailwind::AMBER_400)
})
// Adding ChildOf stops the node from showing up
.insert(ChildOf(*root))
// Inserting Node the old fashioned way *somehow* fixes it
.insert(Node::default());
I tried inspecting resulting entity (using log_components and queries) and I could not tell what is different about it when it has a parent.
I don't have a minimal repro at the moment
Also, doing (partial) ports of existing code is rather painful at the moment. Hierarchies in particular.
ports of existing code is rather painful
One of the reasons I hoped to see bsn in 0.17 🙂
Looks like Commands::spawn_scene is always deferred. It only inserts a ScenePatchInstance on the entity. The actual "spawning" (template build/insertion) happens in the spawn_queued system (currently once per frame in Update) when all dependencies are loaded.
I have a feeling this will confuse a lot of people. Maybe there should be both a sync and an async command for spawning scenes? 🤔 Which leaves the question what the sync version should do if there are unresolved deps
Not sure if (or why) this is the problem in your case, but it might be related
I did notice this and it did throw me off a bit, but no that's not the issue. The entity does end up with the Node component when I don't insert it, but it won't display
It might be a bug in bevy_ui. I should try spawning an empty child entity, and then inserting Node after a frame or 2
If its deferred then it should be named as such.
To be fair spawning scenes has generally been deferred
Considering I never touched the scene system prior to bsn stuff, I don't think that'd be common knowledge
However, I'm a bit confused as to whether bsn! can rightfully be called a "BSN template"
I wouldn't call it that / I think that confuses things.bsn!is aScene.
TemplatePatch is another one that I have issue with: I read that as "a patch applied to a template"
Thats what it is 🙂
but in woodworking, one does not normally modify a template - the template is a constant during production
We are modifying the Template here, but I think the woodworking template metaphor still works here pretty well. TemplatePatches are forming the template we will use to create our final outputs, via layers of template patches. Ex: we are "layering template stencils on top of each other to create the final template". for example, we use a pencil to trace a star template on top of a circle template to create a "spike ball" template.
The "stencils" are the TemplatePatches, "penciling in" is when we resolve those to the final "spike ball" Template, which we then use to trace out the spike ball on the wood.
@split harness Could the Default + Clone requirement be lifted from constructors and associated constants? Presumably by wrapping them in a function/closure.
It has been pretty frustrating to deal with existing components that work just fine, but must be (non-trivially) reworked with GetTemplate if I want to use them within bsn!. Especially when they still need to be usable outside of bsn!.
In that same vein, I'd like to be able to pass functions that produce components(bundles?) into bsn!.
My workaround now is to create one-off Template types that insert the value I need. Got frustrated trying to wrangle traits to make something re-usable so I have a little macro for it.
nvm, I missed the template function, which pretty much solves all that
Instead of creating new Template types you might be able to use the template function, which lets you create templates inline. From the PR:
bsn! {
Text("hello world")
template(|context| {
Ok(TextFont {
font: context
.resource::<AssetServer>()
.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0,
..default()
})
})
}
That does make things a lot nicer 😌
Just remembered there is also template_value if you don’t need any build logic and just want to pass in a bundle!
Fields being placed directly into a closure is rather annoying, this means we have to move calls .clone() or similar out of the bsn! macro
And passing in a variable called value breaks the macro :0
Some convenience for boxing scenes would be nice :)
Gotta recruit some boxers before you can film that scene/j
Code for reference: Port Properties Pane to BSN and Feathers #235
Hey yall! Quick reminder that I’ll be on vacation for a week / I’ll be largely disconnected starting tomorrow. Feel free to keep discussing and when I get back we can iterate some more
@split harness Can you show me how to use an ImageNode in a BSN block? Assume that I have an &str for the asset path.
ImageNode doesn't implement GetTemplate yet on cart's branch but it would need to to support passing a &str to the image field
You can work around it for now
template(move |entity| {
let handle = entity.resource::<AssetServer>().load(string);
Ok(ImageNode::new(handle))
})
Looks terrible though
It would look like this:
ImageNode {
image: string
}
Ugly I can live with.
@split harness Another use case for "intercepted children": menu buttons:
pub fn menu_button(props: MenuButtonProps) -> impl Scene {
bsn! {
:button(ButtonProps {
variant: ButtonVariant::Normal,
corners: props.corners,
on_activate: CallbackTemplate::Ignore,
})
[
{props.label},
:flex_spacer,
:icon(icons::CHEVRON_DOWN)
]
}
}
The problem here is that we want the label to come before the downward chevron on the right. We could hack around this by reversing the order, and setting the flex_direction to ColumnReverse, but if you have more than one child that gets confusing.
Hot reloads anyone!? 🌶️
I implemented a reconciliation (incremental update) algorithm for ResolvedScene based on @native mesa's awesome work in ICBINBSN, with a couple upgrades:
- The
Namecomponent (orTemplaterather) is used to key entities, otherwise it falls back to an auto incrementing id like usual. Works nicely with the#-syntax! Though it will be even nicer with#{<expr>} - The component diff handles removing components that were previously explicit but now required (to make sure they reset when recycling entities)
- Supports all relationships ala
bsn!
This could potentially be used for coarse/medium grained reactivity implementations.
But it's also great for non-destructive hot reloads. Here's a demo using subsecond, reconciling the scene on every frame:
(sorry about the watermark... too lazy to figure out the right ffmpeg args)
Edit: Now available at https://github.com/cart/bevy/pull/36
this is so good
it never occurred to me that you could use the same basic incrementalism for both both logic/interactivity AND for reloading the logic.
the way the checkboxes stay checked during styling is 
Super super cool. Maybe PR this to Cart's branch?
@thick heath look 😄
It's so cool seeing the impossible come together
Yeah, I'm very "this is Rust"??
I am going to have to gush about this a bit; the way you can maintain the full state of the application through a code-swap is something special. Keeping that last button unchecked while moving it around in a function is not something it's easy to do even on the web.
It speaks to the power of the bsn design that people can experiment with reactive stuff on top of it like that
seeing the long-standing tradeoffs of rustdev get melted away by community effort is just really exciting
and yeah like, when working in vue I don't get a lot of what's on display here.
Obviously the big limit to this is going to be the names, right @keen patio ? If you tried to move state across the hierarchy, I wouldn't expect it work (my prototype used namespacing, idk if you found a way to fix that). Any other edge-cases?
hot take i really like the :thingy syntax.
it reminds me of elixir
and the more i see it in use, the more i like it.
And I haven't even min-maxed the linker yet! IIRC there are some magic tricks to make the incremental compilations faster
Yep that's correct. Similar to how it works in reactivity libraries on the web I think. The key identifies a node among its siblings.
I think that's a pretty reasonable limitation. Reordering is much more common than arbitrary hierarchy restructuring.
So like ... this is probably what the editor scene viewer should run on right? We could rebuild & re-render the entire scene, but if we do things incrementally then it's easier to persist things like selection state across a reload.
it reminds me of css selectors
div + :hovered type shit
doesnt really map conceptually though
You're planning to do scene modifications in the editor through bsn? 🤔 So if someone changes a value in an inspector, you're updating bsn code?
bsn will be an asset format.
This diff system (correct me if I am wrong @keen patio ) runs on the final output of that asset system, ResolvedScene. You just just take the current ecs state and "reconcile" it with the ResolvedScene data.
I was thinking of the editor writing bsn asset files, then loading them, resolving them, and reconciling them.
As was i 🙂
Ok, so is that a "yes" on my question?
ah, are you calling .bsn files "code" here?
I called it bsn code yeh
If you are talking about bsn in rust files, no. if you are talking about bsn in static .bsn markup assets, then yes.
that's my understanding of the plan at least.
bsn is what the editor edits.
Yeah that's how I imagine it as well. An inspector however (I think its very important that we separate the two) would still edit components in the live ECS through reflection
Right, so if you have a complex scene with say 10K LOC and you drag an entity across the scene. You're going to patch & reload that scene?
* for each frame that you're dragging
That sounds like a bad idea
This is what dioxus does
I had this demoed a while ago with bevy_dioxus 😅
Yeah that was my question. So if inspector changes (and I assume, changes made to objects in the scene view) don't go through bsn, which changes will?
Well I don't think we'd go through the file. We'd have the bsn data structure already parsed and in memory. We'd modify that ResolvedScene directly, or rederive some stuff to get an updated ResolvedScene. Then we'd rectify that.
Then later, you can write the in-memory representation of bsn out to scene files.
Sure, it wasn't the loading/saving to a file that I was worried about :p
The "Scene view" in the inspector and in the editor are two different things to me
Don't they have the same problem?
The inspector is a tool to inspect a live ECS (for debugging etc)
The editor is a tool to edit scene (aka .bsn) files
In a way the scene view is worse since the frequency of updates is higher than an inspector
Not in my mind. One of those is editing a document, one of those is editing runtime values. Bsn is not a save-file format, at least not currently. You can't take arbitrary ecs state and serialize it back to bsn (currently).
How I would have expected this to work is that both go directly through the ECS (with or without reflection) and when you save the scene it's saved to bsn
How would that work with things like scene imports, or patching?
I don't see why that wouldn't work
Import or patching are infrequent things, so then just serialize the scene to bsn, patch, done
say you've loaded some data into the ecs from several files, one of which imports and applies patches from others. how do you know how to modify those files?
if a component on a given entity has fields compiled from multiple different files (and some default fields), and then you overwrite one of the defaults with a specific value, which file does it go in?
Dunno, that sounds like a self imposed problem. Doesn't that go for any update you make to the scene? If a component is set in A.bsn and one field is patched in B.bsn and now I modify that field, which bsn asset do I update?
Some of the values might not even be in any of the files. They could just be part of T::Default. We would have to track each field on every component to know if it was explicitly set, so we know if it should be included in the saved file
you have to expose the fact that values come from bsn file to the user, to differentiate things, and let them pick what goes where. I suppose you could add a layer to the ecs tracking which fields to serialize where, but at that point you're basically doing the same thing as modifying bsn documents in memory.
Ok, bit confused now. So you're saying you cannot save a scene to bsn with the current approach?
I think we should probably move this to #editor-dev
If you load a scene from several bsn files but you cannot track which one to update after you make modifications, then what's the point of having the scene saved in bsn? 🤔 probably exposing my ignorance of bsn here, just trying to understand the workflow
I don't want to distract from @keen patio 's work.
Will do! I'll start with a small addition to ErasedTemplate which makes it possible to keep the rest of the implementation (which may or may not be more controversial) outside the tree.
Since state is retained does that mean it triggers change events only when values change? Like it’s only going to trigger updated events for a component if that specific component changed?
There really isn't much magic to the state retention here. No change tracking or event triggering.
One just has to think about what is state and what is output. The scene being reconciled would be the output. Any state components has to be added in other ways, and may never be part of the reconciled scene itself, as they would be overwritten with their default values on each reconciliation.
The reason the Checked state is retained is because the Checked component is not added by the reconciliation. It is added as a result of clicking the checkbox. The reconciliation only removes components that it added.
:checkbox in its current state actually includes a state component, namely Hovered. And the only reason you are seeing a hover effect in the demo is because the scene is reconciled in the First schedule. The Hovered component is re-inserted on every frame with its default value (not ideal). update_is_hovered runs in PreUpdate.
@rare whale how would you feel about only including output components in the feathers scenes? State components like Hovered could be implicitly included using #[require()].
I personally believe that it is important to separate state and outputs regardless of what the final reactivity solution will look like. Mixing them together sounds like a recipe for 🍝
Ah, I was wondering how it retained the state. So if you assigned the checkbox value explicitly in the bsn, the state wouldn't persist?
(which would be entirely reasonable)
It would since that would mean that the state is stored elsewhere
What happens with on() ?
Very good question! Haven't tried it 🤷♂️
My guess is with current-style observers you'll get duplicate registrations; with future relation-based observers it will work like other relations. Maybe.
Looks like it outputs a template that runs entity.observe(...) on build. So that would run on each reconciliation
I think the answer here is that Template needs an unbuild method for these special cases. This is very similar to what @timid leaf did with his thing.
Yeah I guess that could be one option
I went for a different solution in proto BSN where observers were added with a generic Component instead, adding/removing the observer using hooks. That way it could handle reconciliation
(also inspired by ICBINBSN)
Maybe OnTemplate could check if there already is an observer attached of that exact type (the static function type, not the event), and if so skip adding it? 🤔
There's no right answer:
- Keeping the same observers means keeping any mutable state
- Blowing away the observers and replacing them means updating captured values from the environment.
Of these two, I think the second is more important
Right, yeah option two makes the most sense to me
Anyway, it's getting late over here (1am in 🇸🇪). I'll try to get something up as soon as possible so yall can play with it too!
Anyway, I'm having weird problems with spawning BSN as children to pre-existing entities. That is, when I spawn a bunch of nested scenes, no problem - but if I try and add a child scene later, using commands.spawn_scene() and then add_child, it doesn't show up - but looking at the code, it should work.
Incredible work, A+, this is the kind of stuff that shows that rust isn't a compromise, its the best possible choice, 
I don't want to take away from the achievement which is really cool (and does way more than what I've been able to do with my templates + UI)
but there's still a 3 second delay for a UI that only has 5 buttons
I think we'll have to wait for a bit to see how this scales to apps like an editor
Hahahahah, true, true
More work must be done!
But im fully convinced that its within reach for rust to be incredible for ui dev, it just will take more focused effort by amazing developers
Hm, this is not so much about Rust I think, since there are already frameworks that have demonstrated it's useful for UI
This is more about BSN / hot reloading I think
Like we already have dioxus which people seem to like
Since BSN is component-based, revert/unbuild logic should go in OnReplace observers. Unfortunately it's mostly a waste to have those observers in release builds where there's no hot reloading. You can avoid that cost by using cfg, but it's awkward and verbose.
Re: reload speed: I’m using proto bsn’s asset format and reloads using the asset are way faster than going through code. Obviously you only have a subset of the features you’d have in code, but reloads are almost instant. The hierarchy I have isn’t huge, maybe 1k lines of ”code”, but still..
in Figma's component-instance system, there are virtual entities which doesn't exists in the "source of truth" data (in our case bsn files), that are created because you are instancing a template that is a tree. If you modify any property of these objects, it's data is saved as "overrides" entry on the immediate non-virtual ancestor. (the instance root)
I feel bsn is an authoring format (allow easy edit in plain text form, multiple ways to do the same thing), but an editor works better with a serialization format. Not sure how hard it is to make the format that support both.
I encountered this as well. I haven't figured out what causes it but as a workaround inserting the Node component (e.g. commands.spawn_scene().insert(Node::default())) seems to fix it. Most of the time at least, in one case it didn't seem to work
Using proto BSN I noticed that if Node is added to a parent in a deferred way (don’t know the right terminology), the children will not show. I have to manually add Node so things don’t break in that one frame when Node is missing.
Yeah it seems like it would be a bevy_ui issue, which just becomes easy to hit since spawn_scene is deferred. You end up with an invalid (has non-Node parent) Node until the scene is resolved and spawned. And it seems like the UI systems can’t handle the parent adding Node, making the child valid
(Have not reproduced and confirmed on main though)
That sounds very much like what I am seeing
Why does spawn_scene need to be deferred? What does it do that requires deferring it?
Since it allows inheriting from scene assets, those have to be loaded before the scene can be fully resolved.
I would assume there will be nicer APIs to synchronously spawn/insert non-async scenes in the future though
To spawn a scene synchronously right now, you could do something like this:
/// Synchronously spawns a [`Scene`] with pre-loaded/no asset dependencies.
fn spawn_scene_sync<S: Scene>(entity: &mut EntityWorldMut, scene: S) -> Result {
// Create a new ResolvedScene and patch it with `scene`
let mut resolved_scene = ResolvedScene::default();
world.resource_scope(|world, assets: Mut<AssetServer>| {
scene.patch(
&assets,
world.resource::<Assets<ScenePatch>>(),
&mut resolved_scene,
);
});
// - Builds the templates
// - Inserts the resulting bundle on the entity
// - Spawns related entities
resolved_scene.spawn(entity)?;
}
I think it'd be perfectly reasonable for a nondefered scene spawn with caveat that it could be blocking if parts haven't been loaded before
There are really two parts to this problem, the first being the deferred spawning. In order to preserve the order of insertion of children, we need an entity id to produced synchronously, but that id has no content until the assets are loaded. But the other half of the problem is that once the assets are loaded, the ui system is (I'm guessing) not updating or propagating what it needs to - it's not noticing that the empty child is now full of stuff.
Could a mvp of bsn just require that bsn files be loaded on startup? It sounds like the current form supports some kind of asset streaming which is an awesome use case, but is an advanced one most users probably wouldn't need?
It's not just the BSN asset - its the assets that BSN refers to, like images and fonts.
This is an important feature
I think a blocking version or maybe the option to preload assets in a startup system would be good then.
I guess both since the preloading would speed up the blocking version
fwiw I think these are common distinctions when loading assets
You can get the asset now (blocking) or later (not blocking). Technically it complicates things but not more than any other version of this type of thing.
I don't get how the ui system isn't picking it up a frame later tho?
It doesn't make sense unless the responsible queries are failing to capture the new entities
Why do we need all those other assets to spawn the bsn? I would assume that the bsn would just be responsible for spawning everything, putting the handles where they need to be, and then let systems deal with unloaded assets (like they do today)
Also, (I am probably wrong about this) I thought the template for handles just store the asset path, so they wouldn't be storing a handle, so they wouldn't be loaded with the asset? (Unless we have special code that looks for those templates and creates those handles so we load them ahead of time)
Your evil hack works: I can get the popup menu to show up by inserting a Node::default() immediately after the call to spawn_scene.
I skimmed through the layout system this morning. I suspect it might have something to do with the change detection used to only do certain calculations/taffy updates when the children has changed. It might need to also check for Node addition on the entity itself
(also curious about this, would’ve expected it to work like that as well)
To speed up incremental builds for macro-heavy projects, I recommend cranking up your macro opt-level. This makes macro execution substantially faster which impacts development builds (and hot-patching speeds) quite a bit.
[profile.dev.build-override]
opt-level = 3
codegen-units = 1
Oh, thanks! I was hoping someone would drop some wisdom on this 😍
Found a short moment to read some messages (I’m not back from vacation yet)
It’s worth noting that waiting to spawn a scene until arbitrary asset references have been loaded isn’t fully wired up yet. Currently it only does scenes. Template::register_data exists for this use case though. My current plan is to add a high level spawn_after_deps_loaded API, and maybe also adapting HandleTemplate to support configuring this per template (ex in SpriteTemplate)
Not quite sure what the final shape of this should be, so feel free to develop opinions and experiment
My main opinion here is that by default BSN should not be deferred. The primary API should be sync. Deferred should ideally just be a convenience component to do the spawning for you once assets have loaded (basically like SceneRoot today).
Good to know templates already have a way to register this data early! It is certainly useful to start loading these assets ahead of time. I'm just not really convinced that you need to block on them. Most of our systems already expect assets to possibly not be loaded, so I'd prefer we lean into that.
agreed
I think the primary use case will be stuff like “I don’t want piecemeal pop-in rendering when loading this scene … all or nothing”
What about scenes where you stream in content based on what's around you?
that works but in the ui case i think this defered nature is causing weird effects like what is happening in talins menu button
I think it’s worth looking into the viability of encoding in the type system that a scene can be immediately spawned / can skip the dynamic representation. My last approach did this, but it had some downsides
Back when I blurred the lines more between templates and scenes
couldnt we do it at the spawn command level? with docs saying "up to you to make sure you dont need defered"
this feels like the current pattern we use for the defered/nondefered info level
We could! That’s certainly easier, but also more “fuzzy / prone to runtime failures”. And it requires going through the dynamic system
Although in practice anything “real” is likely to be doing scene inheritance. Which requires dynamism
i think its a good middle ground between the choices imo
yes exactly the asset system has problem managing vram for this reason that whether a gpu asset is loaded
or not is a bit fuzzier than stuff we can read right into cpu memory. our current approach leaves much to be desired
I see this being a specialized entity / component that references a scene and spawns/despawns based on some criteria
Very tbd. Feels like a “higher level” thing. Curious if you think it would require fundamental changed to the design
What about an entity that swaps out a mesh based on LOD?
That’s mesh / asset config
Which a scene could configure
yes i think we are going to end up treating the mesh more like metadata and build higher level lifecycle on top of the asset api
We have some LOD stuff already
obv loading the mesh fully for physics etc still needs to be supported
so i don’t think it’s a problem for scenes. if the mesh isn’t requested for cpu memory it can just load instantly with its description/layout
and defer streaming to gpu until that’s actually needed
have been talking about this a lot with @white lichen
Right, but (and maybe I'm misunderstanding) that could cause meshes to be (re)loaded at any point during the scene right? So "all or nothing” seems like something that's difficult to satisfy
we probably want an asset process step for physics meshes eventually
straight up throwing a mesh at physics doesnt make sense: you need different things. you dont need tangents or uvs or colors etc, you need geometric normals not interpolated normals, you need acceleration structures like a bvh over a trimesh, or you need convexity, etc
right we want to be able to read all the interleaved vertices ideally straight into a staging buffer from disk. so arguably what’s needed for physics is a totally different processed asset anyway
yup
in general i just want us to move away from the idea that because an asset exists in the world that it has any relation to being in vram or not
separate question, you may want to reference an asset before it’s materialized fully. the asset lifecycle != gpu lifecycle. we just have to make it easy for beginners so that it does the current behavior by default while leaving room for advanced users to optimize
Ah yeah I think “all or nothing” won’t work for everything. This feels like a simple global vs local config problem. I’m not too worried about it / I think we can find something that satisfies the relevant use cases
Like in this case, the author probably knows how they want their scene to behave. We just need to expose the knobs in the relevant places
Ex “fully lazy”, “wait for the lowest lod”, etc
Makes sense 👍
I was envisioning something more like a CSS media query where you could add/omit/change things to a scene based on LOD/other parameters
And just like a browser it'd load assets when they are first required
@split harness @raw mesa I don't think making scene loading sync is the answer, or at least let's try solving the problem some other way before we throw out the baby with the bathwater. It's sill early yet.
I can tell you that better async support would have been a godsend on Sims 2. We spent sooooo much developer time trying to keep the player entertained while loading the neighborhood (which took like 15-30 seconds).
Really, the problems that we are seeing aren't really the fault of BSN, it's more that BSN is exposing weaknesses already present in Bevy.
We do, however, need to expand the limited BSN API, something which cart and I have already discussed.
There's a little weirdness here in terms of what we mean by sync vs async. I want the core BSN API to be "sync" in the sense that I can create a BSN and spawn it directly into the world without waiting for a system to see it or something. I don't want the API to require to wait for every asset the BSN references. I consider that async/deferred since (AFAIK) the scene literally won't spawn until the referenced assets are loaded meaning if you spawn it into the world, you won't actually be able to interact with the scene yet.
I am all for having utilities to wait on the referenced assets before spawning the BSN, but that could be done with a SceneRoot component that also has a flag "wait for referenced assets".
Something like the GLTF case, where a "SceneReady" event is emitted once the various handles have been resolved.
(Maybe even re-use the same event)
Something like that, yeah
Essentially I would like it if spawn_scene was just a very convenient Command (i.e., instantaneous), and then all the asset stuff is handled in like a spawn_scene_from_handle or something (which would be deferred)
I mean ultimately the end goal is "don't let the player see bad stuff"
I don't agree? As in, I think the goal of BSN itself should be to make it easy for devs to spawn a hierarchy (obviously an oversimplification). All this stuff about waiting for assets to load seems like something that should be handled "externally". That allows powerusers to decide exactly what they're willing to block on, and what they're willing to let be deferred.
I know load screens suck, but it also sucks for things to take longer to load in the first time compared to other times. Giving users more control over when they want to spawn scenes is my priority (e.g., we have to block on the floor mesh, so make sure it's loaded before we spawn our BSN, but the trashcan mesh we can just ignore and spawn the BSN before it's ready).
How about a WaitForX Relationship to put the control inside the scene? Like WaitForAllAssetsLoaded [handle_audio, handle_background_image,..] or even WaitForEvent
@split harness A small PR to make ErasedTemplate a bit more versatile: https://github.com/cart/bevy/pull/35
And here's a PR with the reconciliation (hot-reload)!
Technically does not need to be in-tree in its current state, as long as the above PR is merged.
https://github.com/cart/bevy/pull/36
Could you open an issue for it so I can stick it in the 0.18 milestone? 🙂
Oh these are not PRs to bevy main! They are based on/PRd to Cart's next-gen-scenes draft
I know, that’s why I'm asking 😄
Can’t put your PR into the milestone because of that haha
Haha I'm not sure what you mean 😅
eh, doesn't matter, not that important 🙂
I guess there could be issues like "State-preserving scene hot reloads" or "Reactivity". Reconciliation would be one step towards those, but there may be other ways too. I don't think it makes sense with an issue on the bevy repo for it just yet, since BSN may also change and we might find other ways to solve those issues.
@split harness I did an experiment whereby I changed BSN to load scenes in PreUpdate rather than Update, and the FOUC problems went way. However, what I really want to do is instead add an explicit ordering to run it before PropagateSet::<TextFont> - but this can't be done without adding dependencies. Either that, or have the BSN stuff run in its own SystemSet and then put PropagateSet after it.
BSN should definitely have its own system set
OK verified this works too:
app.configure_sets(
Update, PropagateSet::<TextFont>::default().after(SceneSet));
Where I added SceneSet to the BSN code.
Why resolve_scene_patches and spawn_queued happens in Update instead of PreUpdate? 🤔
I think ideally scene spawning happens outside of Update so user systems don’t need to care about ordering, but that seems like a nice fix in the short term
Just a matter of me not caring about that while developing it. It definitely shouldn’t be in Update
Agreed! first-class async support would be a very nice addition to Bevy.
It would be great if we had "async systems" and spawn_scene returned a future, allowing the caller to wait for resolution/spawn before running further logic.
And the "sync spawn" use-case could just poll once and fail if pending, possibly using a utility like futures-util::FutureExt::now_or_never
i've experimented a good amount with this kind of thing, if we ever wanna open the door to specifically talking about tighter bevy async integration i have thoughts, lots of thoughts 👀
Posted a couple more PRs to carts branch:
- Add
#{ <expr> }-syntax to support dynamic naming: https://github.com/cart/bevy/pull/38 - Fix rustmft in codegen.rs: https://github.com/cart/bevy/pull/39
bevyengine#20158
The #-syntax currently allows specifying names using Idents, which is nice.
This PR extends it to also support arbitrary expressions enclosed in braces:
let n = 5;
bsn! {
#{fo...
finally seriously released my reactivity solution 🙂 #crates message
Noticed we lost some feathers on the recent sync with main: https://github.com/cart/bevy/pull/41
I'm somehow still getting child nodes that don't show up 😭
@keen patio I'm still getting missing child nodes with this simple repro. When you have time could you take another look at this issue?
commands.spawn_scene(bsn! {
Node
template(|entity| {
let id = entity.id();
entity.world_scope(|world| {
// This shows up
world.spawn(Text::new("Argh")).insert(ChildOf(id));
// This does not
world.spawn_scene(bsn!{ Text::new("Bruh") }).insert(ChildOf(id));
});
Ok(())
})
});
I have the fixes from you and ickshonpe as I'm on the latest commit on next-gen-scenes
Interesting. Is it only when using Text or do you get the same issue if replacing it with Nodes instead?
Good guess but no it does not work with Node either.
Okay, it's not specific to UI nodes at all.
3d meshes don't appear either
Bah, I didn't try to check it earlier but the scene doesn't get spawned properly. @keen patio, It's got nothing to do with UI this time
Somehow the query inside spawn_queued never considers the scene instance component as 'added' on this entity...
Wait. As I type that I just realized..
It's because this entity is spawned inside that system
aaaaa
these subtle details are what can make this "refreshingly simple" engine... defreshing
@split harness I'm having a bit of a problem with spawn_scene and desktop_app. This is part of my popup menu work, which can be seen here: https://github.com/cart/bevy/compare/next-gen-scenes...viridia:bevy:core_menu_bsn?expand=1
The popup menu is initially invisible, because it needs to measure the size of the popup before it can determine the correct coordinates. One frame later, the code changes the visibility of the popup to Visible, but when desktop_app is in effect, that never happens. I tried sending a RequestRedaw event when I spawn the menu scene, but this doesn't seem to have any effect.
If you wiggle the mouse after clicking the menu button, the menu shows up.
Strangely, my other menu example, which doesn't use BSN, doesn't seem to have this issue.
Also FYI @crude pawn , your Virtual Keyboard widget is broken atm in the BSN branch because VirtualKey isn't Clone. Wasn't sure what the right fix is.
Thanks for the heads up. Definitely worth investigating. I'm sadly still in "release prep" mode, so I won't be able to get to this until 0.17 is ready.
I think it may be a scheduling issue; the redraw request is probably coming too late or too early. I think my next step is to make a component whose bundle effect requests a redraw.
Thats an interesting idea! Definitely an easy API, at the potential "design cost" of making a redraw request "structural". The alternative is just sending an event.
Perhaps it should be structural though 🙂
The bundle effect would just post the event
Yup thats how I interpreted it
The difference is when it happens
At the next sync point if we're doing commands I would guess
Does that matter in this context? A redraw request would be handled after the update triggering the event right?
Actually, I'm wrong. I keep forgetting that bsn spawning is async.
Does an asset load request a redraw? Probably not, but it probably should?
For all I know, it already does. The problem in my case is that there are 3 separate delays:
- The observer calls
commands.spawn_scene, which runs at the sync point. - The scene gets spawned after asset handles have been resolved, which creates the popup.
- The popup waits one frame so it can measure the size.
So what we want is for step 2 to request a redraw so that step 3 can run.
Today in Bevy, you would use the SceneInstanceReady trigger for this, right? Is BSN missing that today (been a while since I've scanned the PR lol)
Actually, I might be able to get by just using the added hook for the Popover component. We'll see.
@split harness @raw mesa So, that didn't work. I tried adding an Add observer for Popover, and it never runs (until I wiggle the mouse). So the redraw request needs to be earlier in the food chain.
It's hard to reason about this stuff because I don't exactly know what existing Bevy systems request redraws.
If any system in bevy requests a redraw, it should run - the check for the request redraw happens in the winit runner stuff IIRC, which is after the entire app runs a frame
Are you sure the scene is spawning at all? Like before you wiggle your mouse? It would make sense to me if the scene was never being spawned in the first place due to either spawning too late in the schedule, or because the asset doesn't notify winit to request redraw (if you're spawning from an asset you just loaded)
So there are potentially three places you might need to request a draw:
- you
spawn_sceneafter the BSN spawning system runs - place a request redraw here to let the BSN spawning system run. - the asset gets loaded - this would ensure the asset gets picked up, and BSN spawning system uses that asset. If you're not using an asset this isn't relevant. (does the BSN spawning system run after
AssetServer::handle_internal_asset_events?). This is also not something we can do from user space. - For your particular case, after BSN spawning runs, using the observer
In the worst case, you may need all three of these
I'm not sure I totally understand what "on" is doing here:
fn player() -> impl Scene {
(
Sprite::patch(|sprite| {
sprite.image = "player.png".into();
),
on(|jump: On<Jump>| {
info!("player jumped!");
})
)
}
I would appreciate an explainer
Like I see it's using a closure. Could it also just use a regular observer I define in code? Like could I do
fn player() -> impl Scene {
(
Sprite::patch(|sprite| {
sprite.image = "player.png".into();
),
on(jump)
)
}
fn jump(
trigger: Trigger<Jump>,
...
) { ... }
on main Trigger was renamed to On
Oh sick, thank you
So basically yes I will be able to do that?
on(jump)
That also frees up a nice keyword. I wanted to name something "Trigger" in my project as it made the most sense to me but I don't like to conflict with bevy
can bsn asset files do anything with components that have generics?
or is that not allowed in spec?
Yes, but the reflection for those generics will need to be manually registered or loading that scene will fail
i ask because of discussions bout using rust analyzer as a source parser vs a rustc driver for reflection dumping in #1278871953721262090 and #editor-dev
Not sure how we could support generics with the rust analyzer approach
tho maybe we could ignore that for intial versions?
Yeah I have no opinions this particular conversation / I'm out of the loop
From my perspective this is the plan
(until something changes)
gotcha
The Sprite template is currently not implemented as far as I could see?
Is there any example implementation of a component that needs a Handle<X> where the template is used to retrieve the handle?
This example shows how to do that with a GetTemplate derive / how to use it in the context of BSN
@split harness @thick slate Something I noticed today, I can use NotShadowCaster in a BSN template, but not NotShadowReceiver. Complains about it not implementing Clone.
Slap some Clone on it please 🙂
Yup Clone is needed for these. In cases where you can't slap a Clone on (ex: 3rd party type), you can also use the template() function to produce the type. Ex: I believe template(|_| NotShadowReceiver) should work.
I would also avoid calling it a "bsn template", as a Scene is not a Template
@split harness Another issue I have run into - which I'm not sure is a bsn issue or not - is that I can no longer have a mesh with multiple materials. It used to be that if I inserted more than one material into a mesh entity, it would render each one, as long as they were different types. This is something that I previously used extensively to do outlines using vertex extrusion. However, now it appears that only the last material inserted gets used.
Pretty sure thats true outside of BSN also
I have vague recollections of this changing from some of the recent renderer changes / optimizations
That's bad news for me - I raised this issue back in Feb 2024 and I was assured at the time that this would continue to be supported.
Yup its definitely something we want to continue supporting / multi-pass materials are a common pattern. One immediate option is adding a child mesh entity with a different material
Note that the renderer folks have plans to make materials more "dynamic", and multi-pass is something that should be on their radar there
Worth bringing it up in #rendering-dev
ok
@split harness What I am futzing around with is "gizmoids" which are dynamic meshes generated via a reactive formula:
commands.spawn_scene(bsn! {
:gizmoid(|_cx: &Cx, builder: &mut ShapeBuilder| {
builder
.with_stroke_width(0.5)
.stroke_rect(Rect::new(-5.0, -5.0, 5.0, 5.0));
})
Transform::from_rotation(Quat::from_axis_angle(Vec3::Y, 1.0))
});
(This particular example isn't reactive yet, as the cx parameter is unused)
Unfortunately, it's currently difficult in BSN to share a mesh handle between entities, unless that mesh comes from a loaded asset - that is, you can make two entities with identical asset paths, but if you are creating a mesh from scratch at runtime like I am, things get complicated. You can make a template() that generates a new mesh handle, but then you have to come up with a way to pass a reference to the handle to the other entity.
Creating custom material instances also requires template():
template(|context| {
Ok(MeshMaterial3d::<UnderlayMaterial>(
context
.resource_mut::<Assets<UnderlayMaterial>>()
.add(UnderlayMaterial { color: RED.with_alpha(0.5).into() }),
))
})
"BSN sets" are my long term plan for making this nice. Search for that term here: https://github.com/bevyengine/bevy/discussions/14437
Also open to exploring shorter-term solutions (or alternatives)
I especially think that in combination with "assets as entities" BSN Set-ish functionality will feel pretty good for this (and be reasonably straightforward under the hood)
Ex: if a Mesh is "just' an entity with a Mesh component, and a Handle is "just" a "strong reference counted entity handle", we might not even need a special "asset" keyword in BSN Sets
But yeah thats "phase 2" stuff. Not planning on building it until the core / minimal functionality has landed
@split harness Have a look at my latest bsn tomfoolery: #rendering-dev message
Delightful. Seems to be coming along very nicely 🙂
Just added: now supports line primitives:
:gizmoid(|cx: &Cx, builder: &mut Line3dBuilder| {
let time = cx.resource::<Time>().elapsed_secs().rem_euclid(TAU);
let size = time.sin() * 2.0 + 4.0;
builder.draw_cuboid(Vec3::ZERO, Cuboid::new(size, size, size));
})
The gizmoid() function is generic over the type of builder passed in, so long as it can produce a mesh. If we pass in a Line3dBuilder instead of a ShapeBuilder, we can draw line primitives instead of triangles.
@golden notch @thick slate
On my todo list: support for additive blending modes, which is very useful for overlays because lines are often too dim to make out clearly.
I don't think I'm up to doing all the fancy screen-space magic that regular gizmos use to draw thick lines 😦
There are several advantages that gizmoids have over regular gizmos, all of which have to do with the fact that they are regular retained meshes:
- You can choose what material you like to use
- The mesh can be pickable, or have other user components inserted
- The mesh only gets recalculated when the input params change, other time it's static
Although in practice, for picking, I would most likely use an invisible proxy - possibly using an avian collider - rather than a ray cast.
^ this is something my toolings (like the navmesh editor) would love to use
right now I have some weird runtime mesh generation stuff that pretends it's a gizmo so it behaves the same as the line gizmos
but it's really ugly
For picking, I'd want to give the user some slop, particular if the gizmo shape is skinny and hard to pick.
Currently the gizmoids stuff is tied to (a) bsn, and (b) my bevy_reactor lib. However, you could make a version that supports manual updates instead of reactive.
Not that this isn't interesting, but this feels like the wrong channel for protracted discussion?
True, but I wasn't sure what channel would be the right one 🙂
Probably #rendering-dev 🙂
Tbh when I was starting with bevy I thought that's what gizmos were, since that's how gizmos work everywhere else in 3d software world
With BSN will modders be able to edit your UI?
it'll depend on how much of the UI is tied up in the codebase vs in assets. like with the bulk of XML bevy ui crates there's been.
if most ui is hardcoded, assetless bsn! declarations then that'll be difficult/impossible to mod. if styling is entirely tied up in bsn files then it'll be relatively easy (depending on how accessible the files are to a user)
Thank you
I would like to try out the BSN branch in my 0.17 dependencyless project to give feedback, but by now it’s of course outdated.
I assume the bsn branch will be updated to main when the RC is released?
Yep!
btw is there any point making a PR on cart's BSN branch for no_std compliance right now? I happened to start using that branch for a project recently, and the no_std part fails due to a couple stds that snuck in.
Obviously low priority at the moment, so I can just make an issue or PR later.
A quick PR to his branch seems helpful: those are easy to review and merge
No idea
The macro is just one part of the whole BSN story. It should also be possible author assets in BSN format (AFAIK) on disk
Great if it didn’t have user mod-ability I would just vendor current ui framework (if it stopped being supported)
That is the plan but idk if we have that yet
BSN assets are arguably the most relevant part of BSN for me. I desperately need to move content out of code, and the bsn macro won't help with that
yeah, it's not a part of the plan for 0.18
Pretty sure Cart's branch does not have that yet
yeah, it's the big one for me. bsn! enables a lot of important things, but .bsn & relevant tooling is where a lot of things get much easier design-wise.
I'm hopeful that a minimal asset implementation will be pretty straightforward! Obviously it gets a little... complicated the more syntax you want to support (like arbitrary constructors or associated contants).
I suspect that someone will come up with a "BSN-like" asset format before cart does - but there's no point in even attempting this until the syntax is finalized.
We’re using bevy_proto_bsn from the editor prototypes repo in the hopes that final bsn will be quite close in its syntax
I have really simple inheritance patched on top of it, along with some of our own tooling
Overall I think that having the macro is nice, but once you go asset-side you don’t really want to go back. Instant hot-reloads without any compilation are just a game changer for so many things.
What do people think about fonts as entities
the current font implementation only cleans up unused fontfaces but not the textures for sized fonts
so if you do something like:
fn embiggen_font_system(mut query: Query<&mut TextFont>) {
for mut font in query.iter_mut() {
font.font_size *= 1.01;
}
}
it generates a new texture atlas for every sized font every frame without deleting the old ones until you run out of memory
Afaik subassets would be too limited, something like:
let font_face: Handle<FontFace> = asset_server.load("fonts/FiraSans-Bold.ttf");
Then later:
let font_with_size_10: Handle<Font> = asset_server.load("fonts/FiraSans-Bold.ttf#10");
would require that the FontFace asset loader already created the size 10 Font asset?
And if you hadn't created a size 16 font in the asset loader, this will fail:
let font_with_size_16: Handle<Font> = asset_server.load("fonts/FiraSans-Bold.ttf#16");
and there is no mechanism for it to create a new sized 16 font subasset as needed later on.
I don't know for sure though, maybe it's not as limited as I think.
But even if it isn't it that limited, responsive font sizes seem really difficult to deal with like:
let responsive_font_1: Handle<Font> = asset_server.load("fonts/FiraSans-Bold.ttf#10vh");
let responsive_font_2: Handle<Font> = asset_server.load("fonts/FiraSans-Bold.ttf#20%");
we want to do assets as entities asap
that sounds like just a subcase of that
ah right oh I had heard about that, forgotten it though
yeah fonts is the most urgent because an app just crashing because you cycle the font size for bouncy text or something is just unforgivable
I'm going to hack together a basic implementation to see where the problems are
is bevy_scene2 intended to replace bevy_scene? and will gltf in the future produce impl Scenes?
Unfortunately, I think that font handles are unergonomic; it's cumbersome to have to acquire a handle in order to be able to style a span of text. I would rather see an approach involving some form of ID that allows for tokens to be defined as static consts: that is, I can define MAIN_FONT somewhere in my code and then assign that to text spans wherever I like; somewhere in my app initialization I would register that name and assign a font asset to it.
Ideally, MAIN_FONT wouldn't simply be a reference to a single font asset, but rather to an entire font family. This means that if I style a span of text with MAIN_FONT + Bold, it would automatically select the correct asset.
Right now, if a user wants to style a text span as bold, they have to know the exact spelling of the asset, and there's no consistent universal standard as to how font variants are named.
that could be implemented on top of this approach
with this now if you have an entity representing your main font, if you change its font face or size that changes it all for the text in the app
we could have some way of associating ids like MAIN_FONT with the font entities somehow
Maybe. You'd have to be smarter about font lifetimes. In my vision, you don't deal with fonts directly, there's a layer of indirection, so the framework has a lot more freedom in how it manages font lifetimes under the hood; however, it also means that it doesn't know precisely when a font is no longer needed, so deciding when to uncache a font atlas entails a heuristic of some kind.
I'm thinking that MAIN_FONT would either point to a FiraSans.family.ron asset, or you would manually register the different variants/styles at startup.
yes that's the motivation behind making them entities mainly, we can just query for a list of all Font components and them compare them against the list of font atlases and delete any fontatlases that don't have a matching font component
Some of these ideas were floated in the old lorem ipsum working group
Based on the discussion #engine-dev message I think SmolStr would be a good candidate for font alias ids. They can be declared statically, and support O(1) cloning.
Anyway, we should probably move this discussion
Is this an interning lib?
No. It's similar to CowArc, except:
- Has an option for storing small strings inline
- Doesn't have an option for borrowing
The lack of borrowing actually makes it simpler since you don't have to deal with lifetimes.
Yes that is my goal, but until we have World -> BSN serialization support (in addition to BSN -> World), I was planning on keeping the old system around in the interest of supporting those scenarios
And World -> BSN is not a particularly high priority for me at the moment
okay. I'm asking because I was looking at this and wondering if it makes sense to do that if BSN lands in 18 and the fix to that issue would land in 18 + I read a comment in the message rename PR that mentioned something like "this scene system being on the way out"
I'm pretty bullish on "canonical roots for scenes" / BSN forces that
I think its worth designing the api in the context of the BSN data model
Rather than the current scene system data model
If I'm understanding the next-gen-scene branch correctly then bsn scenes feature nested scenes much more heavily (one scene per entity?) than current scenes like gltf (which are a world of entities), so pushing some relation from the bottom of the tree to the top-most entity sounds difficult.
I think what would be really powerful, both for making these AnimationPlayer relationships but also for patterns I've seen in the ui examples, would be a way to say "these N scenes/bundles/entities are related with this relationship" and then inserting them at any position in the canonical child-parent hierarchy. If something like that exists or it's been talked about I haven't found it.
then inserting them at any position in the canonical child-parent hierarchy
Can you outline a specific case of what you'd like this to look like?
#[test]
fn related_spawn() {
let mut world = World::new();
#[derive(Component, Default, Debug)]
#[relationship_target(relationship = LikedBy)]
struct Likes(Vec<Entity>);
#[derive(Component, PartialEq, Eq, Debug)]
#[relationship(relationship_target = Likes)]
struct LikedBy(pub Entity);
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
let (source, target) = related::relate_bundles::<LikedBy, _, _>();
let parent = world
.spawn((
Name::new("parent"),
children![(Name::new("alice"), A, source), (Name::new("bob"), B, target),],
))
.id();
let &[alice, bob] = &world.entity(parent).get::<Children>().unwrap()[..2] else {
todo!()
};
assert_eq!(world.entity(alice).get::<LikedBy>(), Some(&LikedBy(bob)));
}
or something else that makes it ergonomic to insert these kinds of relationships. I guess the assert is more important than the specific way I got there
With BSN Entity Names
let parent = world.spawn_scene(
bsn!(
#parent,
Children[
(#alice, LikedBy[#bob]),
(#bob, LikedBy[#alice]),
],
)
).id();
Although I guess the Relation syntax would need to support a mix of both Entity Names/ID's for existing Entities and Scenes/Bundles for spawning new Entities. 🤔
or just use them with normal Component syntax, although then you lose the nicety of knowing what is an Relation at-a-glance.
oh this is how entitypaths/ids are meant to work? could you do something like
bsn!(
#my_entity
[(:scene_relates_to(#my_entity)]
)
or
let my_entity = magic();
bsn!(
{my_entity}
[(:scene_relates_to({my_entity})]
)
@split harness Here's a partly working example of how we could get rid of all that crap in the ui_widgets demo:
fn button(on_activate: CallbackTemplate<In<Activate>>) -> impl Scene {
bsn! {
Node {
width: Val::Px(150.0),
height: Val::Px(65.0),
border: UiRect::all(Val::Px(5.0)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
}
CoreButton {
on_activate: {on_activate.clone()},
}
Hovered::default()
TabIndex(0)
BorderColor::all(Color::BLACK)
BorderRadius::MAX
insert_dyn(|cx: &Cx| {
let entity = cx.owner();
match (
entity.contains::<InteractionDisabled>(),
entity.contains::<Pressed>(),
entity.get::<Hovered>().map(
|hovered| hovered.0).unwrap_or(false),
) {
(true, _, _) => NORMAL_BUTTON,
(false, true, _) => PRESSED_BUTTON,
(false, false, true) => HOVERED_BUTTON,
_ => NORMAL_BUTTON
}
}, BackgroundColor)
[(
Text::new("Button")
TextColor(Color::srgb(0.9, 0.9, 0.9))
TextShadow::default()
)]
}
}
I say "partly working" because it doesn't react to component removals.
Within the insert_dyn block, we are grabbing cx.owner(), which is of type TrackedEntityRef.
This is like an EntityRef which also records access to components (currently only supports two methods).
So the key problem, which we have wrestled with before, is detecting removals. That is, I can easily detect whether a component has been removed, but not whether it has been newly removed.
The code for detecting component changes currently looks like this:
self.component_deps.iter().any(|(e, c)| {
world.get_entity(*e).is_ok_and(|e| {
e.get_change_ticks_by_id(*c)
.map(|ct| ct.is_changed(self.tick, tick))
.unwrap_or(false)
})
})
If I change the .unwrap_or() call to true, then the reaction never converges, because it continues to react to old removals.
Could this be done with observers or hooks instead? Honestly, I can't think of a way to do it, at least not in a way that is any kind of efficient.
There's a fundamental tension that we haven't resolved: observers like marker components but hate mutation; change ticks like mutation but hate markers. You're damned if you do and damned if you don't.
Observers could be made to work with mutable mutation, wouldn't be that hard honestly. I've mentioned before, but you just make a new QueryData wrapper or change Mut<T> to emit events as part of its command forwarding to the Query in apply/queue. Although I know cart wanted opt-in change detection first.
That's not the only problem, though. Another problem is registering multiple observers for every component that you might want to react to.
I wonder if a design like unitys (ECS Dots) enabled components would help solve this problem.
It prevent archetype moves while make changes detectable.
Looking good! TrackedEntityRef makes a ton of sense! I do think better removal tracking (especially for marker components) is worth investigating.
Yeah definitely doable, but tracking those mutations in Mut introduces overhead and threading concerns if enabled by default. And using a different opt-in query param makes it error prone. We could add that behavior statically as part of the component derive (making the overhead + other tradeoffs opt-in), but that also requires the author to anticipate those downstream use cases, and they must be willing to "pay the price" across all use cases
I've been wracking my brain trying to think of alternatives: like having a component change journal (like RemovedComponents, but for all operations), or some kind of removal bloom filter (fixed memory cost, but probabilistic, which for reactions is probably ok).
Hmmm false positives are definitely less of an issue for most reaction cases (especially in the ui space), but that removes a whole class of functionality that relates to "lifecycle-driven data integrity"
Not to mention that the "may have false positives" aspect of it would probably be lost on some people / thats hard to document
I think that part of the difficulty we have in talking about all these designs is that so much is theoretical: that is, we have some toy examples and demos, but we don't have a real, large app to use as a basis for evaluating the merits of various approaches. This also applies the controversies around observers vs. callbacks.
Agreed. Having all of these fancy new widgets in place certainly helps us in that department
derived render resources (textures sized by swapchain, buffers that depend on other buffers, etc) i think are an ideal non-ui use case to keep in mind as we consider generalized reactivity
Yeah this is true, multiple event observers is something I'm still thinking about, but Query Events are an alternative that encapsulates the events into query params and a single event. (assuming a single query works to relay the same semantics)
True, I see it like:
User wants Detection: React<C> query param wrapper
Author wants Detection: Component derive built-in.
I dont know what other path you could take, gotta be provided by the User or Author(or both?).
Thinking about it more though, I don't think Query commands are considered by the auto-sync-point in Schedules, so system_b.after(system_a) wouldn't see the results of any Query commands. 🤔
If it did, almost all systems would be exclusive 😢
what was the reason for not having comma seperation in the bsn macro?
intuitively id expect to have comma seperation
even copilot wont shutup about expecting a comma lol
Is ordering of the observer execution (similar to system ordering) possible?
I encountered a few instances where that would have been very useful.
Instead I had to opt into one observer which then triggers other observers in the right order.
as i understand it, observers are injected into the system graph to happen asap
idk of a built in ordering for them
They don't even make it into the system graph, they run in place when the commands that trigger them run (or immediately if you trigger with &mut World), since they never get collected there's currently no oppoturnity where you could reasonably order them.
With the Event/Trigger rework you can have youre own ordering, although you only have access to the ObserverRunner currently, if the Observer Entity were stored alongside you could potentially lookup the Observer Entity for metadata on ordering/sorting.
It's still in the bikeshedding phase iiuc. Cart's pretty strongly in favor of "less punctuation = less clutter" while the other camp is "make it look more like Rust". Last I checked, Cart's official position is "I could be persuaded but it's one of the least important design decisions at the moment". My read is that the discussion will eventually need to be had, but it shouldn't be frontloaded. It's mostly just an opinion thing that might ultimately boil down to a poll, no reason to waste developer hours debating it when there are so many other more productive discussions to be had in the space.
Don't want to put words in Cart's mouth, this is just my understanding.
Although the Copilot thing is a point in favor of commas that I hadn't seen brought up ||unless you want to encourage people not to use LLMs with Bevy||
While discouraging LLMs has some justification, I don't think that making things more painful for LLM users is the way to go about it.
Agreed. The joke was probably in poor taste in retrospect.
It was fine 🙂
I'm just a cynic
designing apis with a preference for llms instead of humans doesn't sound like a great road to go down 🙃
The question is less about ensuring LLMs work properly, it's about ensuring the out-of-box experience is good, and for better or for worse, the out-of-box experience for some devs includes LLMs.
Although I hear that there's a whole new industry of opportunities for experience developers fixing up bad vibe coding.
Doesn’t more rusty syntax also help rustfmt? Or does rustfmt not process proc macros anyways?
I did some experiments on this with i-cant-believe-its-not-bsn.
TLDR: rustfmt can format macro invocations if the tokens are enclosed in () or [] and can be interpreted as valid rust (syntax-wise).
https://github.com/Leafwing-Studios/i-cant-believe-its-not-bsn/pull/14#issuecomment-2599743084
As for bsn!, only adding commas won't make it fully rustfmt-compliant.
After playing around with it a bit:
- The
#prefix for names is not valid syntax - only works if it can be parsed as an attribute, options that do work:- Replace
#with a token that can precede an expression syntactically. - Use a different way to name entities, where the name is not part of the list of entries, for example using an assignment expr:
MyEntityName = (ComponentA, ComponentB, [ <children> ]) - Skip shorthand and use
Name("...")instead
- Replace
@-prefix for templates is not valid for the same reason as the name prefix -@can't precede an expression syntactically:-prefix for inheritance suffers from the same issue
All of the above could be solved using tokens that are allowed to precede expressions, for example: !, -, *, &. But those come with confusion/ambiguities as they are also used within the expressions.
Designing BSN around what is valid rust syntax may be a footgun for this reason. Instead we could add a formatter to bevy-cli, like Dixous does with dx fmt 🕵️
Its more that, if something is so common that the llms think its necessary even tho they haven't had any time to train on bsn, it implies its statistically likely to be expected by people.
I don't really want to have an extended discussion about llms, especially in this channel, (and I really don't have an opinion about commas either), but its trivial to find multiple counter examples that doesn't use commas. I maintain that designing APIs based on what AI happens to hallucinate today is not the way we should be designing APIs.
It’s just something to keep in mind imo. The relevant data point is "statistically, in similar contexts, a comma is usually included". Let’s not forget that behind all the smoke and mirrors and big talks by CEOs LLMs are just statistical models.
Obviously no one wants to base the design on LLM output, I don’t think that’s seriously on the table 🙂
But I also don’t think we should ignore statistical insights just because they happen to come from an LLM. Just treat it as a tidbit of information about how past languages were designed that we can deviate from if we want.
"statistically, in similar contexts, a comma is usually included"
html doesn't use commas, which is arguable the most widespread similar context (kdl, yaml, toml, etc also don't). The "statistics" here are carrying a lot of weight for not referencing any actual statistics or examples.
Can’t leave JSON out which is much more widespread
okay but JSON commas suck haha
(for humans anyway, not parsers)
Of course there is value to precedent! Although I'm not ready to call BSN's commas a bad idea yet. I value the concise authoring, which I expect to do a lot of.
Comma separation just makes sense to me, like if we take bsn, subtract rustfmt's attempts to space things, we end up with stuff like
bsn! {
Node {} BackgroundColor()Hovered::default()TabIndex()
}```
Atleast having commas you get
```rs
bsn! {
Node {},BackgroundColor(),Hovered::default(),TabIndex(),
}```
For an apples to apples comparison, also remove the spaces from the commas example
Though I don't think picking the worst possible usage of a syntax should be the guiding example, nobody is going to do that
True, my keyboard auto added em lol
What I did is make commas optional unless it's a plain list of values, so
Position: {10 20} // NOK
Position: {10, 20} // OK
Position: {x: 10 y: 20} // OK
Position: {x: 10, y: 20} // OK
which means you can do this, which feels natural
Position: {
x: 10
y: 20
}
personally I prefer "one correct way" so it is not a point of confusion for new users looking at their code, example code and code from people that try to help (and might suspect a missing comma is an issue themselves as well)
"one correct way" and "succinct" are often at odds, going extreme in either direction IMO ends up with less than ideal syntax unless it's really simple (like JSON)
I see that can be the case, I just don't see it with the question of optional commas
certainly the amount of spaces should not matter for example
Right now the BSN syntax has more serious problems: it has an ambiguous grammar. The parser cannot reliably distinguish between:
Marker [children]Relation [related]
Which is why the Implicit Children semantics shouldn't exist.
Children should just use the Relation syntax, Children[]
I feel that's far too verbose.
tbf there are other ways to disambiguate
and implicit Children is nice in a lot of cases
How, you will use so many more Relations it makes no sense to say that?
Will you?
Yes? 🤔
maybe in some cases you'd use mostly custom relations, but UI for example?
or plain 3d scenes?
At least in my own UI code, where I fairly often use custom relations, the vast majority of relation bundles are just children.
In other words, heavily hierarchical structures like UI also tend to be the most verbose hand-authored things. I would really appreciate some concise syntax personally!
Concise to you, is implicit and magical to others, though.
I think there's a balance to be struck, but I don't feel that some concise syntax for Children is too far. It's pretty fundamental.
Thought experiment: how do you remove Children from Bevy and replace it with you're own Parent/Child Relation? Its relevant because this implicit-ness further intrenches the Children type to not be able to replaced.
I don't see how that's particularly valuable, personally. You'd just fall back to the standard relationship syntax in that case.
Being everything to everyone is not always ideal since you can't take shortcuts.
Hmm fair enough, but I am still on the explicit syntax side, for what its worth. 👍
I don't mind Children having special notation, I just don't like that it's ambiguous
right that's definitely not ideal imo
Even just a single sigil would help; in the scenario where you find out that Children actually isn't what you want, you could pretty easily find/replace, similar to the general syntax. The current implicit approach denies this.
could also require some extra syntax between relation name/items: MyRelation: [], MyRelation=[]
cart said he didn't like that so much, just mentioning alternatives
The way I went about it is
parent { // implicit hierarchy
child_a {}
}
child_b {
(ChildOf, parent) // explicit hierarchy
}
// 'implicit' hierarchy for other relationships
(IsA, Thing) {
(IsA, Organism) {
(IsA, Plant) {
Tree {}
}
(IsA, Animal) {
Human {}
}
}
}
that seems especially nice because the explicit syntax closely mirrors what a relation is in flecs and also how it's declared in normal code (as far as i understand). idk if we can do that though 
yeah that's why I went with it, in code you do
e.add(ChildOf, parent);
::<ChildOf>(parent) in bsn, clearly
it's concise, consistent, clear and unambiguous
no support for runtime relationships? 😉
I suppose that requiring mandatory commas between components would resolve the ambiguity:
Marker, [children]Relation [related]
oh i suppose so
bsn_list! {
(#parent),
(#child, ChildOf(#parent)),
}
I think fragmented-relations would look like normal Relations, even in BSN 🧠
What is the #?
Name shorthand, but I think it could also be used as an Entity Variable.
Oh lol, that's funny. In my case # indicates an entity id, like #500
Why not just use raw numbers, just like a unit-prefix I guess?
Because entities can have numbers as names
Why does that prevent you from just using raw numbers 500 as an EntityID? #500 for an entity with Name("500") and raw numbers for providing an EntityID (ChildOf, 500)
500 { // entity with name "500"
}
Ah right, you dont use # for names lol
#500 { // entity with id 500
}
Hmm interesting, I think I prefer #name syntax, and leaving 500 as an ID/number.
The reason is also API consistency. Identifiers are looked up with the lookup function, so:
world.lookup("foo"); // find entity with name foo
world.lookup("500"); // find entity with name 500
world.lookup("#500"); // return entity with id 500
I used to treat numbers as entity ids, but that's annoying since a number can take lots of different shapes
That's a bit weird though, names are the default way to refer to something multiple times, not ids
It's like having to prefix every identifier in rust with #
Besides, in this example ChildOf would be a separate kind of identifier (one that doesn't require #) which is is not very elegant
A single word doesn't feel verbose
Hell its just making it follow a consistent syntax for relations
A single word doesn't feel verbose
Take the HTML of any webpage, and insert<children></children>around all element children. It's pretty verbose
Nope makes it obvious for someone who doesn't have full knowledge of htmls ins/outs
I think its a cost worth paying to teach and have a special syntax for parent children
I agree. The language is designed specifically to (mostly) describe hierarchical structures
All relations create a hierarchy in some form, picking one to favor is silly. What if another, to be created, relation becomes as important as Children? Do you make another special syntax?
What if another, to be created, relation becomes as important as Children?
I don't think that will happen
For example, IsA
This is "verbose"
I do have syntax for that tho :p
e : SpaceShip {
}
// same as
e {
(IsA, SpaceShip)
}
this is sarcastic right? sorry if it's obvious 😅
If you were to design a generic language for expressing graphs, then I think I agree with you. But that's not the use case for flecs/bevy
It's a language that's specifically designed for games/scenes, and those tend to be hierarchy heavy
Ah, I think Bevy patches ontop instead of using a Relation for this 🤔
Html is obviously not complex lol, just the language that was being used for example
Anyway, I don't think it's really worth worrying whether Children is important or special. It obviously is; it's been that way in Bevy for years, and in other game engines for basically as long as they've been around.
Parent/child relationships are often given special presentation. For example bevy_inspector_egui, the gameobject inspector for Unity, and the node inspector for Godot all privilege this kind of relationship.
I can see your point, but still disagree, an ecs with Relations is a general graph framework, and bsn is a macro for easily setting up nodes/entities and edges/relations in that framework
Either way I've stated my points, back to lurk 
Other OOP engines will of course, but those engines also have different Relations they just don't expose them or appoint them with any importance.
That is a difficult position to maintain. What about hierarchical names? Will you also do something like spaceship -(ChildOf)-> cockpit instead of spaceship.cockpit?
(which is what generic graph frameworks do, but jikes :p)
The example I like to use for alternate relations is a treasure chest in an RPG: the chest "contains" items, and is arguably a parent, but we don't want to use the regular ChildOf relation - because we don't want to render the contents of the chest in the game world. The words "parent" and "child" are an imprecise metaphor (the parent didn't literally give birth to the child), and are typically used in software to define hierarchies generically without any implied semantics. (I think the source of the metaphor is the structure of geneological ancestry trees).
In Bevy, the "child" relation does have implied semantics, although the meaning is slightly different depending on whether we're talking 3d or UI (3d children have no layout behavior for example). Picking also treats the child relation as special.
They are the "default" relationship, so it is used for alot of things, other than transform
When I'm developing, I like to group things together using parent/child relations to scope things together, like if I have a tilemap, I like to make all tiles for that tilemap as child of that tilemap, so it is easy to query and to reason about. Also this looks great on Editors,
All this to say that ChildOf should have a special notation, since it will be the default/most used relationship. The point is, as the other said, this default can't be ambiguous
I unironically like the first 😂 🤡
Then we will never agree xD
I think there's a heuristic programmers use where general, consistent solutions to problems are favored over bespoke solutions. There would be a certain conceptual elegance to treating all relationships the same. It would satisfy the afformentioned heuristic, but I think this is a time where that heuristic fails and a special case is favorable. I think this is thoroughly agree-to-disagree territory tho
We could settle this real quick
Let's just ask a bunch of non bevy related people what looks better/makes more sense/has better clarity.
I think our bevy centric knowledge is clouding the objective discussion on the syntactic clarity of bsn
Side note: I know its goofy to talk about 0.18 at this point but if 0.18 dropped next month with just the bsn! macro I'd be stoked. It seems like the bevy dev cycles are getting longer and I don't want to wait 6 months for bsn (even if it comes with 10 other cool features) if it's actually working on main this time next week. That said I don't know what the actual status on any of this is, so consider this 'just yapping'
Just curious, but in flecs how would you do this, if a different Relation needed to be walked to get the name? Say for example the user needs a custom relation, and can't use Children because they don't want to support picking/transform prop/etc. Which children implies.
I don't think that's a very good metric. If you asked business people what makes a good language, you end up with a disaster like COBOL.
I think the Bevy user perspective is important! We're the ones using it after all haha
I'm pretty sure asking other rust users/and game devs what looks clearer is a pretty good metric.
The bevy user perspective is all we have here, we need non bevy people to cut through the bevy bias rn
You don't have to ask, just look at prior art. Find me any example of a language that's used to describe UIs or scenes where hierarchies are not a builtin concept
This is a thoroughly explored design space
Yup making it fully rustfmt-able is a non-goal / would overly limit us. The reason BSN exists is to reach beyond the limits of rust semantics
This is essentially my position, although I would never leave a design decision to a poll
Sure, built in exists, but they still have clear cut syntax, not []
Its no more clear to someone than {} or ()
[] this means nothing to a non bevy except "list"
There's also the argument of consistency, special casing a single relationship is weird, thatd be like strings not needing "" but string slice do
In fairness, neither of those is valid Rust, if you consider bsn!{ } to be a "rust expression
Yup this is priority #1 for me once I'm back on the BSN train
Yea I wasn't aiming for rustness, just intuitively what I expect when giving a bunch of something in a list
It would, it just makes everything read very weird. Only really makes sense if we also make surrounding it with(Marker, [children]) required
So right now if you have ChildOf on an Entity you get a set of behaviors, and there is no way to pick and choose.
In ECS, you would break apart that monolithic Component/Relation into multiple, so that the existence of the data is the existence of behavior.
The more things you add to children the more monolithic it becomes, and more knobs you need to adjust to get acceptable behavior (this is OOP)
Tbf I was already doing that in all my code because it ensures I don't mix what entity I'm looking at
Just in pure curiosity; when are you back on the BSN train?
As soon as 0.17 is out
I'm doing release prep atm
I don't expect comma separators for things that aren't enclosed in [] or (). The reverse in fact
Ex: do you expect A or B?
// A
fn x() {
}
fn y() {
}
// B
fn x() {
},
fn y() {
}
In most languages, things at that level are separated with spaces, newlines, or ;
And ; is clearly a bad fit for this context
I think of a bsn entity spec moreso as parameters, so , works in my mind
i think this is a place where BDFL power is acceptable to use btw. whatever cart says goes on these minor syntactical debates imo
obv that doesnt mean dont question it
but like im pretty happy not needing to have an opinion on this lol
as long as the grammar ambiguity w.r.t children is solved some way, I don't mind which way
Macros can use any brace, right?
So if it was
bsn!(
Name("Ent"),
Marker,
)
But it is enclosed?
By {}
I'm explicitly going to recommend people never use (), in the interest of ensuring our future auto-format tooling doesn't fight with rustfmt on "conincidentally rustfmt-able" bsn!() invocations
That is compatible with my statement that aren't enclosed in [] or ()
From a rust perspective, {} context and fully unenclosed context are essentially the same from a "syntax expectations" perspective
Hmm what about ; then 🤔
Try porting the current bsn feathers example to ; and then get back to me on how you feel about it
My personal feeling is that it makes me incredibly sad
Flecs doesn't support name lookups for other relationships. ChildOf is special here, which is the point I'm trying to make
Name lookups for other relationships don't really make sense, you'd get multiple unique names per entity, potential name clashes when you lookup using a different relationship etc. Messy
Do we really need to be concerned with coincidental formatting by rustfmt? We could just run rustfmt first, and then make another pass with the bevy formatter (and provide RA configs to do so)
"We" is a bit out of our control. IDEs with auto-format enabled will do what they want, in an arbitrary order. If we're running two formatters (bsnfmt and rustfmt), they should produce the same outputs in both orders
Not to mention that developers should be able to run rustfmt and bsnfmt whenever they want manually without needing to worry about order of operations
Good points! 👍
lol didn't mean to kick off a comma war. It did remind me though: can we expect ChildOf(x) to work properly in bsn? My biggest gripe with bsn is that it encourages nested hierarchies which traditionally wreak havoc with most diff tools. It'd be nice if I could phrase my bsn as:
EntityA(A, B, C)
EntityB(D, E, ChildOf(entity_a))
It's one of the reasons I like godot's scene format, even though it's not exactly the poster child for "human editable"
That's how the current scene format works right?
Yes this will work, provided that entity_a is addressable from the current context
See the #Name proposal
That would only really make sense in the context of a bsn_list!, and only one that isn't passed in like this: Children [ some_bsn_list ]
As bsn! assumes a single root
And every other entity defined in bsn! is already a child of something
I'm thinking of the asset, will a .bsn file be a bsn_list! or a bsn!?
Open question. My default answer is bsn!. There is also the question of "bsn sets" (see my design doc)
Cart, when you have time to work on the BSN branch again, would you mind maintaining a branch that uses 0.17.0 and not 0.18.0-dev? I believe quite a few people, me included, would like to beta test BSN the coming cycle, and this way we don’t have to patch all our dependencies to point to 0.18.0-dev or fork your branch ourselves 🙂
I am begging for this
Hmm I think this might be doable. I think bevy_ecs changes constitute the biggest "maintenance risk"
I will give it a try, but I also don't want to get bogged down by it. My first priority is landing it in 0.18
i think just having the current bsn branch updated for the 0.17 release would be enough, doesnt need to track further developments as it gets closer to landing in .18
Yeah that’s exactly what I meant 🙂 no need to maintain it all the way, just have an initial version of BSN that works with 0.17
Ah yeah that seems very doable
You already have my next-steps wishlist 🙂
Yup 🙂
Can't wait to be back on the BSN bandwagon
Thanks a bunch! Just remember to set the version to 0.17.0 instead of 0.17.0-dev or such to allow people to easily [patch.crates-io] it 
Since there are no BSN assets yet, I think only binaries profit from patching for now haha
what do you think about renaming the style fields and enums so "items" becomes "children"?
so "align_items" -> "align_children" and "AlignItems" -> "AlignChildren"
It would make it slightly more awkward to translate from CSS to bevy style definitions but I'm not sure many people do that anyway
it feels to me like there is a lot of confusion among users because we use the flex "items" and "containers" terminology a lot
that might be cleared up if we uniformly replace them with "children" and "parents"
oh no, spent the last hour trying to debug the rem PR:pub fn rem<T: ValNum>(value: T) -> Val { Val::Px(value.val_num_f32()) } 😅
I translate from CSS all the time. I know Bevy tries to be its own styling thing and not replicate the web too much, but CSS is the common language I speak with UI designer friends when asking for feedback
Not saying this is a counterpoint to a rename, just mentioning I exist 😄
Yeah I guess I have done it a lot as well 😄
I guess I think that understanding that "items" means "children" is obvious to users well versed in CSS, but not so much to anyone else
Yeah the alignments in CSS is very confusing and gladly we have hotpatching now so I can just try the different elements.
But keeping it the same as CSS is probably the right path unless its planned to completely break loose from css in multiple instances, then changing the naming would make it obvious that its not meant to be used as a direct reference.
Adding a note about what containers and items means would be good
Yeah I think there is interest in alternative layout algorithms, so my vote is we keep it as-is for now, document the correlation to children, and use "children" if we adopt an alternative algorithm
see the argument here re: standards. i am no css lover by any means, but i think some people undersell the benefits of being css based? like for all the problems of css, there is practically infinite css learning material and content in a way that a custom layout system will never match. absent other alternatives, i think it’s worth leaning into rather than trying to obscure imo. if we can definitively do better than web tech in a way that fits our idioms, by all means, let’s. but being based on the most well understood and widely distributed platform ever is a strength not a weakness
As long as we are fully compliant, and not mostly compliant 🙂
(and there's a fallback / alternative for the cases and users where it doesn't work well)
Being fully compliant would likely drive people insane (css has many weird behaviors for legacy reasoms after all), but deviatig arbitrarily probably would too.
That said, alternative layout options would be amazing. We have a much improved spawn API now, and BSN should improve this, with good reactivity and an asset version of BSN it might even enable good hot reloading. But even then the guesswork involved in CSS-style layouts is wildly frustrating
-# Morphorm/subform style layouts when?
It's widely recognized, even among CSS partisans, that some parts of CSS are better than others. I can say this with some justification because recent feature additions to CSS have striven to overcome some of the weaknesses of earlier design decisions. For Bevy, we need to do two things:
- Adopt the best parts of CSS
- For those parts where we deviate from the CSS spec, make sure the dividing line is easy to explain and understand.
For example, one of the most confusing parts of CSS is the cascade: the rules for prioritizing property overrides based on selector specificity. I don't think that anyone is advocating adopting this part of CSS in Bevy, other than people who are doing third-party crates that support all of CSS in a compatible way.
You can think of CSS as having multiple parts:
- Syntax and Structure
- Selectors
- Static
- Dynamic (psuedo-elements, etc.)
- Cascade and Specificity
- The Box Model
- Layout Systems (flex, grid, block...)
- Typography and Text
- Colors and Backgrounds
- Visual Effects (animate, transition, borders, shadows...)
- Responsive Design (media queries, viewport meta, flexible units)
- CSS Functions (min, max, clamp, calc, etc.)
So for example, Bevy today does not support CSS syntax or selectors, but does support the box model and layout
To be fair, there are more options than CSS or NIH. Another of bevy's strengths is our ability to choose newer standards. Webgpu is relatively new, but we're on the front of it. Getting in front of a newer layout standard is an option I would really like: it saves us inventing something, it presumably already has documentation, and it's ideally nice to use. Obviously if no such thing exists, I agree CSS has a lot over a custom solution. Personally one of my favorite things about bevy is its lack of baggage, but UI is kind of the notable exception (I realize there are many reasons for this)
My two cents
what other options are there for newer layout standards?
As a taffy maintainer, I am very open to adding more layout standards, especially established ones that are stable with a nice spec
Honestly, I might not really be the best one to be starting that conversation. I don't do much UI, and even less UI framework dev. That's mostly why I added the disclaimer that if no such thing exists, we can stick with what's there. I guess I just assumed there were other options: I know morphorm uses its own algorithm, there are more basic things like Android's old layout system (or its new ConstraintLayout) or Godot's containers, or more recently I've been seeing composition-style layouts like Flutter, SwiftUI, Compose, Elm, etc. I realize those aren't equivalent to CSS, they're only layout algorithms/engines. Tragic backstory to help you understand where that initial comment came from: I just recently was exposed for the first time to what it's like trying to use HTML/CSS raw without any framework on top, and I found the experience surprisingly bad. CSS is happy to conflate layout and theming into the umbrella of "styling", meaning things that should be local (like layout) get thrown into the same pot as things that are more global (like theming). Godot's UI clearly separates them: layout is done by nesting Controls, and things like text color, font size, background image, etc. are nicely encapsulated in "Themes" that propagate downward.
The tl;dr is: I may not have useful advice ||but I do have a dislike of CSS||. I assumed there were more modern algorithms/models that we could take advantage of, but thinking about it more deeply, I realize there's more to it than just "replace the layout engine". If CSS really is the state of the art, I'm sad, and I think maybe a custom solution could win, but I can't really argue against the status quo more effectively than that.
I'm not against there being better solutions, but for the most part I've never seen a layout approach that a bunch of people didn't really hate, and I don't think that's an API problem (and especially not a problem "one better api" could solve)
And thus the unfortunate conclusion: we use the layout approach that some people really hate, but has infinite learning material and resources available
I guess I shouldn't say unfortunate
It's only unfortunate for the people that really hate it
there will always be that group, even if its different people. and the learning material and resources is a big win
But all the other solutions are unfortunate for everyone because they're less discoverable/learnable
i do think that there’s been a lot of learning in the ui space over the past decade and would rather write ui in 2025 than 2015 but there’s a reason people keep making endless new approaches. i think ui is just kinda hard and uncomfortable in general
Simple solution: just get rid of users 😩
blaming your users for your bad ui, a tale as old as time!
I think enabling alternative solutions to exist is good, but css is also a really good default given many considerations
btw @thick slate i used taffy on my last contract for a c++ project and it worked great!
i convinced them not to implement an ad hoc layout engine lol
Most people that dislike CSS can point to specific things they don't like but aren't in a good position to fix it. You'd probably need a CSS expert that understands why everything is the way it is to propose an alternative
Otherwise you'll just end up with just another layout API
I think that any alternate layout algorithm would have to meet a high bar with respect to documentation. The canonical "how to use flexboxes" essay is something like 6 pages of text and illustrations. You're not going to compete with this via a couple of paragraphs in a rustdoc.
To be fair, my biggest gripe is simply conflating theming with layout, which seems like an easy fix
@tawny stone is morphorm stable enough to be added to taffy?
Not inherently opposed to flexbox
I'd need to ask you what you meant by theming to understand this, since things like spacing also fit into theming in my mind. "anywhere you can stick a variable" is basically design token potential
(though I'm not convinced that you couldn't* get away with a much simpler and more performant home grown alternative if game UIs are your only use case)
This makes me very happy to hear 😄 @glossy tide will be enthused too.
Last time I checked the answer was "no"
Thanks both of you!
I do think we should support a simple anchor-based layout solution, both in Taffy and Bevy
is "anchor based" here just absolutely positioned everything?
No, relative to the eight compass-directions + center in the screen
yep, that's basically what I did for my UI prototype as well
Which is great for things like "life bar" and "minimap"
In the last 5 years there has been a lot of emphasis on "css-in-js" or "scoped css", which is the web dev's answer to the globalness of CSS.
The problem with CSS's rule-based approach is that adding a new CSS rule can have broad impacts which are hard to reason about. For example, there's no automated way to detect dead CSS rules.
yeah, I've done work on emotion, etc
Could go either way, but my point is that I don't think text color and list layout should be in the same place. Again, look at Godot. Their way is sane enough, and imo is much nicer than CSS. I'm sure some people hate it though 🙂
It's not that no one has bothered to write a dead-rule detector, it's that you can't write one.
A browser couldn't tell you which CSS rules haven't been referenced?
A browser cannot prove that the CSS rule would match some dynamic state that could potentially happen, but hasn't happened yet.
ah, so floats?
Link link? I haven't heard that term before
Right ofc, but instead of static analysis you could do dynamic analysis, which would still be very useful
For example, a DOM element might have a class name that is algorithmically generated.
This happens more often than you might think
I doubt it, I do this all the time
On the plus side, the set of use cases that CSS encompasses is vast: I would be very surprised if there are any alternate layout algorithms that are a superset of what CSS handles.
hmm, this isn't going to have multiple elements in the example, but float is a really old css feature that got replaced by flexbox/grid/etc: https://developer.mozilla.org/en-US/docs/Web/CSS/float
basically "stack to the left", "stack to the right", etc
BSN is pronounced "bee, es, en", and stands for Bevy Scene Notation. BSN is just the scene format, the rest of the scene system isn't inherently BSN imo
