#Next Generation Scenes
11848 messages · Page 12 of 12 (latest)
Its just sugar for what I was expressing
anyways. I sense you're pretty set on @SceneComponent and not my *SceneComponent idea?
But yeah I agree its much nicer. That has always been the dream. Glad we finally have it
Yeah
Allright, should i see how hard that is to get working right smack-dab in the middle of my docs and tests changes?
make it a proper "a lot of bsn changes" PR instead of just docs/tests?
or rather split it out?
Split it out please 🙂
We also still need new syntax for template patches
what about * for those? we kinda need some new symbol no?
I don't like that it reads as a dereference
that was why i thought it fit...
hm
what about just .?
tho that can read as a call chain
# isn't ideal because attribute macro. or maybe that works as attribute macro requires #[
$Template
%Template
>Template
~Template
=Template
Kind of weirdly ambiguous:
bsn! {
Foo
.Bar
}
vs
bsn! {
Foo.Bar
}
Foo.bar vs Foo.Bar isn't actually ambiguous if we don't want it to be. But its a weird distinction imo
yeee
Also #Name syntax 🙂
🤦
Perhaps we should just cut template patching for now. I think it will come up eventually, but its niche
perhaps. we could also just change the prefix to something but leave it undocumented in case it does come up?
I want to save -Thing for "removal patches"
the list is getting smaller
If template patching is cut I'd like to make sure one-shot system templating is in 0.19 (I have both a prequel PR posted and the actual PR ready to be updated once the prequel is in)
template(|context|{ ... }) definitely won't be cut
thats always gonna be a (ugly) escape hatch i think
yea I don't like that
tho tbf we're specifically talking about the feature which currently uses the @ operator as a template prefix
is that something you have a usecase for @frosty shell?
I'm going to break for lunch!
yea if one-shot system templating isn't part of 0.19 then I would be using @SystemIdTemplate { ... }
not really 
! is "not" in Rust and should probably be axed
+ is semantically "add" pretty much everywhere and should probably be axed
whats the issue with @?
We're stealing it for "Scene Components" 🙂
we need something for SceneComponents other than : so we can safely define : as just being 'caching'
As they already use @prop for their "prop fields"
$Template
%Template
>Template
~Template
=Template
slowly but surely whittling down the options
Yeah $ is really the only one that doesn't have existing semantics in Rust
does ~?
could also consider a custom keyword
It used to 🙂
2018 or earlier?
Earlier
ah
~T became Box<T> i think, pre 1.0
i'd say thats squarely in the "its fine to use it then" range
Yeah I don't think its a big deal
is that a "i like this" or a "eh, lets just choose this to be done with it"?
yeaaahh $ invokes associations with bash for me
the latter, but it also matches the "magical" feeling of patching via template rather than component
whether that's a good feeling or not,
lol
i vote for "we go with ~ for now and see if during PR/review anyone has a good argument against it"
Sold
(now im really breaking for lunch)
would be nice to get a visual comparison of the two in a decently sized bsn! invocation
if someone happens to have one they can edit on hand
let scene = bsn! {
:some_scene()
#SomeName
ComponentA
@Template
ComponentB(0.0)
@Template
Node {
height: px(0.1)
}
on(|evt: On<MyEntityEvent>, mut query: Query<&mut ComponentB>| {
let mut b = query.get_mut(evt.entity).unwrap();
b.0 += evt.value;
})
Children [
#Child1 ComponentA
,
(:other_scene() #Child3),
Link(#SomeName),
~Template
@MySceneComponent {
@some_prop: 3,
normal_field: 5
}
~Template
Node {
width: some_variable
}
ComponentB({some_variable + 3.})
$Template
:Container {
@items: {
bsn_list![
#item1 SomeComponent,
:some_scene()
#item2
$Template
]
}
}
]
};
perhaps my commented 'showcase everything' one wasnt the way to go...
yea sure ~ seems fine
sprinkled in some $ versions
yeah i prefer ~, its way simpler. $ is very "noisy"
@ is also "noisy" buuut eh
okay, so to recap:
:will not be called "inheritance" anymore, but instead "cacheable" (name not final)- the Template patching prefix changes
@->~ - the SceneComponent prefix changes
:->@ - the
:prefix is allowed in front of@SceneComponentas well, marking it as being cached
open questions:
- should
:be allowed multiple times now? Is there a reason to still enforce this if it just means cacheing? - what happens with scene expressions:
{ bsn!{ #Foo } }in scene entry position? Do we allow:prefix? - do we forbid caching of functions which take an argument? I think no, now that its explicitly just "cacheable"
- can we standardize on a term for the scenes which are merged here? maybe "base"?
My opinion might not be worth much here - but I’ll just say i am personally not a fan of how this is looking. I feel this is not particularly readable, nor user-friendly. Might just be a skill issue my side, but there are just way too many “special characters” here doing specific things. Perhaps I am over complicating things or just missing something very basic
to be fair, this is a thing i wrote to cram as much of the syntax as possible into one thing, and also has all three variants we're considering at the same time
if you want to know what bsn will look like, and mostly continue to look like, look at bevy_feathers, for example: https://github.com/bevyengine/bevy/blob/a0baf1a2c869ec03949d0e3e33922c3433eaeae6/crates/bevy_feathers/src/controls/radio.rs#L64
for the one i linked, these planned changes would not change a single thing
but yeah, don't take that one to be representative of what it'll look like.
what would the example above look like with all of these changes?
my ugly one or the feathers one?
the ugly one
like this
let scene = bsn! {
:some_scene()
#SomeName
ComponentA
~Template
ComponentB(0.0)
~Template
Node {
height: px(0.1)
}
on(|evt: On<MyEntityEvent>, mut query: Query<&mut ComponentB>| {
let mut b = query.get_mut(evt.entity).unwrap();
b.0 += evt.value;
})
Children [
#Child1 ComponentA
,
(:other_scene() #Child3),
Link(#SomeName),
~Template
@MySceneComponent {
@some_prop: 3,
normal_field: 5
}
~Template
Node {
width: some_variable
}
ComponentB({some_variable + 3.})
~Template
@Container {
@items: {
bsn_list![
#item1 SomeComponent,
:some_scene()
#item2
~Template
]
}
}
]
};
yea that seems manageable
in a real world situation i would probably break out the children into separate bsn! invocations
yup
i would probably not have almost every concept in a single macro in the first place
Do you think something like this is immediately obvious to other developers what is happening if they’ve never seen bsn before?
Appreciate this is an extreme scenario
no, not at all, but i think thats a bit too much to hope for. For simple cases i think it can be obvious.
Consider:
pub fn label(text: impl Into<String>) -> impl Scene {
bsn! {
Text(text)
TextFont {
font: FontSourceTemplate::Handle(fonts::REGULAR),
font_size: size::MEDIUM_FONT,
weight: FontWeight::NORMAL,
}
PropagateOver<TextFont>
ThemeTextColor(tokens::TEXT_MAIN)
}
}
this is a pretty reasonable bsn! scene function, one which i would wager is clear enough if you've spawned bundles in bevy before
I think that’s because this isn’t vastly different to defining a Node/Bundle now
Agree
thats the point: for many cases it won't be that different
theres definitely still ways to make it look very ugly in "normal" usage, like, just look at the feathers_gallery example... but thats more a case of "nobodys bothered to split that into functions
yea ~Template { ... } isn't expected to be a commonly used construct, but is still necessary to support as an extension point
I do think overall these changes will make it way less confusing to newcomers, especially as they allow docs to be far less confusing
recap of the changes we've discussed: #1264881140007702558 message
@split harness tell me once you're back from lunch what you think about this summary, and the open questions
~Template looks like a destructor
c++ user spotted
I feel we are moving away from self-documenting code at this point. I would just be mindful about obscurities, and logic being hidden from users, for the sake of saving a few extra keypresses. Again, I’m an old timer with lots of OOP under my belt, but in my experience - the higher the barrier to entry, the less likely adoption will happen
Just a cautionary tale from my end 🙂
I mean, nothing were planning here is really changing the complexity. We're untangling 2 previously linked concepts which really didn't need to be linked, thats it. I'd argue its cleaner now, at the cost of introducing a new symbol for a even less used part
should : be allowed multiple times now? Is there a reason to still enforce this if it just means cacheing?
No: this limitation was always motivated by the fact that caching requires it.
what happens with scene expressions: { bsn!{ #Foo } } in scene entry position? Do we allow : prefix?
In theory we could, but id prefer to punt this until it comes up
do we forbid caching of functions which take an argument?
We should forbid it until we support it, and I don't plan on supporting this in the short term, as it adds new controversial design aspects to the system (building a mapping from parameters to cached scenes, ensuring the parameters are all hashable, etc).
can we standardize on a term for the scenes which are merged here? maybe "base"?
Wht do you mean by "merged" here? Like:scene?
An alternative name for "cached" or "inherited"?
well, inherited, yes
I think "cached" is probably the best / most descriptive term for now, if we aren't using "inherited"
"baked"?
nah cached makes sense
thats not what i was trying to ask
like, we have a bunch of scenes which get patched on top of the previous ones. What do we call the scene that is in relation to the current one the source of a list of patches?
idk if that makes sense
I'm going to sleep for the night, if anyone wants to try and help me by finding a spurious macro parsing failure, here ya go: https://github.com/laundmo/bevy/tree/bsn-prefix-shuffle
just look at the tests in bevy_scene/src/lib.rs for "expected identifier". Best i can tell that error shouldn't be showing up and it should parse fine
@Template feels a bit more aesthetically pleasant than ~Template but maybe that's just me
~Foo
-Foo
I think this would be dangerous
alas, @ is not an option (we're stealing it for scene components)
just want to chime in with - always has a NOT meaning in my head so unless this is what it would do if it was a valid bsn token, I'd rather not have it included
are scene props going to be usable in contexts other than scene components in the future? if so, it feels very strange to be unifying them syntactically under the @ character
i like it if it means i can glance over any cluster of @s and quickly understand "this is shaped like a scene component" but only if that's consistently true
I just love that what started as a little rust engine is now heavily entangled in a DSL design discussion
I wonder whether we will end up (in quite some years) with a DSL akin to SQL that also ends up covering queries, spawn/mutability semantics and/or maybe even simple functionality that ends up superseeding this current bsn!
all this given that we went down the path of a DSL for bsn while for queries we're still using plain rust
Yeah this is what I thought too, they look too similar
$Template isn't bad either.
If you squint hard enough you can sort of argue that they're similar to the $ token used in other templating languages (maybe a bit reaching though)
It'd be so cool if Bevy ends up with its own version of Flecs script one day! I always feel jealous when I see snippets of what it can do.
Actually now that I think about it, $ is used in flecs script for queries so maybe it's not a good idea if we want to go down that path
Aye, $ was the other token in consideration. Ultimately, which toke is used is relatively easy to adjust once the other parts of the syntax work. The branch where i'm working on this currently fails to parse :@SceneComponent (cached scene component)
specifically, it somehow thinks the : should be a Identifier. My guess is that theres an error in the actual scene component branch which maked the parser back off and try the next branch which fails on the ident, but i'm struggling to debug it
Very unlikely. Props are the same as scene function arguments, its just that for SceneComponents we have to disambiguate them compared to the components fields. Unless you can think of another case where this kind of disambiguation would need to happen?
with its own version of Flecs script one day
That'd require the ECS to natively support scene primitives (like flecs prefabs), an ECS-native reflection system (so you can create types from plain entities/components), and an ECS-native reactivity framework (for thetemplatestuff).
All things that have been discussed in the past, but looks like bevy is taking a very different route (a scene representation that exists outside of the ECS)
I dont know flecs, does its scene representation allow writing scene files like bevy plans to?
yup
What does it mean then for the scene representation to exist within/outside of the ECS?
It means that when you load a scene file, its entire output is ECS primitives (like entities & components). The ECS has native support for things like prefab inheritance, property overriding, variants etc. Flecs script is just an object notation frontend for the ECS.
Ah, hmm. Then i suppose the question is what it means for the ECS to have native support for that
Yes, and it looks like bevy is not going that route. With BSN, an editor will be modifying the BSN AST which exists adjacent to the ECS.
I suppose the difficulty i have with this is differentiating betwen "ECS has native support" and "ECS just happens to be storing this"
as in: if the BSN AST happens to be stored in a component, does that mean the ECS has native support for it?
No, because applications would still need to read the BSN AST in order to make modifications to the scene
hmm, and i suppose in flecs the API to interact with the scene representation is the ECS?
Yeah, and "scenes" (prefabs in flecs) are just plain entities with components + a feature called inheritance (that does actual inheritance)
i am so glad i managed to convince cart to rename bsn "inheritance" to something more sensible
So you can do things like
prefab Unit {
Health: {50}
Attack: {10}
}
prefab MeleeUnit : Unit {
Attack: {100} // override
}
my_unit : Unit { } // instance
Unit / MeleeUnit are plain entities with a Prefab tag
gotcha, and "spawning" the prefab means copying/cloning the entities and removing the tag?
You use the same inheritance feature
ah, so scenes/prefabs aren't technically "spawnable" in that sense
In code it looks like this:
entity Unit = world.prefab(); // same as world.entity().add(flecs::Prefab);
entity MeleeUnit = world.prefab().is_a(Unit);
entity my_unit = world.entity().is_a(MeleeUnit);
They get spawned when you add a (IsA, other_entity) relationship
(which also spawns children etc)
yea but at the root there has to be a non-prefab entity?
(if you want to actually use the created structure for application logic, i mean)
Not 100% sure what you mean, you can use the regular ECS API to interact with the prefab entities, so you don't need to spawn one before you can use it
One thing i have found with Jackdaw is that modifying the AST makes things like physics-based prop placement quite easy to “rollback” and undo as you only commit the edits to the ECS once confirmed
To instantiate it for game logic yes, you'll have to create an instance that's not a prefab
thats what i meant
Also with things like bi-directional editing when using play-in-editor
That has felt easier to me when editing the AST
would this be the point at which property overrides are applied/flattened?
Overrides are at the component level. What happens is that you can mark a component inheritable like this:
world.component<Attack>().add(OnInstantiate, Inherit);
When a component is inheritable, it's not copied to the instance when instantiated. Instead it'll be shared across instances. When a component is not inheritable (the default behavior) it's copied to the instance. Functions like e.get<Attack>() and queries automatically resolve inherited components.
So when you have a 1000 instances of a sword, you're not copying the same static data 1000 times. Better for caching, and also speeds up spawning (because no need to clone values)
Hmm, but take your example above: MeeleUnit has Attack: {100} but Unit has Attack: {10}
at which point is the decision made that {100} is the value of Attack which the users code sees instead of {10}?
Is that technically resolved every time e.get<Attack>() is called?
No that'd be slow
What happens is:
gettries to find the component on the entity- if it doesn't find the component, check if component is inheritable
- if component is inheritable, traverse
IsArelationship until you find a base entity (prefab or not) that has it
On queries this is amortized (stored in the cache), so you don't do the lookup each time you iterate the query
Say that Attack is inherited, and Health is not. What you'd see if you were to inspect the components of an entity is something like:
Health, (IsA, Unit)
and if you looked at the components of Unit, you'd see
Attack
ah, so it is resolved every get (yes yes, cached)
i gtg, but this was very informative, thank you
tho i did manage to get this out: https://github.com/bevyengine/bevy/pull/24367
Only if the component is inherited
Yeah I can see the advantages, but I'm not convinced that an easier rollback implementation is enough justification for basically bifurcating the ecosystem
It's harder to do a good integration, but the end result is simpler (as evidenced by the confusion around the new terminology etc)
re: forbidding caching of scenes with arguments. In the SceneComponent case, theres no syntactic way to differentiate between a SceneComponent with default props or a SceneComponent with no props.
This means with the current design, the SceneComponent when called with default props can be cached. I dont think this is an issue, since default props and no arguments acts the same.
But it does bring up an interesting idea: If someone wants to cache a scene(component) with arguments, they could define a new scene which just specifies the arguments they want to cache and doesn't itself take any.
fn uncacheable(arg: u8) -> impl Scene {
bsn!{
Foo(arg)
}
}
// user code:
fn make_cacheable() -> impl Scene {
bsn! {
uncacheable(8)
}
}
fn use_cached() -> impl Scene {
bsn! {
:make_cacheable()
}
}
is this a pattern we want to document/talk about?
After sleeping on it, i'm able to hopefully more clearly communicate what i was trying to ask here:
being "cached" is an adjective, its something a scene can be. But i'm looking for a term which describes any scene who's patches are applied to the current one.
Example sentences, with <base?> as the placeholder:
A Scene can contain many <base?> scenes, but only the first can be cached.
The cached <base?> scene needs to be the first scene entry.
When the first <base?> scene isn't cached, its patches are applied to an empty ResolvedScene. If its cached, the cached ResolvedScene with those patched applied is used instead of re-applying patches from the first <base?> scene
Another important quality for the ideal terminology here is that it supports sensible verbification (the process of creating a verb from another word class).
I think composition, with the verb being "composed", works independently of the other term, in case theres no better idea
Other potentially helpful terms:
- mixin
- augment
- aggregate
- refine
- extend
- foundation
i feel like many of these only work as adjectives as well
Part of me wants to suggest "root", but I'm not sure that works well as a verb...
i mean, root isn't bad, the downsides are similar to base
I'm not sure if I saw it earlier, but what are the downsides of base exactly?
well, it kinda implies theres only one, and it still has connotations of inheritance
Maybe fragment? Although I think that has the opposite issue of 'base'.
yea
i might go with base but only when its absolutely necessary, working around mentioning it by name otherwise
sigh I wonder if Bevy would be much better off if it takes lessons from Flecs from the start instead of re-inventing things.
Things like relationships and X-as-entities took so long to get off the ground. And yet the former is still very incomplete and the latter still feels janky and tacked-on-at-the-last-minute. I can imagine if they were designed together from the start, it would feel so much more coherent and elegant.
And now it seems that the tradition is once again repeated for the scene system.
Sorry for the offtopic negative rant... I still very much would like to believe in the possibility that all of this will turn out better than Flecs.
For the term for what happens when a scene is referenced in another scene:
I just had an idea. Well, kinda, my dad prompted me to re-consider something i'd instinctively dismissed: "include"
I'd dismissed it because i've always seen "include" mechanisms as simple "insert the other file here" mechanisms.
But doesn't "include" fit basically perfectly? Its close to what happens on a syntax level as well as the mental model. "included scenes" and "scene include" i think work pretty well. The "primitiveness" it implies actually matches BSN surpridingly well.
cc: @split harness what do you think? This would pretty much just be terminology for the docs, but i think its the best i've found so far and would make talking about "included scenes" way easier
I don't really want to re-defend design decisions every time @karmic rock decides to drive by with "things are better in Flecs land" posts (which we've already discussed at length).
In short:
- The scene represenation being outside of the ECS vs "in" the ECS is an open question. We aren't "ignoring" the Flecs approach, its been a constant part of the discussion. Especially once "Assets as entities" lands, I suspect we will partially or fully move things into the ECS.
- Not treating components as the "source of truth" for Scenes is intentional, as they cannot retain the full expressive power of a scene system like BSN on their own (Scene -> Component is a "lossy conversion"). Put another way, dynamic scenes aren't always round-trip-able. Bevy chooses to embrace this throughout the system, which makes everything "cross compatible" with itself. Flecs chooses to have two separate systems (prefabs and templates), where prefabs are "component driven", roundtrip-able, and less flexible and templates are closer to the AST approach (slightly reductive but my time is limited). We are still discussing how to go about this for known-to-be-fully-static things on the Bevy side. We may very well go for "roundtrippable component hierarchies", but that comes with its own sets of tradeoffs. Both Bevy and Flecs are tackling the same problems, we're just drawing the boundaries slightly differently (its kind of "inside out" vs "outside in").
- We are exploring Flecs-like "entity inheritance". I'm not yet convinced this is the right move for Bevy, but it is on the table.
- Flecs uses component-level granularity for patching whereas Bevy uses field-level granularity. This approach results in a variety of tradeoffs on both sides (#1264881140007702558 message)
Part of the difficulty is that we're navigating through a thicket of already-overloaded terms: all of the best and most obvious words are already being used for other things.
which is why i think "include(d)" is about the best we can reach right now: not a term used in rust (except for a macro), not something which has a lot of connotations about complex behaviour, and somewhat representative of what actually happens
There are a couple of resources that I find helpful when researching names. First is the OneLook Reverse Dictionary and Thesaurus. Another is having a long conversation with Claude.ai. I might not trust an LLM to have all the right answers, but I do trust it to have an encyclopedic understanding of language.
okay and?
i have proposed a term after considering quite a lot of them and being left unsatisfied with any of them but least with "included". I dont need tips on researching, i'm asking for feedback on my proposal
I think "include" is fine
I'm witholding judgement for the moment
"scenes can include other scenes"
yup!
For example, if the word "layer" wasn't already taken, that would be one I would pick
Especially for the purposes of writing docs, that is more than enough. I'm not sure we need to make "include" a "capital I" Include concept.
I think its equally fine to say "scenes can extend other scenes" and "scenes can pull in other scenes"
i did consider it, and i dont think its taken? (except for render layers, but those are in a different enough context that i think its fine) but i dont like the fact that "layer" lacks an equivalent to "included scene" which works as well
"A extends B" generally means "A does changes to B" not "A takes everything from B and potentially overwrites it (or not)"
as for "pull" that feels too active, like a scene is explicitly requesting things, when "include" generally means "literally just copy paste the other thing here pls" which fits the behaviour pretty well
anyways, i think i'll go with include in the places where i struggled in the pr
What abour error messages?
"Include" is fine
example: The error when a SceneComponent doesn't have @ (previously :) used to read Scene components should be inherited using @MySceneComponent
i'd change that to 'included' and keep it the same, instead of having to rewrite the whole message
every time @Sander decides to drive by with "things are better in Flecs land"
That's not entirely fair. Flecs was referenced by you, incorrectly, which I replied to. @jaunty pewter then mentioned it again, which then caused @vapid forge to ask me a bunch of questions about flecs. I'm not just randomly driving by, I'm replying when it comes up.
I'm also not saying that things are better in flecs land
I essentially summarized the discussion we had a while back about the different approaches
i think the nature of these kinds of conversations is such that the main topic is the advantages, not disadvantages
@vapid forge I had considered also using the word "foundation" to describe the first inherited scene, since it is special
well, the current plan is for that to be the cached scene, since thats the actual effect
Base/Root?
i have used base in the docs once or twice
Root implies that it's the top of the transitive chain
Traditionally, a root is defined by the absence of a parent
but the important part for me is that the first scene, if it has : prefix, only differs in that its cached and with how the system is built, caching can only work up to the first potentially dynamic change. Which, for simplicity sake, we limit to the first scene.
At least thats my interpretation from looking at how the caching is going to work.
aaand ofc i pretty much immediately find a method which could use this language: The one which used to be called ResolvedScene::inherit
i initially called it ResolvedScene::use_cached but it would make sense as ResovledScene::include_cached
Flecs was referenced by you, incorrectly, which I replied to
This was two days ago, and a completely different conversation.
That's not entirely fair
I'll concede that this wasn't an entirely fair way to characterize your engagement. However you also conveniently omitted most of the nuance that justifies our position, while describing a ton of the benefits of your approach, which caused someone in the conversation to spin out, and I then needed to stop what I was doing to provide the missing nuance. Forgive me ... I was frustrated.
I have no idea how flecs is implemented but couldn't you have some sort of snapshots/checkpoints or some sort of stack that can get popped?
This was two days ago, and a completely different conversation.
It was, but your comment suggested that this is not an isolated instance (I'm randomly driving by), and since you mentioned flecs a few days ago I thought that was relevant to call out.
However you also conveniently omitted most of the nuance
Sure I wasn't here defending bevy's position. That's a bit unreasonable to expect.
while describing a ton of the benefits of your approach
I didn't do that either though. @vapid forge asked questions, and I answered how it worked. If people conclude that they like the way flecs works better, that's not my fault.
Yes you could, and that was the kind of system I had in mind when I replied.
Would be interesting to see some BSN side by side with flecs script. I think both from "which is better" perspective but also to better understand BSN/flecs script. Contrasting and comparing is one of the best ways to learn something new IME.
I'd be willing to write the bsn side if i could find a decent prefabs-based flecs script. I haven't looked too long, but googling "flecs script prefab examples" didn't find much that wasn't either too long or too simple
See https://flecs.dev/explorer, the editor on the right is flecs script
the example script there doesn't seem to be using prefabs
See the scripts in this folder. The kenney_assets one loads the prefabs
i dont see a folder with more scripts in the explorer?
Sure I wasn't here defending bevy's position. That's a bit unreasonable to expect.
Perhaps generally, but this is explicitly a place where we are trying to build the best scene system for Bevy. The purpose of this chat is not to be a general-purpose platform for discussing ECS scene systems. Part of accomplishing the goal of building the best scene system for Bevy is comparing to what is out there, and I appreciate both having Flecs as a thing to compare to and your good-faith participation in the discussions.
However another important part of accomplishing the goal of building the best scene system for Bevy is establishing a shared consensus on direction. This is the one place we have to do that. Unlike pretty much everyone else here, your primary motivation is not that. I hope you can understand why choosing to ignore the body of decision making / consensus building / nuance that has led up to this point might be unproductive for us.
i think your long messages about this are also a bit unproductive, ngl
i'd much rather have a review on the bsn prefix shuffle PR :P
I agree fwiw. I'm trying to tread carefully here to preserve a relationship I care about while also establishing boundaries / expressing how I feel
Unlike pretty much everyone else here, your primary motivation is not that
My motivation for being on the bevy discord is to follow topics I'm interested in (like these) and engage if I think I have something to add. I don't think it's that meaningfully different from others. The only meaningful difference is that I have actual experience building a different solution, which in most cases people would consider a. That does mean I'm going to be more contrarian than others*
If you're saying that this working group is past the point of discussing/iterating on ideas, and is more about evangelizing BSN, then maybe it's time for me to leave this thread.
Its definitely not past the point of iterating on ideas, look at what i've been doing just before and while this discussion was going on. But its at the point where we have 0.19-rc1 out and a lot of things left to do, none of which include entirely rethinking how bsn is structured
I appreciated the knowledge of how flecs does it, but cart does have a point that if we have random people dropping in here to express disappointment bevy didnt follow flecs more closely then maybe that was caused by something
Thats not what I'm saying. Very open to discussing and iterating on ideas. I'd just prefer to spend as little time as possible re-discussing the same conversations because someone (who notably isn't building or using Bevy or actively invested in the consensus building process) decided to only present one side of the argument / ignore the results of the process. Imagine how chaotic it would be here if there were 10 different projects doing that.
Sorry for throwing gasoline on the flame here but from where I'm standing you've been continually disrespected told at least twice by Bevy maintainers assuming you have bad intentions and are trying to hijack the threads you are in. It's not a good look in my opinion. Makes the Bevy community seem quite hostile towards differing opinions. Lots of people who are maybe not actively participating in development are watching threads like these.
are these choices/discussions/design decisions documented in some doc somewhere, that we can point users to?
like a comparison vs the flecs way
I don't want to start more drama here so let's just leave it at this. I don't think I was representing Bevy badly here (certainly wasn't trying to), or presenting just one side of the argument, but if that's how it came across then apologies. I like most discussions here as long as they're on topic and not ad hominem
This seems... strange? Sander has been here for many years and i've seen plenty conversations which continue pretty well. If you could point at places where this "continually disrespecting" happened, outside of this current conversation, i'd appreciate it, so i can know which kinds of interactions can come across as disrespectful.
Sadly the round-tripping tradeoffs conversation doesn't have a good summary.
I mean it doesn't happen every day but I remember a feisty interaction with Alice as well a few months back. I don't stay up to date with the server that much so I might have just been "unlucky" to see these types of conversations the times when I read stuff here.
Disrespectful might be too strong of a word. The times I've seen this happen it has been walked back pretty fast and I don't think any large damage has been done.
And it's not that I disagree with cart either about the purpose of this kind of group. It's just something I've noted happening a few times and it doesn't sit right with me.
i think i found it, thanks. Definitely seems like a "unlucky" situation considering the number of convos since then
if we want to avoid retreading ground we probably need to be better about documenting the decisions. something like ADRs (Architecture Decision Records) dont have to be very in depth or complicated or formal, but they should probably live in the repo somewhere so when someone asks "why not X?" we can just link the relevant one
I dont think its a matter of "we've fully decided we won't do X" more of "we dont currently think its the correct next step". Those ADRs would be outdated/wrong in half a year or less, which is short enough that i personally think the maintainance burden isn't worth it
I largely like your discussions here as well. I'll admit that this was a relatively "minor" issue in my mind. All I'm really asking for here (given the context and purpose of this Working Group) is some acknowledgement of the context of the Working Group.
When a given topic is "opened"
Writing design docs does help insulate against a lot of these problems / reduces the "cost" of re-opening topics
is the most complete design doc of BSN the original RFC? or is there something more complete/up to date?
alas, BSN is something complex and fluid enough that even the original PR description was already outdated in parts before i came along and added a few more changes on top
The pull request description for the main PR is probably the best one, combined with docs we have today
not the bevy_scene module docs tho, those are pretty flawed
Yeah, on a different day I'd probably let your "randomly driving by" comment be what it is. Today just happened to be the day where 10% of the company was let go, and I didn't know until this morning if I had a job or not. So I'm probably a bit more on edge too.
(i really need to at least make a draft PR for my docs changes at this point)
(but i dont wanna deal with the constant CI emails)
But don't all the discussions (which I currently understand to be the best collection of the design decisions) getting outdated similarly fast? (Some of them are over 1000 days old, all of them are at least 6 months)
https://github.com/bevyengine/bevy/discussions/9538
https://github.com/bevyengine/bevy/discussions/14437
https://github.com/bevyengine/bevy/discussions/21431
If anything I would expect a more centralised design system to be easier to keep up-to-date
Dang. Very sorry to hear that :/
It's ok, I'm fine. Many people aren't tho
yes, they are. They're discussions, not documentation.
and yes a central system might be easier to update, but do you want to spend hours each day going through it to clean up old/abandoned/re-considered design decisions?
anyways, this has gotten awfully offtopic
Do we produce hours of architectural design decisions each day? I believe we would need many more Working Groups and C-Goals and SME's to staff those. And even then the time to update these docs would probably be similar to the time spent on design docs for those working groups.
I just want to say that this process doesn't seem to be untenable, at least not for major contested topics, not every single decision would be logged ofc.
imo at the very least we should have clear documentation of the constraints of the problem space we are solving within, im not sure ive seen a comprehensive list
Ofc this is up to maintainers to decide if the benefits of such a system make sense compared to the costs, I'm not saying they should or shouldn't do it, just trying to avoid being dismissive of it.
I dont know if removing the word "inheritance" from bsn would qualify for needing a design record under such a system, but i can tell you i would be far less willing to seriously engage in such a topic or contribute to it if i had to write such a document alongside my PR
i'm already pretty damn glad i don't have to write a migration note since this is the first release with bsn
on that topic: CI has passed, i'd love some reviews!
https://github.com/bevyengine/bevy/pull/24367

Is this all because of a strict design constraint to not have any reserved keywords?
Yeah this conversation also brought me here / this is where my mind went the second ~ entered the equation. We aren't explicitly anti-keyword, its just that up until this point, we haven't been super "syntaxsymbol-ey", as we're largely leaning on Rust syntax / slightly tweaking the semantics of the standard syntax.
The "problem" with using keywords is that they're kind of ambiguous:
// template keyword
bsn! {
template MyTemplate {
}
}
// template is a "scene variable"
let template = bsn! {};
bsn! {
template
MyTemplate {
}
}
Now they can be reserved / take precedence. It just introduces an arbitrary, potentially confusing limitation on variable names
Although thats also true for Rust keywords (ex: you can't have a variable named const or async)
We could support r#template
I probably wouldn't use that exact syntax, as in rust r"hello world" is a "raw string", but this isn't really "raw"
But I agree that we could have special "keyword syntax" that unambiguously says "we're about to specify a keyword"
However I think the ambiguity is probably "better" from a legibility perspective
the r#ident is a real thing supported by rust
Yes, I've used it for r#type
So this would be to disambiguate in the context of keywords (ex: still allow a template variable)
(I initially interpreted it as the reverse)
Yes, exactly:
template -> A keyword
r#template -> A variable named "template"
Also, if you use an existing Rust keyword there's no ambiguity: https://doc.rust-lang.org/reference/keywords.html
Yeah this seems solid for this scenario
Ideally usage of this should be rare - the fact that you didn't know about it speaks to its rarity
People don't normally name variables after reserved words
@vapid forge I'm reasonably convinced that a template keyword is preferable to ~
This is assuming that "template" is not the name of a function commonly used within BSN templates
Ah right, I wasn't thinking about this case:
bsn! {
template(|context |{})
}
Good catch
Yeah, you landed on the one word that doesn't work as a keyword.
We could get "crazy" with it and make template() a "part" of the keyword resolution
im not sure if the ship has sailed on this already (i think it was punted?) but this is yet another class of problem that would be solved by comma-delimited components
Yeah its largely sailed. Nothing in BSN is fully set in stone / immutable, but I don't think thats "worth it"
Aka template() always resolves to the canonical "bsn template function", rather than whatever is curretly in scope
We need to get past the "kicking the tires" stage on BSN and take it on a major cross-country road trip
Truth!
Specifically, some of the strengths and weaknesses of a design are only revealed at scale
Also ideally, UI isn't the only design-space tested/as-example.
I do think getting rid of "inheritance" was worth it, but yea, whether we use ~ or template doesn't really seem like something which matters that much. If people don't like a syntax detail for something this rare, theres always the option of hiding it in a scene function
Yeah manual templates are niche enough that this isn't SUPER pressing
I think its completely fine to stick with ~ for now.
I think a lot of the
reacts on the comment expressing a dislike for the number of prefixes are because nobody read the fact that its trying out the old and 2 alternative prefixes in a single macro invocation
I do think that any new "symbol" addition (especially things in the category of ~) is worth significant scrutiny. It should definitely be a path of last resort
People are understandably allergic to them
sure, but the fact that all the
happen on that specific example is something to keep in mind. And its an example where i tried to condense all possible syntax into as few as reasonable lines before i added the stuff to test the different prefixes
Just wrapped up my review @vapid forge
neat!
Thanks! Perfectly valid changes and suggestions
the "cached multiple times" one is... ugh, how'd i miss that? i was trying to make sure to carefully read through each docstring twice
Anyways, i'm gonna go sleep, i likely won't be able to get the docs PR into a useable state till weekend, the next 2 days are busy for me
this was mentioned before, but ~ does feel odd to me considering it's the bitwise NOT operation in C, C++, C#, Java, JavaScript, Python, Go...
and in C++, ~ is also used for destructors
in Rust it's not used, but in so many other contexts (and specifically, in programming languages) it carries significant semantics
it also looks a lot like - to me unless I look very closely
Yeah ~ is not ideal, although in the world of "one character symbols" its maybe the least worse option. I think its maybe worth trying to make something like the template keyword work
it being rare/niche also i think exacerbates the confusion it will cause when people see it for the first time - and individual symbols like these are more difficult to search the meaning of (e.g. on google)
That's why I really appreciated Alice's "table of symbols" in the rust docs for BSN
now that i'm looking for it, i don't see the current @ template patching syntax documented in those tables, nor do i see a single usage of ~ in the PR
is it theoretical/not yet implemented?
Why is there a need for that many custom symbols anyway? I am pretty confident at this point that we don't need extra Symbols but I might be missing a long term vision for them.
Considering they are changing at the moment or at the very least it is discussed - that is hard to believe though tbh. Is the vision outlined somewhere? I think it would be valuable to align the community here or is bevy set on not doing sth like this?
To take it one step further, I think some of the syntax decisions are actually not ideal (commas, names) and might even be limiting
Or require a lot of jumping through hoops
Or complicate parsing
I think this applies to the bsn syntax generally, not just changes. The BSN pr was merged without a single approval, I think that's important to point out here.
Is it necessary for FromTemplate::Template to require Output = Self? If not, then you could just impl FromTemplate for your custom Templates, and then you don't need another syntax.
the PR calls this out: i've already put a bunch of effort into improving the main module docs (lib.rs) in a different branch and did not want to update those in this PR just to go back and update them in my next PR again
pretty much the only reason they're needed is to disambiguate things which need different generated code but look the same.
~Foo means "use <Foo as Template> directly instead of going through <Foo as FromTemplate>::Template"
@Foo means "Use <Foo as SceneComponent>::scene instead of FromTemplate"
#Foo means "This is actually a string Name("Foo")"
:bar() means "Cache this scene" and only allows the first scene to be cached per entity, only if it doesn't take any arguments
I hope this helps for understanding why different syntax is necessary.
The goal is to allow having both a FromTemplate::Template and a Template impl produce the same things and being able to choose between them.
Took me a while to wrap my head around that as well, and i'm kinda annoyed i didn't respond to it with my understanding, but this is carts message on this #1264881140007702558 message
Right that's what I responded to.
Is it like:
bsn!{
Sprite, // FromTemplate
~Sprite, // Template
}
// Or
bsn!{
Sprite, // FromTemplate
~MySpriteTemplate, // Template
}
the latter
Yea that makes sense but I dont think this has to be solved through additional symbols, i.e. it doesnt make it necessary strictly speaking. Maybe I prefer different approaches and that's fine as well, time will tell
I don't think anyone's proposed a keyword based alternative that works well, yet
If that is the case, then besides for the loosening of semantics, removing the bound and have templates use themselves via FromTemplate should work no?
you're welcome to prototype it lol, i dont have enough info to know if that would just work right off the bat
Perhaps, as doot said feel free to try it, but i'm not convinced its that simple
With the bound removed, you could also do the same thing with SceneComponents, but have them return an generic SceneTemplate<C> or somethiong.
Then no special syntax 🙂 (Except name, we like name)
I kinda have already, so I feel like I have a good understanding about the tradeoffs and to me its feels like bsn is doing too much
And not doing the important things well
link? I didn't see it
Its not public yet
Here the issue is the @props: val syntax, which would need to be supported for all components then
oh wait i thought you were replying to my other msg lol
Yeah I dont know how that works under the hood unfortunately, so cant give any insights as to how that would be effected 😔
Typing on phone, accidentally edited instead of replied lol, I meant to add: bsn might turn out amazing. Its just my perspective at this time
bsn! {
@SceneComponent {
@foo: 5,
bar: 6
}
}
approximately means
bsn! {
SceneComponent::scene(5)
SceneComponent { bar: 6 }
}
if that helps
have you looked at the way its used in feathers right now? Thats the biggest example i can think of for how "reasonable" real world usage of bsn will look
i'd be curious to know what exactly the important things are which it's not doing well. Do you mean "being easy to understand" or actual features/usage?
Yes I have and I dont think it is doing that well (yet), also it is a scene Notation so UI being the biggest example is a red flag in of itself
Mostly around composition and devex
Ill write more about this at some point, I promise
i don't know, UI was always a big target for bsn. Feathers happens to be the main thing which was updated to use bsn. I don't think thats a red flag, it just means its the first adopter within bevy
I actually went through quite similar stages and thought processes as you outlined the last few days, it was very interesting to follow
a lot of things which want a scene notation are not things which bevy itself does or needs to do. And while a bunch of examples could likely be updated, thats work someone has to do
Yea I guess you could put it that way but then bsn is more about composition to me than anything else and I dont think it is solving that elegantly yet imo
But that could change or improve
i really am curious in which way you feel it doesn't yet. But if you wanna take the time to write it out properly, feel free to.
This doesn't cover the case where a Template produces something that already has a FromTemplate impl (they would conflict). And logically FromTemplate really should have that constraint
My concern is that the number of available ASCII punctuation symbols is extremely limited, and we are burning through our supply very quickly, in the service of subtle distinctions that many users won't grasp. I'm not even sure what that first item means.
do we need standard ascii or extended ascii works ?
We need symbols that are easy to write on a keyboard
if it doesn't have a single key on standard layouts, then it's easier to use a multichar identifier
idk which encodage is supported for compiling macro, so just asking which table
cause the non reserved standard ascii characters are
@ _ ~ ` \
and that it
but we use ":" in bsn, so idk what the actual rules that limit our set
Most users will not need to know about or use ~Template i hope. But the general "i'm not sure i understand what this means" is alleviated by not expecting to understand what it means from a single example about the rough differences in how the macro generates code.
what if we use patch as a keyword for it
bsn! {
MyComponent { ... }
patch Template { ... }
}
The only keyword and for something we dont expect users to use often kek
I see what should be the rule for keyword vs symbol, we could look at how rust decides?
Hmm is it just me or has IntellISense stopped working within the context of template in bsn!? On main
that seems strange, i was relatively sure it kept working
I'm not sure how to even describe this behaviour so I recorded a video. It doesn't work the same way if it's done outside of debug!
And only after finishing something once.
This PR did just touch function args: https://github.com/bevyengine/bevy/pull/24174.
I'll poke around
Huh, you think thats the one which broke it? I'm pretty sure i was writing on() observers just fine. I think its more likely the latest one
alas, we've had a power outage and internet hasn't come back for a few hours now
Just reproed. This is breaking in a way that I "understand": it autocompletes when the expression is "valid rust" but if it is incomplete (ex: missing a semicolon) then it stops, because it fails to parse
I'm guessing we recently switched to strict parsing somewhere, rather than just grabbing the contents of the {} block
ahhh, it needs one of those extra thingies
Yeah I wrote a few helpers to cover cases like this
i'm pretty sure the issue happened due to trying to validate that cached scenes don't have arguments
Just confirmed that it isn't broken prior to https://github.com/bevyengine/bevy/pull/24174
I'll try before the cached scene PR next
Broken before the cached scene PR, so probably 24174
Yup its 24174
Definitely going to be this 🙂
#[derive(Debug)]
pub struct BsnSceneFnArgs(pub Option<Punctuated<BsnSceneFnArg, Token![,]>>);
i was sure i had completions working perfectly in observers...
I'm quite certain that switching from parenthesized_tokens(input)?; to Expr parsing in BsnSceneFnArg is what did it
If you're doing Expr parsing, you've already lost unless you provide a fallback
ah hmm. That's... annoying
I should have applied more scrutiny. I avoided trying to solve this problem earlier because I knew #Name parsing would be hard in this context
I don't think its that bad tho, the Expr isn't something thats explicitly required so it should be fine to swap it out
The commas are the main problem: you can either skip parsing them entirely and parse to the next ), or you can "consume" the tokens via something like parse::<Expr> ()
I think the ideal solution is probably either a scan + swap approach (ex: look for #Name locally as you scan through the parenthesized content token stream) or a "grab tokens to next comma or end of content" approach
Is the reason why Expr doesn't autocomplete understood?
Yup! I described it here
Whenever we parse Expr, we're assuming the content is fully valid rust. But something like context. is not valid rust on its own, so it fails to parse, and we don't emit the tokens
Ahhh, Expr doesn't allow invalid Rust?
Correct. Whenever you parse something in syn it expects it to follow the Rust rules for that thing
(which is as it should be)
Its just that macro code generally doesn't actually want to follow a strict Rust grammar when generating an intermediate representation
My parse_closure_loose function exists to solve this exact problem for closures
Which is where this is breaking
right so we'd need to store something which allows any invalid code as an argument as a fallback to the arguments we care about
but only up to the next meaningful comma, which makes this hard
Yup. We essentially need a slightly more structured parenthesized_tokens that breaks things on commas
And for each argument outputs an enum that is either a raw TokenStream or a Name ident
Or alternatively, just output the raw token stream and try to do a scan-and-replace for every #Name found
well, i'd make it just output a list of split TokenStream if possible, then we can test if each is something we know like #Name
Yeah that is also how I would do it
Do you want to pick this up?
Basically just:
- Copy the internals of
parenthesized_tokens - remove the
context.parse::<TokenStream> - Use the cursor strategy used in
parse_closure_looseto scan the stream for commas and break it into smaller streams
i was thinking the same, so i found those internals: https://github.com/dtolnay/syn/blob/b706e6e9519d541264b4d3c7cd22c3e6a7301026/src/group.rs#L79
there might be a simpler way
what if, on any failure, we make the whole function args return the input as-is?
Definitely an option, but its not as good because its slower
oh, interesting, in which way?
ahh
Yup yup. This is one of the ways unsynn is "faster" ... it just skips the hard problem of parsing real rust
i thought you meant the returning of input as is is slower
Do you want to pick this up?
/ do the impl?
(@vapid forge )
It certainly is more interesting than writing docs. (I really should make a draft PR for the docs changes i have tho i'm well aware some of them aren't ideal. I just wish there was an option to disable C in draft PRs...)
Alas, i'd currently have to use my phone hotspot to work since the internet outage is still ongoing, which i've taken as a sign to go to bed at a reasonable time today and hope its back to working tomorrow.
log story short: if you want it done before the weekend, probably no
Cool cool. I'll be away tomorrow and the weekend, so I'm in no rush
I'll focus on IRS annual report stuff for now. We can see where we're at on monday
before that, got an idea for an example for ~Template?
An alternative SpriteTemplate that randomly returns a sprite from a list of sprites 🙂
Not actually a good example. Not very practical
A more practical, but "larger" example would be a Mesh3dPrimitive template, which is an enum (ex: Mesh3dPrimitive::Cube) that produces a Mesh3d with the appropriate cube handle
any thoughts on this btw, as an alternative to ~Template? dunno if you saw it
Ah yeah: I don't like it because normal "component syntax" is also a "patch"
Just realized splitting on commas won’t work naively: imagine you have a struct as an argument. Can’t split on those commas
It has to be “scope aware” for all possible things in rust that could have commas
Maybe templates shouldn't be supported in struct form, just function form?
When you see an Component or SceneComponent patch you know what will be added(roughly), but that isn't the case with templates. (This is a similar concern in other areas of the engine as well)
Is it possible to dynamically create #Name? And also what is now the correct name for those, at some point I thought they were EntityReference?
For example
let scenes = (0..6)
.map(|i| {
bsn! { #Name{i}}
})
.collect::<Vec<_>>();
To add onto this. I am assuming we can't use special symbols or spaces in names with the current syntax? Is that (still) accurate?
This does compile. As spec-ed and according to my "scoped entities" impl, each #Name there would only be valid within the context of the bsn!.
But @vapid forge I have a bad feeling about this in the context of your new impl 🙂
Would these not all refer to the same entity now that scoping is removed and we rely on line position?
Yup #Name syntax follows Rust Ident rules
So if we add new symbols that are used in names, it would break, correct?
Currently yes. If we really want to come up with something like #"Arbitrary $tring Here" we could
I don't think some standardization is a bad idea. Godot has "standard name formatting" too
Although that might just be a covention / auto-generated thing
Yes, I think it's very valuable in a context like this, and somewhat expected behavior imo. I think we avoid accidentally parsing the name, but that ship might have sailed. Afaik a space or custom symbols would force using quotes basically.
Doesn't seem to compile; the trait bound {integer}: bevy::prelude::Scene is not satisfied.
Also same thing when explicitly typing the lambda argument
let scenes = (0..6)
.map(|i: i32| bsn! { #Name{i} }
).collect::<Vec<_>>();
yields the trait bound i32: bevy::prelude::Scene is not satisfied.
But that's a good point that each #Name would be valid only within the bsn! context so that might complicate what I had in mind
you'd have to use only an expression, like this:
#{format!("Name{i}")}
@vapid forge it seems like there's some bsn_list errors happening that might be related to the reference work
(this is on main right now)
its the 3d_scene example
it compiles and runs fine. rust-analyzer is the only aspect showing issues
I experienced the exact same error when I was developing against the RC from crates.io until I realized that laund's stuff weren't on there. So maybe rust-analyzer is just stuck on some old dependency version?
Hmm, you're likely right, which means EntityTemplate does need to rely on something unique per invocation, the internal counter isn't enough since its reset per invocation. Very solveable tho.
try restarting rust-analyzer
not just reloading workspace
🤦 thanks
not sure if this goes in here or in #1019697973933899910 , but I've hit a possibly related regression in rc2 in some of my own bsn + SceneList code. I haven't yet managed to figure out exactly what the cause is - it is very possibly PEBCAK, since I currently have a lot of entities sharing the same name, and while that previously worked in a pre-bsn world the most recent changes have made names somewhat more important in a bsn setting!
the stuff I've got looks like this
bsn! {
// ...
RelationTargetName [
{function_returning_bsn_list()},
{another_function_returning_bsn_list()}
]
}
// ...
fn function_returning_bsn_list() -> impl SceneList + use<> {
something.iter().map(move |(position, item)|
Box::new(
bsn! {
:{ item.scene() }
template_value(position)
}
// where item.scene() may very well introduce a #Name, and that #Name might not be unique
)
).collect() // and the other function is very similar
}
on rc1, every scene returned by the two functions was correctly assigned to RelationTargetName's relation; on rc2, only a seemingly arbitrary subset of them is related properly. 😅
I suspect this is exactly what we were just talking about just a few messages up, and i'm writing a test to catch this issue right now and fixing it. I think the fix is very doable.
Eek, apologies! I am awful at scrollback reading sometimes :(
np!
I might need to convince Francois to release rc3 soon with this fix, i suspect a few people will run into it
I’ll hold on rc1 for now, thankfully there doesn’t seem to be anything broken or missing in rc1 that I need rc2 for c: thanks for the quick catch on it though!
yuuup
let scenes = (0..6).map(|_: u32| bsn! { #Name }).collect::<Vec<_>>();
world.spawn_scene_list(scenes).unwrap();
world.query::<&Name>().len() == 1
I should probably move off using Name for what I'm using it for, in honesty. I think in 0.18 it was just a handy pre-existing bevy thing that I could use to assign names to things and at no point did I enforce the invariant that each name was unique (in fact, there's some places where I'm directly assuming that multiple things answer to the same name 😱 ). Of course, now names do have semantic weight, and if they're all being spawned into a BSN list at the same time they're expected to be useful cross-references :D
fyi: there's some conversation about getting the docs to build for the rc in person right now, so there will likely be an rc.3 for that at some point
if you know where this is happening, could you mention this problem and that i'm working on a fix, and that it'd be nice to have the fix as part of that?
yes. françois is right behind me.
docs are now built for the rc.2, issue was the builder limits
but no problem pushing a rc.3 if needed
did they permanently raise the limits for bevy?
yup
okay, i got a hacky fix for multiple calls to the same scene using #Name
ugh, i had to introduce a from/into, but if a #{name} expr doesn't implement the trait the error shows up as affecting the entire macro
somewhere along the way, span information is lost
The old scope system would solve this, so we could also revert back at the cost of the new flexibility. I’m curious what your “hack” is. We’ll want to do a very careful analysis this time / extend our test cases
If I wanted to dynamically create entity names, the first thing I would ask myself is whether I'm doing this to create Name components or whether I am doing this to create names in bsn scope. Although BSN #name does both of these things, I'm not sure it's the right tool for algorithmic names:
- If the goal is to create name components based on a formula, then just use the
Name(format!(...))component constructor and don't bother with the BSN syntax - If the goal is to create an array of similar scoped names in bsn - Ummm, I'm not sure how that is useful
My inital hacky way was to make the call-time Instant part of the hash
the less hacky one i've almost got finished is to use a per-macro Atomic counter to distinguish calls. Its incremented once every time the scope containing the generated code is executed. Its basically done, i'm just working on making it work nicely with #{expr} names
mostly making errors due to the expressions actually show for that expr, and not for the whole macro
honestly i probably spent way more time on trying to fix the trait error span than anything else
AHAHAHAHA
ITS SO FUCKED
i got trait errors working without drawbacks!
let span = proc_macro2::Span::from(value.span().unwrap().start());
let name_expr = quote_spanned! {span=>
__Name::from(#value)
};
this awful bullshit
To make trait errors show up not as errors for the whole macro invocation but instead errors of the part which caused it i have to
- get the span of the part which caused it
- convert it from a proc_macro2::Span to a proc_macro::Span
- use the proc_macro::Span::start() method to get a 0-length span just at the start of this span
- convert it back to a proc_macro2::Span
- apply this new 0 length span to the function call causing the trait error, in this case
__Name::from
Things which do not work:
- Apply the span of the entire value expression to the function call: This either fails, or if forced by looping over the tokens and calling
.set_span(span)on each, causes R-A to show the docs for all types on hover. - Any sort of nesting of the value in a block or similar things, all cause the error to be shown for the entire macro invocation
- Any combination of quote_spanned! with the original span simply does not work
- Any force applying the original span causes the R-A issues
this has taken... WAY too long
PR fixing the accidental entity deduplication: https://github.com/bevyengine/bevy/pull/24402
I'd love if either of you could test this: @dusk lynx @nova bridge considering you both ran into this issue
Not sure if I'll be able to test it this side of today, but I'll see what I can do
Incidentally, how does one make cargo pick up Bevy from an arbitrary Git branch? I think I tried that once and it only switched the metaproject to git, not any of the dependency crates
If you depend on a crate that depends on bevy subcrates you also have to specify/patch those
ahhh 😅 much looking at the lockfile in my future then :)
There's an issue on the cli repo about this https://github.com/TheBevyFlock/bevy_cli/issues/79
Not saying I ran into it per se, was just exploring an idea if you mean the dynamic #Name. But I can of course test it at some point tomorrow or early next week! The actual issue I ran into was that IntelliSense was breaking within template().
just pushed a change to https://github.com/bevyengine/bevy/pull/24402 to use a continuation of the compile-time entity name counter at runtime for name exprs, resolving the concerns @beicause had for using a hashmap key itself as a unique value
hmm, it looks like I'm going to need to try to work out how to test that. I've reached a point where my code isn't working in rc1 (because I want to make one child reference another child) and if I upgrade to rc2 that'll work but the aforementioned'll break
You'll probably want to depend on my fork+branch directly and likely patch any other dependencies which also depend on the rc
i keep thinking "today i'm gonna work on docs" and then getting pulled into other interesting topics and end up nowt working on docs :/
new PR https://github.com/bevyengine/bevy/pull/24443
td;dr:
:scene was allowed but :scene() threw a "Cannot cache scene function with arguments"
even tho :scene just gets turned into scene() in the macro. This PR allows :scene() again, since it also has no arguments
@split harness i just noticed something potentially bad: Cached (previously inherited) scenes can come after component patches in a scenes entries, but they're not automatically hoisted to the top. That means, from my understanding, the behaviour would be different between caching and non-cached usage.
#[derive(Component, FromTemplate)]
struct Position {
x: f32,
y: f32,
z: f32,
}
fn a() -> impl Scene {
bsn! {
Position { x: 2. }
}
}
fn b() -> impl Scene {
bsn! {
Position { x: 1., y: 1., z: 1. }
:a
}
}
world.spawn_scene(b())
Currently, without caching, this becomes:
let p = Position::default()
p.x = 1.;
p.y = 1.;
p.z = 1.; // end of Position from scene b
p.x = 2.; // scene a
but from my understanding, with caching implemented, it would become something like:
let cached_p = { // assume cached_p is loaded from the cache
let p = Position::default();
p.x = 2.;
};
cached_p.x = 1.;
cached_p.y = 1.;
cached_p.z = 1.;
because the cached a() is the ResolvedScene of just a() which would be the base ResolvedScene on which b() builds.
In other words: I think we should disallow caching :a() syntax if its not the first scene entry.
Note: the code blocks above are very stripped down to show only the order of operations which is relevant
Going through the kinds of SceneEntry we have, the only ones which would be "safe" above a cached scene are:
- Name/NameExpression
- RelatedSceneList
- Templates which only set Default (unsure)
Okay, i've implemented the "can only use caching syntax as the first item" and its not a big change. Essentially, instead of found_cached_scene = false being set to true if we fined a CachedScene entry, i've replaced it with is_first = true which is set to false after the first entry unconditionally.
This does bring up another idea: If we go this route, is there even any need for the caching syntax anymore? It'd be relatively easy to just treat the first scene as cached if it doesn't have arguments, and if it does, not cache it. That does make behaviour a bit more implicit, and removes the easy way to look at a bsn call and say "oh, you're not caching that", but it also frees up the : prefix, simplifying the syntax.
@split harness sorry for pinging again. this issue is kinda worrying me, i'm somewhat stuck on it regarding docs.
All good! I got caught up on this earlier this morning / I’ll be switching to “let’s get BSN ready for 0.19 mode” after lunch
I'll get my current docs PR out as a draft then
its not complete, but its there now. Complete with the entire diff from https://github.com/bevyengine/bevy/pull/24402 included because reasons
Yup this is true. I believe I added some error handling for this at the inheritance / patching level, but it would be nice to catch it early in the macro
Aight i'll make a branch for my changes which do this and PR them
I'll also respond here because I'm a bit confused: i thought we talked about this in depth and that caching is planned for all those cases as well? or did you change your mind completely? Or are you saying caching needs to be there first?
Its planned but not implemented. Theres a world where we don't ship with caching for those cases, so its better to treat them as error cases until we actually add support
Is it? It costs nothing to support the syntax now and add caching later, while it costs a lot of effort to forbid the syntax now and for people to add it back later.
Plus, theres the whol "do we even need a syntax anymore" question
I dont think inferring the caching behavior for first position is the right move. This feels like something that would be good to be explicit, given the constraints involved
(and differences in performance)
I think the distinction is especially important if / when we add cached parameterized scenes
(ex: scene functions with arguments)
Okay, fair, i wanted to bring it up because right now its very possible to infer it
The "cost" is that we're telling people it is caching when it isn't. I was fine with bending the rules back when : wasn't explicitly caching, but now that it is, it feels wrong to not be explicit about it here
IMO the alternative is removing the syntax entirely for now. Because if we error on those cases it has 0 uses as bsn assets are also not implemented yet
Leave it as a, to users, entirely new concept to be introduced in 0.20
Shipping with it lets us ship and test experimental asset format crates that people on 0.19 can use
We do support scene assets in the new scene system, we just haven't added an impl yet. I don't want to artificially hobble that use case because we don't want to write error messsages for the other cases 🙂
oh yeah, its made in a way users can implement it, forgot about that
I don't want error messages which we know well remove very soon
that feels... pedagogically flawed
teach people they can't use this, then turn around and teach them they should use it very shortly after
It is very ok to support a feature that can be used in some cases but not in other cases. See if let in Rust
(the history of it)
i feel thats a bit different
"caching is only supported for scene assets" is a very easy sentence to write/understand in docs and in error messages
"if let cannot be used in else expressions yet" is very similar imo
More importantly, this is significantly better than silently letting people think their scenes are being cached when they are not
Ugh, i suppose i'll just have to not think of the many people who will see that error once after 0.19 and never realize it was enabled for those cases in an update
We can say: "we plan to support this in a future release" and perhaps include a link to an issue
theres no way to show a warning/hint from a macro is there?
And if we manage to implement caching for these cases before the release (which is low hanging fruit), then we can remove the failure
Idk actually. Its fine to error there / I think it is preferable to
like a compile_warning!
no i meant the inverse: warn that it could be cached
in the future i mean
In my mind these are our two best options:
- Produce a proc macro compile error which fails compilation, explains why and says that this will be supported in the future
- (maybe possible) Produce a proc macro compile warning which says that this will be cached in the future, but do the uncached behavior instead.
yeah that was what i was getting at
makes this easier to stomach
if we can warn in the future
I believe (1) is better, as the final bsn is more self documenting. People reviewing code on github won't necessarily know about the compile warning or the fallback beahvior,
oh wait i misread your 2
my idea is to, if we can, warn on cases which are trivially cache-able once caching is there
that also annoying tho
anyways
wayyy more important
could i get a review on: https://github.com/bevyengine/bevy/pull/24402
fixes that issue with looping the same scene as parts of another scene
that would spawn 1 entity instead of N
I've been reviewing it for awhile now 🙂
Presumably you haven't picked up this stuff yet?
Is that something I should hop on?
Very nice, just read through it. About name expressions: Do you want to go through with removing them entirely? That really would reduce complexity a lot, but bit i fear it'll feel too obvious to users that this should work. But you're right about the workarounds being surprisingly comprehensive, tho definitely something to document
nope, haven't had the chance, been busy with all the other stuff
Yeah I think thats the move. Sorry this is largely my fault for implementing the easy part first #{format!("Hello")} without consideration for behavioral consistency with #Name
Maybe worth seeing if anyone else can think of a use case first
(that doesn't have a good workaround)
Eh, i spent an annoying amount of time making it work, especially the "show errors at the correct point without any compromises
but i do think its just... way simpler
At least the span trickery i figured out should be useful for other cases
Yeah I suspect we'll want that elsewhere
the issue with that is that very few people have experience with bsn, especially the tricky parts
Yup. Perhaps another good reason to remove this and see how people adapt to the constraints
We can always re-add it later if we decide it is necessary
okay, so, marking that as decided
Hi guys, im sorry to ask questions about bsn here (im new, not sure where to ask questions about bsn).
I noticed that bsn does not support recursive caching of scene right now, is this the intention or a limitation?
If understands the current ResolvedScene impl right
fn scn0() -> impl Scene {
bsn! { SomeComponent }
}
fn scn1() -> impl Scene {
bsn! {
:scn0
Foo
}
}
fn player() -> impl Scene {
bsn! {
:scn1
Player
}
}
this code snippet does not equals to
fn scn0() -> impl Scene {
bsn! { SomeComponent }
}
fn scn1() -> impl Scene {
bsn! {
Foo
}
}
fn player() -> impl Scene {
bsn! {
scn0
scn1
Player
}
}
This is currently a limitation, caching is planned but as it works without it, other things are being prioritized. If you want to look into making caching work, you're welcome to, a lot of the code is already there. Also note that we recently decided not to allow caching syntax (:) for cases where its not actually implemented
Those 2 snippets should be equal: whether player includes scn1 which includes scn0 or player includes scb1 and scn0 separately, their templates still end up in player.
The only way these could be different is if the order of how the other scenes are listed changes, and they patch the same components and values
in the new docs i talk about scenes being "included", which hopefully makes the behaviour somewhat more obvious:
In your first case, scn1 includes scn0, and player includes scn1, becoming something like this
bsn! {
SomeComponent //from scn0
Foo // from scn1
}
and then this is included in player
bsn! {
SomeComponent // scn1 (originally scn0)
Foo // scn1
Player // player
}
With caching, when caching is implemented, the behaviour will be the same. But instead of having do do the "include scn0 in scn1" calculation every time, the result of scn1 would be cached and then Player added to it.
I hope that makes some sense
Thanks for the explanation. Its all clear for me now.
@split harness
I'm running into an issue: scene_fn without () raises a trait error spanning the whole macro which is incredibly annoying. I have not managed to apply the span trick to this, because the issue is not related to macro spans. The error happens for the entire generated expression even after inlining the macro result - this is base rust behaviour apparently.
To resolve this: What do you think about disallowing non-expression inline scene variables?
let foo = bsn! { #Foo };
bsn! {
Component
foo // this here would need to become {foo}
}
This would allow us to catch foo without () at parse time and throw an error suggesting to call it if its a function or do {foo} if its a variable.
actually, i found another way this could potentially be fixed using codegen instead of parsing:
{
fn _b() -> impl Scene {
b
}
_b
}()
this limits the error to only that new _b function
wait no this doesn't work with variables because only closures can capture, duh
Okay i may have found a somewhat workable solution: If we have a trait which implements a method for T: Scene which does nothing but pass through the variable of type T (i'm calling it IsScene, we can get a somewhat related error message, but only if we use {#tokens}.is_scene(). If we use IsScene::is_scene(#tokens) the error escapes out again.
its probably the same issue why a function which takes impl Scene and returns impl Scene dosn't work
This is what it looks like. Dunno if thats acceptable or not, i couldn't figure out a better way
Yeah this problem is nasty in general. Put out a draft PR and we can play with it. Might need to wait until after 0.19. I'm starting to feel the need to reel things in
its a problem that exists on main as well
yup
i did comment on an older rustlang issue by alice and immediately got estebankuber saying "we need to do something" https://github.com/rust-lang/rust/issues/141258#issuecomment-4566044338
@vapid forge just wrapped up a review of your changes here:
https://github.com/bevyengine/bevy/pull/24402#pullrequestreview-4385240292
Just a few small tweaks
saw and replied, will get to it as soon as the "error on caching unimplemented scene" pr is out
which is now: https://github.com/bevyengine/bevy/pull/24473
Looking now
reviewed
Lets remove the to_scene stuff for now. It does work, but I don't love the extra codegen / functional jank (it is spurious / increases the perceived complexity of the system / makes macro outputs harder to read).
Might still be our best option, but I'd prefer to consider it on its own time
thats why i was careful to make it its own commit, i'll move it to a new branch
@split harness could you explain what you meant by this? https://github.com/bevyengine/bevy/pull/24402#discussion_r3321050573
also, i just applied most of the fixes you asked for
besides that comment and another i was unsure about
done.
I meant the commented out lines that don't currently do anything
ah
and the other call_id comment?
call_id for the variable name probably makes more sense than for_refs
both done
Aight, i'm going to bed, maybe i'll polish docs some more tomorrow
spoiler: that didn't happen
@split harness about enum VarianDefaults: should i mark an issue running into this as Docs+Wontfix and remove the bug label?
in other words: i assume VariantDefaults has to stay?
i went ahead and labelled it like this, if its wrong, oh well
@split harness @vapid forge I'd like to re-raise the issue about atomic replacement of bsn scenes to ensure it doesn't get lost in the shuffle. (At the risk of being repetitive, I want to make sure that new readers get all the context, so I'll explain in detail).
bevy_reactor uses the following pattern ubiquitously:
entt.despawn_related::<Children>();
entt.queue_spawn_related_scenes::<Children>(...replacement_scene...);
...where entt is always a ghost node. Note that this isn't the only kind of dynamic replacement that bevy_reactor does, but it's the one used to make any sort of large-scale structural change (like an If or Switch statement).
The problem with this is that there is an undetermined length of time between the removal of the old children and the spawning of the new ones, creating an unsightly flicker or gap.
We have discussed several ways of addressing this, but none are workable in the current environment:
- Using the non-queued version of
spawn_related_scenes- this requires deeper knowledge of the child scene than is available to the reactive primitives - Somehow expressing the dependencies of the scene via type information - this seems like a major research project that is unlikely to bear fruit any time soon
The simplest solution, in the short term, is some means to do the despawning of the old children in the same frame as the parenting of the new children.
Unrelated to this, I just patched in the latest Bevy mainline in the bevy_reactor, and I'm having difficulty using the #name syntax, I get rust-analyzer errors like this:
no such field
missing structure fields:
- reference
Makes sense. While I still think ultimately we should be making the reactive primitives dependency aware so we can make these types of reactions "immediate", even with that feature I can see this being desirable for some use cases.
I believe I have something that will work for you without needing new features:
fn queue_replace_children(&self, scene_list: impl SceneList) {
self.queue_apply_scene(bsn! {
bundle_template(|context| {
context.entity.despawn_children();
Ok(())
})
Children [
{scene_list}
]
})
}
You would run queue_replace_children on the ghost node entity.
This (should) work because templates are applied before children are spawned when applying scenes, so the old children will be cleared out right before spawning the new children.
Error:
`()` is not a `Component`
I think its worth leaving open for now. I left a comment there and removed the WontFix
A right we need a bundle template
one sec
Alrighty this should really be upstreamed, but heres the bundle_template impl:
fn bundle_template<
F: Fn(&mut TemplateContext) -> Result<B> + Clone + Send + Sync + 'static,
B: Bundle,
>(
func: F,
) -> impl Scene {
SceneFunction(move |_context, resolved| {
resolved.push_bundle_template(template(func));
})
}
@rare whale I've edited the example above to use it instead of template
Something very strange is happening - it seems like queue_apply_scene only works once. Subsequent calls have no effect.
alternatively, allowing template() to return () as well, which would mean implementing Scene for FnTemplate where Result<(), E>
Due to the "component vs bundle template split" introduced in my dynamic bundle spawning changes this isn't possible
ah, oof
We need a static distinction between a "bundle template" and a "component template" now (at the scene level). This could ultimately be lifted if we sort out nested dynamic bundles, but thats a harder problem to solve
(See the new BundleWriter)
Hmmm
Does this still happen with this branch?
https://github.com/bevyengine/bevy/pull/24474
There’s been few cases reports of this lately and it’s been resolved by restarting Rust Analyzer
That is not very satisfying 🙂
If it keeps coming up. It would make sense if this happened after updating to a new version of Bevy with different bsn! code
Are you sure theres "a few cases" and not one case per person who didn't restart after pulling in the new version?
More like the latter. Sorry for not being precise enough!
I am personally only aware of the one case per person, personally
because working on this, lemme tell ya, R-A does not properly recompile proc macros when their code changes
i have to restart R-A for basically every change i make to the macros
Sounds lovely 🤐
Honestly, i'd be fine, if rust-analyzer allowed its lints to disappear if clippy doesn't find them
but i can't live without R-A completions and go-to-source, so i have to keep using it, which means i have to keep restarting it
Verified
As in, it still happens?
No - as in, the fix works
Thats good news 🙂
BTW, here's what my code looks like now:
/// Trait that represents a function that can produce a [`SceneList`]. Used for conditional
/// blocks in control-flow templates.
pub trait SceneListFn: Send + Sync {
fn spawn(&self, parent: EntityCommands);
}
impl<S: SceneList, F: Fn() -> S + Send + Sync + 'static> SceneListFn for F {
fn spawn(&self, mut parent: EntityCommands) {
parent.queue_apply_scene(bsn! {
bundle_template(|context| {
context.entity.despawn_children();
Ok(())
})
Children [
{(self)()}
]
});
}
}
Then, elsewhere:
/// Conditional control-flow node that implements a C-like "if" statement.
struct IfReaction<ConditionFn: Lens<bool>> {
state: IfState,
condition_fn: ConditionFn,
then_branch: Arc<dyn SceneListFn + Send + Sync>,
else_branch: Option<Arc<dyn SceneListFn + Send + Sync>>,
}
Can I see it in context / a usage example 🙂
commands.spawn_scene(bsn!(
Node {
left: ui::Val::Px(0.),
top: ui::Val::Px(0.),
right: ui::Val::Px(0.),
position_type: ui::PositionType::Absolute,
display: ui::Display::Flex,
flex_direction: ui::FlexDirection::Column,
border: ui::UiRect::all(ui::Val::Px(3.)),
}
BorderColor::all(css::ALICE_BLUE)
Children [
Text("State: "),
switch(|cx: &Cx| *cx.resource::<State<GameState>>().get(), |cases| {
cases
.case(GameState::Play, || bsn_list![Text("Playing")])
.fallback(|| bsn_list![Text("Not Playing")]);
}),
Text(" - "),
if_then_else(
|cx: &Cx| *cx.resource::<State<GameState>>().get() == GameState::Play,
|| bsn_list![Text("Yes: Playing")],
|| bsn_list![Text("No: Not Playing")]
)
]
));
Hehe if I ever find time I'm going to build "structural" equivalents for bevy_reactor 🙂
I haven't really been working on this, I'm just keeping the code up to date with the latest releases
But yes, that would likely be quite doable
Much appreciated. It will be nice to land 0.19 with a reactive option in the ecosystem
Not sure how you would do the switch cases structurally, since the number of cases is variable
Also, there are many other design variables which we have discussed
Like whether the then/else block should be closures or just straight up bsn literals; whether the If reaction should use dyn functions or generics; and so on.
Main reason for using closures is that the path not taken has zero cost
It's not really "in the ecosystem" since I've never actually pushed it to crates.io. I don't want to make the mistake I made with quill, where I clutter up the rust namespace with a permanently abandoned crate name.
Yeah I see the appeal. The theory is: generating the description of every case isn't particularly expensive, it allows us to know when all dependencies have been loaded, and it lets us cut down on boilerplate because the closures are no longer necessary
I could be convinced
bsn! on its own is generally just some some functions on the stack in practice
(with some data captured in closures and sometimes a full component template here and there)
Another option is to make the "lazy reveal" an explicit wrapper: say you have something like a tab deck or dropdown menu, where one tab panel is expensive to maintain - you want to despawn the contents while its invisible, because otherwise you have to spend cpu keeping the contents (tree view, font list, whatever) up to date with the game state. The wrapper would only auto-spawn its children when visible.
Makes sense. Good to have options
@vapid forge I switched to QueuedScenes (see my comment)
https://github.com/bevyengine/bevy/pull/24474#issuecomment-4580003799
@rare whale can you verify that this version also fixes your issue?
@vapid forge is your bsn doc pr still a "draft"? let me know if / when I should review it
Yep, appears to work
Thanks!
Can we get one more review on this?
https://github.com/bevyengine/bevy/pull/24473
Maybe @rare whale as it has implications for how function scenes like pane() are spawned in the short term?
@rare whale ultimately the :pane syntax will be supported again, once we add function-scene caching
Can't do it right now, maybe later
Done
Its a draft in the sense that theres still changes i want/need to make. But i would already welcome some feedback on what i have so far, at least high-level "this is missing" or "maybe mention this". Lets wait on nits till its out of draft
@split harness Another thing I have been mulling over a lot recently is the BYOA (Bring Your Own Artwork) widget collection idea and how that fits into the BSN vision. I have a prototype but I don't like it very much - the code is too clunky and inflexible.
The problem I am trying to solve - associating different style parameters with different states - is fairly trivial with a reactive solution. However, not only do we not have consensus on what a reactive solution looks like, it's worse: none of the proposed reactive solutions will work with asset BSN. Everything that's been done so far involves writing Rust code that lives inside a BSN macro.
My goal with the BYOA prototype was to encode all of the visual dynamicis in an entirely no-code, declarative fashion. This would be done using bitmasks to represent the various states such as pressed, hovered, disabled and so on. Since this is just data and not code, it could be serialized easily. However, the resulting widget design is more complex than I'd like.
A different approach which I have also prototyped was to build a simple interpreter for embedded expressions - not trying to re-implement Rust or even Lua, but just enough to be able to write simple formula in a BSN asset and have it be compiled when loaded. I got pretty far along this path - I was able to call simple functions and run them, I had a working type inference solver - but the complexities really start to snowball once you start letting scripts access ECS data structures.
Unfortunately, I lately have been feeling burned out by the lack of progress, and had difficulty with motivating myself to work on this stuff.
Yeah, i am pretty concerned about trying to actually implementing anything like dynamic scripting, I think focusing in on hot-reloading with the dioxus crew, and trying to work harder on integrating that more closely with bevy, and making it an easier option to use, making it work better with all parts of the bevy ecosystem, is something of less maintenance cost ( not that it will be small but the cost of our own language is large ) and also just integrates better with the whole ecosystem of bevy and rust.
We can get easily editable, dynamic re loadable ui without it being in asset files, if we lean really hard into optimizing stuff for working well with hotpatching.
Probably overkill, but I have to mention that you can JIT Rust (and own languages) with cranelift
That's fine for development-time compilation, but you aren't going to ship a rust compiler on android
I have looked into this, only a little bit
from my understanding, hotpatching does work with android, not just in the emulator
but with a real phone
The thing to understand is that all of these JITs - WASM, Cranelift, Rust, etc. - are just performance optimizations. You can do exactly the same thing, albeit slower, by implementing your own stack-based interpreter. A JIT makes your code run faster, but it doesn't solve any other problem - particularly it doesn't address issues of garbage collection, lifetime management, or reactivity.
do you mean you want users to be able to modify UI in real time?
not developers?
The typical division of labor between script and engine is that scripts provide command-and-control duties, while the engine does all the heavy lifting. So the speed at which the script interpreter runs is only important if it's being used for CPU-intensive parts of gameplay.
No, I mean that if you are embedding code in an asset, that code has to be compiled at load time - unless you plan to ship precompiled assets for each different target platform.
Anyway, building a simple VM is not that hard - managing access to the ECS world with lifetimes is hard.
Well, if you're just doing observers or systems, it's not that difficult, because you just have no state persist ( aside from Local in system params ) so all other state just gets wiped out after your frame ends. https://vxtwitter.com/MalekiRe/status/1903903164010471577
Lifetimes are just for the length of the system, or for the length of the function call, and that's it
I'm trying nodegraph scripting again. Already more progress than I made last time. The goal is to snarf all of rust's features using bevy reflection for functions and values. The idea is to create these node graphs and they will run as systems, as a way to do modding for bevy.
I've done several different kinds of ecs integrated nodegraph and lexical scripting with bevy where i keep everything ephemeral
( this doesn't work if you start to allow complex rust things like borrowing, but you wouldn't need to for UI stuff )
( but this is different than real rust )
Here's an example where I run into trouble: say I have a script that contains a foreach loop, and inside that loop we do something with a Vec3. Well, we can't store a Vec3 on the stack because the interpreter stack can only store scalars - so we have to allocate heap space for it. But unless we do some kind of GC, each time we go through the loop another Vec3 is going to be allocated, so now you have all these temporaries. Sure, we can store them all in an arena and free them at the end of the script, but we can't know how big the heap is going to grow during execution.
You might be able to solve this with lifetime analysis in the script language, but that's something I haven't been successful with.
You can totally store Vec3 on the stack!
In Rust, yes
Box is a pointer, that isn't the stack :p
I thought we were talking about the VM stack not the native stack, please correct me if this was incorrect @rare whale
Yes, the VM stack
But, going back to your original point
already in rust, you do not know how many Vec3 you might store in a Vec in a for loop
before a function calls and they all drop
OOM errors are a problem you can solve by canceling scripts that grow too large, but it's not something generally solveable
but in your case where we ephemerially allocate it
we can just put it on a Box<dyn Reflect>
I don't want to get this channel too far off topic
We can move to #modding-and-scripting if you prefer
I brought all this up because it relates to BSN, specifically embedding logic in BSN assets
Yeah, i'm still on the boat that logic itself shouldn't live in BSN assets, we should just make hotpatching really good and first-class
See, I disagree - those are solutions to two different problems
could you elaborate on the difference? I'm not getting it yet*
what properties do bsn assets* have, that rust files don't, if we have good hotpatching?
Expressing “I want this rect to be half the width of the parent” is not something you want to put in hot reloadable code I think
Trying to think of the right words to express this. OK let's try this. Before I started working on Bevy, I spent two years building a game engine in JavaScript based on three.js. Since I was using JS, I had hot patching capabilities that Rust programmers could only dream of. I could modify anything in the engine and instantly see the results. However, this did not eliminate the need to be able to put custom logic within assets for characters, dialogue trees, scenery, quests, and so on.
Because this was a large open-world game, I didn't want to have to keep in memory all the time the logic for every character in the world, particularly logic for characters that were currently "off-stage". So some kind of dynamic loading and unloading of behavior was required. Fortunately, this was easy to do in JS.
I see, that makes sense, you don't want to have the entire game itself stored in ram
To be fair, most of the loadable game logic was in .ts files in the assets folder, which could be loaded and then eval()'d as needed. But that's harder to do with Rust since any .so file or DLL is going to be target-specific.
yeah, but also shipping anything to deal with ECS that is rust-like is going to end up a huge undertaking. Relatively complex ide extensions will need to be maintained to support debugging, subtle differences between what we support and what rust supports, it's massive, and doing a non-rust like integration is also quite sad, because we opt out of the ecosystem and start building our own. It's a whole mess either way.
We could ship something like the gLTF interactivity spec, but that is visual not textual ( not easily textual ) and also doesn't fit our domain
What this all boils down to is that I don't know what the right answer is
Honestly, out of all the options presented here, pre-processing individual rust files as their own dynamic libraries per platform, and then dynamically loading and unloading them just to call the function that creates whatever scene feels the least janky, which is crazy
i might try a PoC of that, see how it goes
Providing a scriptable environment that’s deeply integrated with a rust based engine and doesn’t rely on any kind of heavy infrastructure wouldn’t detract from the rust ecosystem, it would add to it
At the end of the day you can’t ignore the non functionals of rust
A scripting environment just provides better UX for the end user
Theres a reason people use gdscript
Not because it’s their favorite language. It’s just the path with least friction
I'm greedy, the best option would be it has all the benefits of a scripting language, but still be rust: ( I disagree with the commonly held notion that dynamic typing is a benefit of scripting languages ) but the hot-reloadability, the speed of iteration, the ability to load it dynamically. These are things I would want to work towards providing in rust. I want the path of least friction to be just writing rust files, the idea that i could pull in some iroh p2p networking code directly into my bsn asset logic if need be is awesome to me. That i could use someone's color calculation library they wrote in rust anywhere in bevy is awesome, because all of bevy is rust. I am fearful of losing that capability. Broad ecosystem support of rust means i can just grab libraries from wherever and use them everywhere.
A scripting language would necessarily lose out on that. There would have to be some form of FFI.
maybe it's not possible
I understand the appeal, there just isn’t a lot of empirical evidence to support it’ll work. There’s overwhelming empirical evidence to the contrary as virtually all game engines have their own scripting environments
It’ll be an uphill battle whether it’ll work or not
Going off topic... but I sometimes wonder what Bevy but in C# would have been like considering it solves these issues already.
Moving this over to #modding-and-scripting but i might be cooking?
could someone tell me if anything of relevance to bsn! was said since talin started this?
i don't think so
nothing other about bsn was said during the convo except talking about scripting stuff
I think it was about embedding a scripting language inside a bsn file
People only use gdscript because it's basically the only choice for Godot. Despite being advertised as having "first-class" support, C# support in Godot is absolutely terrible and got borderline unusable with Godot 4.
If you're experienced with C# and love it enough to ignore the jank, you can kinda wrangle it together. But beginners who hypothetically would've been fine with C# in say Unity, will inevitably pick GDScript for Godot because the out-of-the-box experience is just terrible.
please, move channels
oops sorry
Is it a known issue that bsn! blocks seem to significantly slow down rust-analyzer's code analysis/completion proportionally to how many of them there are in the same function?
No, and thats strange, afaik the macro isn't doing that much work
could it be related to the huge error messages generated in that example? does it happen even in the absence of any errors?
This happens whenever I change any code inside a function with multiple bsn! blocks, so I don't think it's related to the errors.
https://github.com/bevyengine/bevy/pull/24474 This milestone PR needs a second review please
Apologies; it wasn't really my intention to get into the weeds on scripting languages. Really what I wanted to talk about was the high-level strategy for specifying behavior and game logic within BSN assets. I wanted to compare general approaches, not get into low-level details; I wanted to know whether I should abandon lines of research, or whether there are avenues that I had not considered, and whether other people had done anything substantive in this area yet.
The BYOA widget problem is only interesting in this case because it's a small-scale problem that has crisp boundaries. There are many other possible use cases; I can see for example using BSN with custom relations to model hierarchical character goal trees, with components representing specific behavior types - essentially using entities to represent a serialized expression graph, but with backtracking evaluation.
I've mentioned it before but bevy_gearbox is all about defining statecharts in entity hierarchies. I haven't been using it with bsn since I'd like to wait for release, but bsn will massively improve gearbox. In fact I would say that without it gearbox is barely usable.
Some things that stick out as far as "high level" stuff:
- When a state machine is spawned it needs to be initialized. I do this by detecting the insertion of a component on the root and assuming everything is ready to go. So that initialization component needs to be inserted last, after the entire hierarchy is built. This is a huge painpoint. It's a massive footgun that's very challenging to signal to users. This is specific to gearbox but I imagine this kind of insertion order sensitivity will be a general bsn issue if it's not somehow solve implicitly by bsn.
- Theoretically, an entities behavior is defined by it's current state. My goal is to be able to query on that state, meaning that state is ultimately represented as a component on the root entity. But in the actual state machine, state is represented by active state entities which are children/grandchildren of the root. Bridging this gap has been awkward. Right now I have the notion of a "StateComponent" which is inserted into the root and removed when its state entity activates. Though this is somewhat awkward I'm not really sure how else to do it. I imagine most behavior-as-entity-hierarchy systems would run into this. Maybe you just put state components on the state entities, query on those + an "Active" marker component, and get the root? Maybe a query parameter that lets me do something like
WithState<MyStateComponent>whereWithStateautomatically resolves state entities to their root? Idk
To be clear the WithState would basically be "Does a child have the given component." With the little fiddling I've done with query filters this would be quite the hack.
For your first point: I don't think its inherently solved by bsn but there are potentially ways to work around it or even solve it. If you just need to insert the initialization component last and its okay if theres other commands in between, you could use template() which gives you access to the world, allowing you to queue a insert command to be run at the end of this commands flush. This was already possible before sith lfiecycle hooks/observers btw, with the same drawbacks: The command ia queued to the end of this flush, meaning many user commands might run in between.
I'm pretty sure lifecycle observers run during scene spawning directly when each component is inserted, but thats something that would need to be checked (not sure how this behaves with BundleWriter). If it already runs them after the full scene spawn, great! I don't think so tho. I think its plausible to potentially delay them to the end of the scene spawn, or otherwise provide a mechanism for running something directly after a scene is spawned. Triggering a EntityEvent after the scene has spawned would actually be pretty reasonable to implement.
Regarding your second point, i think its not very relevant to bsn, its more about querying. I think its possible a custom QueryData could do this, or otherwise a custom SystemParam definitely could (with enough generics fuckery, it could even do this while acting like a Query)
and whether other people had done anything substantive in this area yet
Flecs script takes the scripting language approach, but with features that are purely declarative. So not a general purpose scripting language, more a way to express a side-effect free transformation from input data -> entity tree.
Point 1: I'll have to look into this! Definitely would like to make this something users don't have to think about.
Point 2: Yeah definitely not a bsn thing but it was a difficulty I'd run into trying to encode behaviors into trees so I thought it was worth a mention. After seeing your message I was looking at a QueryState system param that gets the query data from the root entity but which lets you apply the query filter to the state entity (or some arrangement like that) and I think I like the pattern! So thank you 🙂
i just un-draft-ed the docs PR https://github.com/bevyengine/bevy/pull/24464
cc @split harness review please! i expect quite a bit of feedback
If you assign the bsn blocks to variables rather than just having them "float", does that change anything?
(all of the blocks except the last one, which is returned as the impl Scene)
I'll take a look
Nope, same thing
I'm testing this on scene/bsn.rs example, it's easy to reproduce there
Cool I'll give it a look / see if I can repro
I think it's related to on templates. When I comment them out it doesn't slow down nearly as much.
Ah that would make a lot of sense, and it would probably be fixed by my autocomplete work (which will also significantly reduce the parsing cost)
You can test whether Expr parse speed is the culprit by trying a commit before this one https://github.com/bevyengine/bevy/pull/24174
Doesn't seem to change anything. I'm guessing it's hitting some slow path in r-a because of all the generics on OnTemplate maybe?
Does template(|context| {}) of equivalent "code size" (source code, not generated code) slow things down in the same way, or is it just on?
It still slows things down, but considerably less. For 6 bsn! blocks in example's fn ui:
- Just components: 341ms
- on: 2271ms
- template: 537ms
How are you getting these measurements? Screen recording?
Cool yeah that does point to "generic / type system" stuff and not somthing directly BSN related
Added this to vscode config:
"rust-analyzer.server.extraEnv": {
"RA_PROFILE": "*@3>100"
}
And then view them in Output -> rust-analyzer Language Server
Ooh very nice
I wonder if type-erasing/boxing can fix this?
Perhaps. Although if the "cost" is type resolution, I'm not convinced breaking the chain a little early is going to win us anything
Boxing a complicated type still requires the compiler to know that type
But thats a simplified view of the world. My answer is "I have no clue and testing that would be helpful" 🙂
Compiler yes, but this is more about r-a, which might do this differently
But yeah, this needs more investigation
I just did a first review pass and pushed an editorial pass
I'm going to do a proper review tomorrow too @vapid forge
https://youtu.be/73Do0OScoOU?si=WTOFG4fKlJ2hyZlH&t=5692 - apparently Flecs wasn't the only ECS to come up with components as entities & integrated prototype inheritance (IsA relationships). Highly recommend watching this.
It's uncanny how close to Flecs the model he describes is
A bit earlier on he talks about first-class queryable relationships: https://www.youtube.com/watch?v=73Do0OScoOU&t=5410s
This is very cool, but it sounds like it would be a better fit for, and seen by more people in, #ecs-dev
This relates directly to the scene system which is explicitly not-ECS, so I think this is the better location
e.g. IsA inheritance is most similar to BSN scenes
(though I agree that it should be part of the ECS, but it's not)
Is it? Scenes are pretty distinctly a spawn-time thing, almost more of a fancy constructor syntax, than something which it makes sense to care about in systems, while from what i understand of IsA and the talks you linked, they're inherently meant for querying and systema to use.
like, maybe if you think of BSN as being more akin to a gltf file it makes more sense
The implementation is different, but the role it fulfills ("instantiate stuff of this kind") is similar
The question around "should this be part of the ECS" vs. "should this just be a fancy constructor" is exactly the discussion we've had in this thread, which is why I think this is the right place to discuss it. I'm fine with posting it in #ecs-dev, but it can't really be seen separately from BSN
At mimimum it's another reference to prior art that argues in favor of integrated support
yeah idk i really don't see how they're at all similar roles
or alternatives of each other
if we tried to do this with BSN it would change the very core of what its trying to become: serializable
If you listen to the talk you'll hear that they instantiate things like Warrior with an IsA relationship, and that they use this relationship to resolve which properties that entity should have ('walk up the hierarchy to find X'). I could be wrong, but I believe that property overriding is very much a thing that falls under the BSN umbrella
Also, if you scroll up (far), you'll see me and cart having this exact discussion around the IsA relationship and the flecs prefab model
Anyway I'm not really interested in having a discussion around where this should be discussed 😛 I think the talk itself is way more interesting
If a mod thinks this should be moved, I'll move it.
BSN ultimately generates this code:
component.field.nested_field = 5
component.field.nested_field = 8
when someone overrides the nested fields value to be 8
Very possibly, i did listen into it and yeah, its interesting. I was skipping through the timestamps, questioning why this was related to BSN, until i decided to ask you to move it. If you don't want to, welp, i tried.
Search up this thread for "IsA". You'll see why this is the right spot
I was there the last time
if anything, that last convo is why i'm so sure this isn't the right place (anymore, probably)
Last thing I heard cart say was that nothing’s been ruled out yet.
I do think this is a fundamental question that should still be on the table, but I'm not yet ready to engage further with it while we prep for release. Thanks for the link!
Just pushed my autocomplete fix PR:
https://github.com/bevyengine/bevy/pull/24519
@vapid forge
Also @tawdry owl (in case this happens to improve the perf stuff you were talking about)
oof, i should already be in bed. sorry, i probably won't be able to review this for a solid day
Not really. This problem isn't macro related - it's still there even with a fully expanded macro, so probably recomputing all the generics for IntoObserverSystem takes too much time for some reason.
I would love to spend some time attempting a simplification pass here
_but I don't have it 😆 _
Maybe this is something that should be fixed on r-a's side instead? Right now splitting each bsn! invocation out into separate functions is enough to keep devex reasonable, so I don't think it's critical
Possibly! Although theres a solid amount of type complexity in the observer system code path.
Cutting down on that might also help with compile times generally