-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Next Generation Bevy Scenes #20158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Next Generation Bevy Scenes #20158
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial discussions / FAQs
#[reflect(Component, Default, Clone, PartialEq)] | ||
pub struct Mesh2dWireframe(pub Handle<Wireframe2dMaterial>); | ||
|
||
impl Default for Mesh2dWireframe { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the deal with all of these manual Default impls?
Handle
no longer implements default. This is because it now implements GetTemplate
. Implementing Default
would make it match the blanket impl of GetTemplate
for T: Default + Clone
, resulting in conflicting GetTemplate
impls. This is in line with our goal of making Handle
"always strong" (ex: removing the Handle::Uuid
variant). That means that anything using Handle will need to derive GetTemplate
instead of Default.
Rather than port everything over now, I've added a Handle::default()
method, and anything currently relying on default handles now has a manual Default impl that calls Handle::default()
instead of Default::default()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume that Handle::default
will be fully removed once the porting is complete? The diff may be easier to read and grep for it we call it something else: either Handle::null
or Handle::temporary_default
would work well.
|
||
fn button(label: &'static str) -> impl Scene { | ||
bsn! { | ||
Button |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does bsn!
not separate Scene patch entries with commas? That doesn't feel very Rust-ey
- Flat entities (no wrapper tuple) are allowed. In Rust,
x, y, z
is not a valid expression - Unlike previous proposals, relationships are now expressed as peers of the components (old proposal:
(COMPONENTS) [CHILDREN]
, new proposal(COMPONENTS [CHILDREN])
). I think in all cases, they are more legible without commas, especially in the very simple / very common case ofSomeComponent [ CHILDREN ]
. The comma version of this looks weird, especially without the tuple wrapper:SomeComponent, []
. - Removing the commas helps visually group entities when they would otherwise be lost in a sea of punctuation. I think that in practice this is extremely noticeable (try adding the commas to the various
bsn!
examples and establish your own opinions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Removing the commas helps visually group entities when they would otherwise be lost in a sea of punctuation.
I'm not convinced its more readable - I prefer trailing commas, e.g.
BorderColor::from(Color::BLACK),
BorderRadius::MAX,
BackgroundColor(Color::srgb(0.15, 0.15, 0.15)),
As a bonus, it pairs better with the straight rust code - this makes it easier to interchange between the two.
There's another example in examples/ui/feathers.rs
in fn demo_root
in the first button. Below is with the comma's, which I think is easier to read tbh
(
:button(ButtonProps {
on_click: callback(|_: In<Activate>| {
info!("Normal button clicked!");
}),
..default()
}),
[(Text::new("Normal"), ThemedText)],
),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for children we should be more explicit than just [ ]
, I think @[ ]
could be good (would pair nicely with ${ }
for expressions as mentioned in a different thread). For a beginner, it's easier to search Bevy docs for @[
than it would be for [
. It also means we could use similar syntax in the future for other relationships / features.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not trying to argue for commas (though I kinda like them), but a simple mitigation for 1 is to simply use parentheses for the macro: bsn!(A, B, C)
and it looks like a valid tuple again.
As far as 2 (probably more controversial), I probably won't use the children shorthand. I like that Children
isn't special-cased in the ECS, it's "just another component", and the shorthand syntax kinda obfuscates that. I'd be much more likely to use Children [ ... ]
for consistency in my own scenes.
I think 3 is a lot more opinion-based, though, and I don't have anything to add.
I realize commas aren't the most important detail and I'm not trying to waste time with a big argument. Just my two cents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the choice to not have commas between components - I would prefer a more rusty syntax with commas delimiting all components. I would also prefer the distinction between what is a component and what is an entity to be more explicit - I think it would be nice if all entities can be marked with #
- I think it would make it easy to see what is an entity and what is a component at a glance. This would make #{name}(...)
required for each entity. It has the added advantage of allowing users to build a correct mental model about what relationships look like (a collection of entities).
edit: not sure if intentional but the # prefix == entity association is also a nice reminder that an entity is just an 8 byte number - whereas a component can be something larger.
bsn! {
#Root(
Player,
Sprite { image: "player.png" },
Health(10),
Transform {
translation: Vec3 { y: 4.0 }
},
on(|jump: On<Jump>| {
info!("player jumped!");
}),
[
#NamedEntity(
Hat,
Sprite { image: "cute_hat.png" },
Transform { translation: Vec3 { y: 3.0 } } ),
),
// still an entity - but without a name
#(:sword, Transform { translation: Vec3 { x: 10. } } ),
]
)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bsn! #Root{
I believe this wouldn't work because of mandatory delimiters around function-like proc macro invocation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right... got trolled by an LLM, mb.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like the choice to not have commas between components - I would prefer a more rusty syntax with commas delimiting all components. I would also prefer the distinction between what is a component and what is an entity to be more explicit - I think it would be nice if all entities can be marked with
#
- I think it would make it easy to see what is an entity and what is a component at a glance. This would make#{name}(...)
required for each entity. It has the added advantage of allowing users to build a correct mental model about what relationships look like (a collection of entities).
Maybe I am just not familiar enough with Scenes, but after going through the proposal and feeling like I understood everything I was quite confused what we are actually describing with the macro. An entity? A Scene? (I get that a Scene is also just an entity at the end of the day).
I like your idea to differentiating more clearly between component and entity and merging it with Name.
Not trying to argue for commas (though I kinda like them), but a simple mitigation for 1 is to simply use parentheses for the macro:
bsn!(A, B, C)
and it looks like a valid tuple again.As far as 2 (probably more controversial), I probably won't use the children shorthand. I like that
Children
isn't special-cased in the ECS, it's "just another component", and the shorthand syntax kinda obfuscates that. I'd be much more likely to useChildren [ ... ]
for consistency in my own scenes.
Google tells me with curly braces the macro can be used as a statement, but I this does not seem to make a difference in the examples posted yet.
Agreed on the Children. I don't think it should be special cased, especially without commas I think X []
is very confusing unless I already know that X is actually a Component and not a Relationship (how am I supposed to know?). If anything they should be even more obvious besides Relationship []
.
Whether making commas mandatory is a good or the best solution for any of the issues is another question, but to me it seems like the obvious thing to reach for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to add that using #Name(X, Y, X,)
for entities and @Relationship [A, B, C,]
would also make the entire thing greppable and easier to navigate, especially for larger scenes!
#
for entities, @
for relationships, ,
for component separations
(Allowing autoformatting out of the box might also be great when using bsn!()
instead of bsn! {}
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
after going through the proposal and feeling like I understood everything I was quite confused what we are actually describing with the macro.
We're describing a template. That template can be instantiated to real entities as many times as needed. That's my understanding anyway.
use variadics_please::all_tuples; | ||
|
||
/// A [`Template`] is something that, given a spawn context (target [`Entity`], [`World`](crate::world::World), etc), can produce a [`Template::Output`]. | ||
pub trait Template { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to Construct?
Template
fills a similar role to my previously proposed Construct
trait. The core difference is that Construct was implemented for the output type rather than the input type. The Construct
approach was good because it allowed us to correlate from the desired type (ex: a Sprite
component) to the "props" type (ex: SpriteProps
). Note that "props" was the term I used for the "template" concept in my previous proposals. However the Construct approach was overly limiting: there were a number of desirable use cases where it resulted in the dreaded "conflicting implementations" error. Template
lets us have our cake and eat it too. Multiple Templates can produce the same output type. The GetTemplate
trait can be implemented for types that have a "canonical" Template
, but we can always use raw templates directly in cases where a type cannot have a canonical template, shouldn't have one, or we want to override the canonical template.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very happy with this evolution! Template
tackles a lot of the concerns I had about Construct
. Flipping it around this way makes much more sense 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, I expressed this in the discord but this does seem like the best iteration. All the nice bits of both "directions".
} | ||
|
||
impl SpawnScene for World { | ||
fn spawn_scene<S: Scene>(&mut self, scene: S) -> EntityWorldMut { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to efficiently spawning scenes "directly" without resolving to the dynamic format?
For awhile I supported this by unifying Scene and Template: Scene was implemented for any T: Template<Output: Bundle>
. I then implemented Template for TemplatePatch and the SceneList impls, meaning that Scene was just a specialization of Template, and whether bsn!
was a Template or a Scene was a matter of what trait you decided to return. Scene could then expose a spawn
function.
I opted for a hard split between the traits (and the types) for the following reasons:
- This conflation resulted in a number of confusing / inconsistent behaviors:
- When spawned as a template, patches would step on each other if they were defined twice. Ex:
Player { x: 10. } Player { y: 10.}
would behave as expected when spawned as a Scene, but they would step on each other when spawned as a Template (for performance reasons, patches returned a Bundle with the full patch type, preventing unnecessary archetype moves). - Attempting to spawn a
bsn!
that uses asset inheritance, directly as a template, would fail at runtime. - The more interestng "stateful" behaviors, such as patching inherited children, would also fail at runtime.
- When spawned as a template, patches would step on each other if they were defined twice. Ex:
- It complicated the conceptual model. Users had to know "am I using the subset of
bsn!
that can be spawned directly" - Templates that didn't return a Bundle needed specialized Scene impls to be used in that context.
I'm open to reconsidering this if we can address those concerns, or if we can't make the dynamic form suitably performant. Being able to directly / cheaply spawn a bsn!
subset certainly has an appeal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for highlighting this and the reasoning here! This is helpful.
I really think we should reconsider using the dynamic format though. Deferring scene spawning is worrying as pretty much every other function on World is instant. spawn
, insert
, insert_resource
, ... all instantly apply their affects. Especially something named spawn_scene
I would be (am?) very surprised if it didn't actually spawn a scene but just spawned a thing that will eventually spawn a scene. We already sorta have this problem with SceneRoot
, and it's not something I would like us to carry over for the general case. At least SceneRoot
has the benefit that it just looks like a component spawn, which is exactly what it is - this maps well to the mental model.
Your concerns about stuff failing at runtime is understandable, but I'd rather have that than users "coping" with the fact that spawning isn't really spawning - that's basically why spawning scenes requires using SceneInstanceReady
. Another way to cope with this is using the SceneSpawner API directly, though it's much more cumbersome. BSN has a chance to change that though!
Maybe this is an unpopular opinion, but I'm ok with panics as long as they are load and unavoidable - in this case, if you use an "invalid" BSN feature for this sync case, that panicking is almost certain to be hit every time. It's annoying, but it's also easy for users to see and fix.
Edit: I realized after the fact that we can just make it not panic, and just return a result now that we have fallible stuff! At least we'll get error messages about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think we should reconsider using the dynamic format though. Deferring scene spawning is worrying as pretty much every other function on World is instant.
spawn
,insert
,insert_resource
...
I do agree that we should reconsider having the static resolution if possible/more performant . The way we could statically produce the whole bundle in the old proposal with EntityPatch
was really elegant!
However, I don't think its needed to solve the deferred/async scene spawn problem. If we were to have APIs for sync spawning (returning Result, with error for unresolved stuff or similar), those APIs could still use the dynamic version.
|
||
#[derive(Component, Debug, GetTemplate)] | ||
struct Sprite { | ||
handle: Handle<Image>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to "Template/Prop field syntax"?
This PR actually implements it! That being said, I am proposing we cut it, as it complicates the mental model, in practice it doesn't seem to be needed often, and in cases where it is, that functionality can be added manually to templates that need it. I really don't like that it forces manual user opt-in for each template to anticipate that actual value passthrough will be needed. If we decide inline handles should be supported in templates, we can add a HandleTemplate::Value(Handle<T>)
, and it will magically be supported everywhere without special bsn!
syntax or manual opt-in.
This is what it looks like in the current system:
#[derive(GetTemplate)]
struct Player {
#[template]
image: Handle<Image>,
}
bsn! {
Player {
image: @"player.png"
}
}
let handle = assets.load("player.png");
bsn! {
Player {
image: TemplateField::Value(handle),
}
}
Sprite { | ||
handle: "asset://branding/bevy_bird_dark.png", | ||
size: 1, | ||
nested: Nested { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional nested struct names?
In theory, we could omit nested struct type names:
// Type name specified
bsn! { Transform { translation: Vec3 { x: 1. } } }
// Type name omitted
bsn! { Transform { translation: { x: 1. } } }
However there are some issues:
-
Unnamed field structs are ambiguous with tuple values
foo: Foo(1, 2) foo: (1, 2)
This is potentially resolvable by treating tuple values as patches too, as the field vs tuple accessors will be the same.
-
Named field structs without fields specified are ambiguous with expression blocks
foo: Foo {} foo: {}
We could resolve this by just picking a winner (ex: empty expression blocks are actually empty struct patches, or vice versa). However that will make error cases (which will happen) potentially confusing for developers.
-
Named field structs with fields require reaching further into the struct to disambiguate between expressions and struct patches.
// V V at both of the points marked with 'V' we don't know if this is an expr or a struct patch foo: { x: 1 }
This is only a minor complexity issue from a parsing perspective as we can obviously look forward. However it has implications for autocomplete because when typing
x
we're still in a quantum superposition of "expression" or "field patch". We need to pick a winner, but a percentage of the time we will be wrong. In those cases, autocomplete will yield the wrong results, which will be painful and confusing for people.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should use a more explicit syntax {{ }}
or ${ }
for expression blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO the explicit variant does communicate the intent better. While {{ }} is faster to type, ${ } is easier to distinguish and so easier to read (for the human reader) As sourcecode is much more often read then written, I would prefer ${ } style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we should consider using a more explicit/less ambigous expression syntax. Being able to omit struct names is very appealing. It would make the DX/discoverability so much nicer, especially whensince we have nice autocomplete support for members.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another benefit to the longer syntax is that it will be familiar to people used to other tools, making it easier to distinguish and pick up on.
${ }
for expressions, though also often for string interpolation, is used by: Shells (bash etc), JS/TS, Groovy, Perl. This is the syntax I personally prefer.
{{ }}
on the other hand is used by templating languages like Jinja, Nunjucks, Vue, Angular, Django - but also mostly for string interpolation. The common format for "logic" is {% %}
but that's entirely un-Rusty.
Either of these are far nicer options than single { }
, not only due to familiarity, but easier grep-
ability as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least in bash ${foo}
is the same as $foo
, it's long-form syntax for variable access, with some possible extra options (e.g. echo "${PATH:10:14}"
will give you the 10th to 14th character in $PATH
), $(foo)
is used for subshells.
But Rust isn't bash so it's not necessarily a good idea to get inspiration from there. The meaning of any already-existing Rust syntax involving {}
shouldn't change so Foo {}
is a struct and {}
, without any leading something, is a block, how about :{}
or ?{}
or #{}
for "I don't want to write this identifier, infer it for me", and leave block syntax alone.
All in all though I don't think it hurts to be a bit unwieldy and verbose when it helps clarity. Scene data is, in the end, data, if you want anything complex you're probably going to generate it in one way or the other, and if you don't the verbosity doesn't hurt because you're not writing much of it.
So, instead of worrying about bsn! { Transform { translation: Vec3 { x: 1. } } }
being awkward the question should be "how hard will it be for people to implement a macro so they can write translate!(1*up+2*down)
".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least in bash ${foo} is the same as $foo, it's long-form syntax for variable access,
You're right, i was actually thinking of $() for subshells but that's obviously not the same.
The meaning of any already-existing Rust syntax involving {} shouldn't change so Foo {} is a struct and {} , without any leading something, is a block
While yes, that is the case, its disambiguated far more clearly by surrounding syntax in Rust. Syntax which bsn won't have, which makes it not just more difficult on a technical level, but far more importantly for me on a visual level.
Scene data is, in the end, data, if you want anything complex you're probably going to generate it in one way or the other, and if you don't the verbosity doesn't hurt because you're not writing much of it.
I disagree with this sentiment. If this was the case, there wouldn't be any need to even create a custom format, and especially not as a macro. You may want to work this way, but bevy has always promised that even with an editor it will stay a code-first engine - and that should include being able to write more complex structures easily and efficiently. If you're automatically generating things you'll either generate asset files (which this isn't about) or you'll generate the underlying Rust code. But you won't generate bsn!
macro calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bsn is very much also supposed to be an asset format, and a lot of it will probably be editor generated -- e.g. from blender (replacing the semi-hackish "components in .gltf" approach), or bevy's own editor, say gui layouts. Levels, unit stats, lots of things done by people who couldn't :q
their way out of a wet vi.
For that type of use you want a plain data format, something that's easy to parse and generate and efficient to process at load time (because asset modding, I guess). Usability as a text format is a nice-to-have at that stage, but ultimately can be tacked on as an additional compilation step. Something along the lines of nickel: The ergonomic (and programmable!) part is a seamless extension of the plain data format.
> RegisterSystem<In> for IntoWrapper<I, In, Marker> | ||
{ | ||
fn register_system(&mut self, world: &mut World) -> SystemId<In> { | ||
world.register_system(self.into_system.take().unwrap()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this leak? We need a way to unregister the callback when the owner is despawned.
This is why I had proposed using cached one-shots for inline callbacks, but we have to solve the type-erasure problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does leak in its current form. This style of "shared resource" cleanup feels like it should be using RAII, as we discussed previously.
I'm not sure how the a cached one-shot would solve this problem, as it would still leak in that context as it doesn't use RAII?
From my perspective the only difference between this approach and register_system_cached is where the cache lives (inside the template, which is shared across instances, or inside world).
world.register_system_cached
can be directly swapped in here for world.register_system
if you think it would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because cached one-shots eventually get removed automatically, the Callback
instance can be safely dropped without needing a destructor.
For the SystemId
variant, we will need destruction, which I was attempting to solve with an ownership relation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I understand the disconnect. We wouldn't use register_system_cached
, that just returns an id. We'd use run_system_cached
. That means that Callback
retains ownership of the closure and is responsible for dropping it.
}, | ||
..Default::default() | ||
position_type: PositionType::Absolute, | ||
left: Val::Px(4.0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd really like to be able to say 4.0px
here, or even 4px
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to include use Val::{Px, Percent}
in the bevy_ui prelude, which would allow the still rust-ey but much shorter: left: Px(4.0)
syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was also a proposal for an extension trait that would allow 4.0.px()
, but tbh I prefer the Px(4.0)
syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I particularly like that
- The "enum variant export" solution requires no special casing in the macro, meaning anyone (3rd party crates, app devs, etc) can utilize the pattern for their own units. This means everything will look / feel consistent.
- It works with RA go-to definition, autocomplete, doc hover, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could .into() help turn Px(4.0)
into Px(4)
?
label: C, | ||
) -> impl Bundle { | ||
( | ||
pub fn checkbox(props: CheckboxProps) -> impl Scene { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to the label?
Both checkboxes and radios contain a mix of iconic built-in children (the actual "box") and a parametric child (the label). This allows toggling by clicking on the label text, because otherwise checkboxes are an annoying small hit target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The label is supplied by checkbox inheritors, if they so choose. Clicking still works as expected here, to my knowledge. I'm not getting annoyingly small hit targets in the feathers.rs
example.
fn ui() -> impl Scene { | ||
bsn! { | ||
Node { | ||
width: Val::Percent(100.0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to write 100pct
or even 100%
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm. 100.0
seems standard IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe alias Percent
as Pct
, along with removing Val:: , then you'd have Pct(100.0)
), | ||
button( | ||
ButtonProps { | ||
on_click: Callback::System(commands.register_system( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that commands.register_system()
is leaky too - I was working on a solution for this.
examples/ui/feathers.rs
Outdated
), | ||
} | ||
[ | ||
:radio Checked::default() [(Text::new("One") ThemedText)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not understanding something here about the way the children lists are merged. Or at least, what's happening here doesn't match my naive intuition, I would have thought that [ ... ]
would replace, rather than appending, children. This needs to be made clear in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the "append" behavior clear in the Inheritance section of the description. I agree that when the actual docs are written, this should be called out explicitly.
examples/ui/feathers.rs
Outdated
), | ||
} | ||
[ | ||
:radio Checked::default() [(Text::new("One") ThemedText)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this Checked::default()
instead of Checked
in order to disambiguate it from relationship syntax? In the examples from the PR description, there are a few uses of e.g. Node [ ... ]
that surprised me as well because it looks like it should be parsed as relationship syntax and thus fail because Node
isn't a RelationshipTarget
.
I think an explicit marker is needed to distinguish relationship syntax from patch + children shorthand, something like Children: [ ... ]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup resolving this is at the top of my todo list. We discussed this in the working group Discord a few days ago.
{children} | ||
] | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(posted here for threading, line of code irrelevant) In the PR description you write
It is generally considered best practice to wrap related entities with more than one entry in () to improve legibility. One notable exception is when you have one Template patch and then children:
bsn! {
Node { width: Px(10.) } [
Node { width: Px(4.0) } [
// this wraps with `()` because there are too many entries
(
Node { width: Px(4.0) }
BackgroundColor(RED)
[ Node ]
)
]
]
}
What does this mean? I can't make heads or tails of this, this is an exception to wrapping for children because too many entries but then we wrap anyways? why is it too many entries? how many is too many? is this an optional "best practice" or is the exception that its mandatory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an optional best practice. Not mandatory. To help illustrate:
bsn! {
Node { width: Px(10.) } [
// This has one "entry", so no need to wrap with ()
Node { width: Px(4.) },
// This has two "entries", so we wrap with ()
(Node { width: Px(4.) } BackgroundColor(RED)),
// This has two "entries", but one is Node and the other is [], so we opt to not wrap it in ()
Node { width: Px(4.) } [
Node
],
// We _could_ wrap it in () if we wanted, but I'm arguing that this does not improve
// legibility, so we shouldn't include it in this case
(Node { width: Px(4.) } [
Node
])
]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhh it's because ()
is used to group all components for a specific entity, while ,
is used to separate entities. It's because this:
bsn! {
Human [
// First child entity
(
Arm
Health(100)
Length(2)
),
// Second child entity
(
Leg
Health(75)
Length(3)
)
]
}
Is easier to read than this:
bsn! {
Human [
// First child entity
Arm
Health(100)
Length(2),
// Second child entity
Leg
Health(75)
Length(3)
]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bsn! {
Human [
Arm
Health(100)
Length(2),
Leg
Health(75)
Length(3)
]
}
Is a little spooky syntax to me. Since a single comma (that's easy to miss!) somewhere can completely change the meaning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bsn! {
Human [
Arm Health(100) Length(2),
Leg Health(75) Length(3)
]
}
I hope we don't mega-bikeshed this one syntactic feature. There's ways to make it work either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this issue is particularly important as the entities get bigger - for smaller (dare I say toy) enties with simple components, you can segregate the entities by line, but as each child entitiy gets more complex, the risk increases.
Maybe we need a complex example to ruminate on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With proper tooling, I don't think this would be a problem in practice. A formatter could use some heuristic to determine how many components is too many, and then place them in parentheses.
foo: 10 | ||
}, | ||
} | ||
Gen::<usize> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you considered allowing omitting the :: in the turbofish as a syntax sugar? Not sure if it can be resolved in all cases but it seems like it should be possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hash: u64, // Won't be serialized | ||
name: Cow<'static, str>, | ||
} | ||
pub struct Name(pub HashedStr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these changes feel very uncontroversial and isolated/easy to land without even needing the greater bsn as justification like this one imo. Thoughts on landing bits like these early?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think changes like this could land early.
@@ -145,6 +145,7 @@ default = [ | |||
"bevy_picking", | |||
"bevy_render", | |||
"bevy_scene", | |||
"bevy_scene2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ValorZard I'm converting your top-level comment into a thread (please see the PR description):
(Not related to this comment at alll)
so this is probably out of scope from this PR, but how exactly will .bsn as a file format will work?
My original perception was that .bsn was basically JSX from React fused with the way Godot does scenes, and that seems to mostly be the case.
However, in the example you posted of the bsn! Macro having an observer callback, that’s just a regular rust function.
bsn! { Player on(|jump: On| { info!("Player jumped"); }) }
Will a .bsn file just have arbitrary rust code that runs during the game?
The idea is that .bsn
will be the subset of bsn!
that can be represented in a static file. In the immediate short term, that will not include things like the on
function, as we cannot include arbitrary Rust code in asset files.
The primary goal of .bsn
will be to represent static component values editable in the visual Bevy Editor. In the future we might be able to support more dynamic / script-like scenarios. But that is unlikely to happen in the short term.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drat. I thought I did the comment thing correctly.
but otherwise that makes sense. So .bsn and bsn! Aren’t fully equivalent. Interesting.
I guess Bevy could support something like Godot’s callables in the future maybe, and have some sort of id sorted in the .bsn file that could be converted to a function? Idk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, function reflection + a registry should make stringly-typed callbacks defined in assets very feasible.
Team::Green(10) | ||
{transform_1337()} | ||
Children [ | ||
Sprite { size: {4 + a}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering why the expression syntax { }
is required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally thought it was because there aren't any commas to separate field assignments, but that actually isn't the case here since its a struct constructor.
use std::{any::TypeId, marker::PhantomData}; | ||
use variadics_please::all_tuples; | ||
|
||
pub trait Scene: Send + Sync + 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are Template and Scene separate types?
Could Template not just be expressed as patching an empty scene?
E.g. Godot has a single scene type.
handle: Handle<Image>, | ||
} | ||
|
||
#[derive(Component, Clone, Debug, GetTemplate)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does GetTemplate need to be manually derived?
The GetTemplate traits feels... weird to me. Creating a template from a type seems like an internal detail to me. As a bevy user, I just want to think in terms of entities, assets, components, and helper functions that return impl Scene. Ideally I just derive component on my type and can automatically use it in scenes/templates.
Can we not just blanket derive GetTemplate, and hide it as an internal detail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, GetTemplate
here is more manual than I want it to be. Could we do something like:
impl<T: Template> GetTemplate for T::Output {
type Template = T;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sadly goes directly against the point of GetTemplate.
The idea as I understand it, is that a Template is like a factory. And you can have several factories producing the same type of thing. So there's not a one-to-one correspondence between outputs and template types.
GetTemplate is there to express that sometimes there should be an obvious template for a type.
But I'm 99% sure this impl block is not valid, because it can and will create conflicting impls. (Even if it didn't create conflicting impls, rustc cannot prove it.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could make a ComponentTemplate<T: Component>(T)
type that implements GetTemplate, and then in the bsn! macro, it could automatically wrap components in ComponentTemplate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would it know what Template logic to use for T: Component
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also if you would like to try your hand at any reframings, you are welcome to! The best way to convince me there is a better approach is to make it happen. To keep the scope down / make experimentation easier just skip porting bsn!
and compose scenes directly in Rust.
} | ||
} | ||
|
||
fn widget(children: impl SceneList) -> impl Scene { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
impl Scene, or T: Scene works well for functions that return new scenes.
What are the options for storing a type erased scene/template(?) as a value?
E.g. I could see a use case where a plugin asks the user to provide something like a ResourceThing(Box<dyn Scene>)
, and then the plugin can use it to spawn scenes when needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scene
is object safe, so you can just box it directly. We could consider implementing Scene for Box<dyn Scene>
if we decide that is necessary. In userspace, you can also already build a impl Scene for BoxedScene
wrapper.
Templates are not object safe. There is an ErasedTemplate
type used by ResolvedScene
, but it is not usable as a Template directly. In theory it could be framed as a Template<Output = ()>
though, if we decide that would be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a long standing request of mine, see #3227. I would be extremely happy with a resolution in some form here.
@@ -58,14 +58,24 @@ struct Player { | |||
move_cooldown: Timer, | |||
} | |||
|
|||
#[derive(Default)] | |||
struct Bonus { | |||
entity: Option<Entity>, | |||
i: usize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Code span unrelated.)
Linting BSN
I work on bevy_lint
, and can write lints that help users migrate / enforce good practices with BSN. Do you have any ideas for lints that could help here?
The linter is able see basically anything the Rust compiler can, especially:
- Syntax (before and after macro expansion)
- Type and trait information
The linter works exactly the same as Clippy, so you can use that for further reference on our capabilities.
The linter doesn't work as well with formatting, however, so using it to auto-format bsn!
invocations is mostly off the table.
cc @DaAlbrecht, who also works on the linter :)
use variadics_please::all_tuples; | ||
|
||
/// A [`Template`] is something that, given a spawn context (target [`Entity`], [`World`](crate::world::World), etc), can produce a [`Template::Output`]. | ||
pub trait Template { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we currently spawn resources via templates?
If no, are you planning to allow that? Is that blocked on resources-as-components?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have a ResourceTemplate that has no effect on the entity in question and just inserts the Resource (and handles conflicts in whatever way it sees fit). I think this might be better suited to the BSN Set scenario, where we could make resources their own "type" in the set. But theres nothing stopping us from experimenting with the ResourceTemplate approach.
type Output; | ||
|
||
/// Uses this template and the given `entity` context to produce a [`Template::Output`]. | ||
fn build(&mut self, entity: &mut EntityWorldMut) -> Result<Self::Output>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so I understand the broader design: if we have templates and scenes (which can contain multiple entities), why do we need bundle effects, which primarily allow bundles to contain multiple entities?
* Added new "subpane" widget. * Added new "pane" widget.
Node { | ||
display: Display::Flex, | ||
flex_direction: FlexDirection::Row, | ||
align_items: AlignItems::Center, | ||
justify_content: JustifyContent::Start, | ||
column_gap: Val::Px(1.0), | ||
} [ | ||
( | ||
:button(ButtonProps { | ||
on_click: callback(|_: In<Activate>| { | ||
info!("Left button clicked!"); | ||
}), | ||
corners: RoundedCorners::Left, | ||
..default() | ||
}) [(Text("Left") ThemedText)] | ||
), | ||
( | ||
:button(ButtonProps { | ||
on_click: callback(|_: In<Activate>| { | ||
info!("Center button clicked!"); | ||
}), | ||
corners: RoundedCorners::None, | ||
..default() | ||
}) [(Text("Center") ThemedText)] | ||
), | ||
( | ||
:button(ButtonProps { | ||
on_click: callback(|_: In<Activate>| { | ||
info!("Right button clicked!"); | ||
}), | ||
variant: ButtonVariant::Primary, | ||
corners: RoundedCorners::Right, | ||
}) [(Text("Right") ThemedText)] | ||
), | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of duplication with having to type key_name: TypeName::Val
and :button(ButtonProps {
. One thought I had is that we could use required-components to set default values and convert each field to a component.
The end BSN would look something like this:
(Node Display::Flex FlexDirection::Row AlignItems::Center JustifyContent::Start ColumnGap(Px(1.0))) [
(:button OnClick(|_: In<Activate>| info!("Left button clicked!")) RoundedCorners::Left) [
(Text("Left") ThemedText)
]),
(:button OnClick(|_: In<Activate>| info!("Center button clicked!")) RoundedCorners::None) [
(Text("Center") ThemedText)
]),
(:button OnClick(|_: In<Activate>| info!("Right button clicked!")) ButtonVariant::Primary RoundedCorners::Right) [
(Text("Right") ThemedText)
]),
]
The biggest drawback I see is that the fields themselves no longer autocomplete - you simply have to know which options are available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO not supporting autocomplete will reject a lot of people as 'this system is broken, do not use it', which will erode trust in the whole system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this critique (about using atomized components) is really about the design of Feathers and not BSN. Which is fine, but probably needs to be discussed elsewhere. BSN should ideally work regardless of component design choices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The biggest drawback I see is that the fields themselves no longer autocomplete - you simply have to know which options are available.
You personally might not use autocomplete that much, and be able to work well in systems with a "need to know what's available" approach, but i can promise you this is a dealbreaker to many people.
The solution to your issue, IMO, shouldn't be removing the field names (keys), but making the struct/enum types (values) shorter, like variant: Primary
. This should already work by bringing the individual variants of enums into scope (see the Px
idea)
What i think is worth considering is providing multiple sets of "preludes" for different usecases. The one for UI could have the UI enum variants directly available, while one for game entities could have a different set of potentially conflicting variants, with the ability to choose at the top of the bsn!
macro (or later, asset). Or alternatively, a short module name like ui
for all the ui short variants, so you can write variant: ui::Primary
. But i think both of these things should already work without any need to change the syntax.
use std::{any::TypeId, marker::PhantomData}; | ||
use variadics_please::all_tuples; | ||
|
||
pub trait Scene: Send + Sync + 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you're probably tired of me harping on this. However, as a person who writes a lot (and who reads books like George Lakoff's Metaphors We Live By for fun), I like to be precise about using language. Also, I adhere to Mark Twain's admonition, "Use the right word, not it's second cousin."
Nor do I think this is mere bikeshedding; I think picking the right words will help people learn the relationships between the parts more quickly.
What is a Scene?
Before BSN, the word "scene" is defined as "the thing that the GLTF asset loader loads": a hierarchy of entities and components that make up a scene graph. What BSN calls a "scene" is clearly not that - while it may be "scene-shaped", it's no more a scene than a collection of blueprints is a house. If you want redefine the word "scene" to be compatible with it's use in BSN, then the word "scene" no longer fits with what the GLTF loader puts out, unless you are willing to be egregiously imprecise.
More broadly, several terms in cart's BSN design use a kind of metonymy - a figure of speech in which a concept is referred to by the name of something associated with that thing or concept. I first ran into this on my first day of USAF basic training, in which the drill instructor pointed to me and barked, "You! Black T-Shirt!" and gave me an order. While I appreciate a good synecdoche as much as the next man (and I don't think the drill instructor would have appreciated if I tried to explain that giving an order to my t-shirt made no sense), I think it's an error to conflate the thing being created with the thing that creates it - to confuse the casting with the mold.
The problem of course is that we do this all the time in programming: we use the word Button
to mean both "button, the class" and "button, the instance". And most of the time it doesn't matter, people aren't confused. That is, until we start using words that are specifically about the process of fabrication and construction, like "builder" or "design".
I've looked at a bunch of different metaphors on this, from cooking ("recipe"), to electronics ("schematic"), to textiles ("pattern"). However, the words "scene" as used in 3D graphics most likely derives from theatrical and movie production, using a mix of tools from different crafts. Within that realm, Blueprint
is the best I can come up with. (Other contenders are SceneSpec
, SetDesign
, and so on).
BTW, I have no problem with the name ResolvedScene
, except that I would call it just Scene
. For example, I'd be happy with "a SceneSpec
produces a Scene
".
What is a Template?
I have some issues around the word "template" which mostly revolve around its mutability, however I think I can live with it. Part of the confusion is that it took me a long time to understand exactly when a template is mutated. From a metaphorical standpoint, templates are constants: that's the whole point of them, that they don't change during the build process.
Cart tried to explain to me a process whereby you would combine physical blueprints to create a template - combining a star with a circle to create a spiky circle - but strictly speaking, that's not a template, it's more of a mold or die (there isn't a word for this in all crafts because it's not that common). The closest general term I can think of is "prototype", but that's a word that has other connotations.
What is a TemplatePatch?
This is the one I find particularly confusing, because while it might be frequently used in conjunction with templates, it's actually an independent concept: you can implement the TemplatePatch
trait without ever making reference to a Template
. Moreover, the way I read the two words "template patch" is "a patch upon a template" or "a patched targeted at a template", but it's not actually patching a constant (which would make no sense) but rather patching an unfinished scene element. It is, in effect, a tool. Or an incremental processing step.
Like a real patch, it's generally shaped like the thing it's patching, but it need not be.
I don't have a great name for this, but the best I can come up with is SceneElementPatch
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solid points regarding Template and TemplatePatch. For scenes, that word is the industry standard vernacular and you probably don't want to move away from it too much. The language is imprecise, but you probably do want it to be a familiar term for folks coming from other engines. Personally, I don't think there's much confusion in knowing your scenes exist both in a written notation and in a runtime instantiation form. Your Button example speaks to this. There isn't anything wrong with introducing the term SceneSpec, but I think most developers using any engine intuitively understand that scenes are typically constructed from some or other written form when instantiating them and that the word "Scene" can be used to refer to both forms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As someone with a lot of experience in Godot and Unity, Scenes are a quite flexible term especially in Godot. So I think a lot of people coming into Bevy will be used to it being so flexible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would ask whether the word "scene" is used consistently in Godot and Unity, or whether the term is used to mean multiple contradictory things. (I'll admit that my experience with Godot and Unity are somewhat limited, most of my career in games involved technologies that existed before them - like RenderWare, DirectX, OpenGL and so on. I did use Unreal quite a bit, but that was back in 2001 when Unreal was very different.) Unity and Godot aren't the only things out there: GLTF has it's own definition of a "scene", as does three.js, Babylon.js and other not-insignificant frameworks. I would argue that the GLTF definition is likely more relevant for our users, since they will actually use GLTFs when working with Bevy.
In Bevy, we already have a "SceneReady" event. When that event is triggered, it means that something - a scene - is ready. That "thing that is ready" is not a BSN Scene
, so now we have an inconsistency. The two things aren't even made of the same stuff: a loaded GLTF scene is made out of entities and components, whereas if you inspect a BSN Scene
you won't find a single entity or component - just templates and patches.
Also, while I am sympathetic to the argument of industry jargon, also known as "but Mom all the other kids are doing it", I also feel that there's a lot of coders out there who have limited vocabularies and are not good at naming things or writing clearly. I think names have to be judged on their own merits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would argue that the GLTF definition is likely more relevant for our users, since they will actually use GLTFs when working with Bevy.
For the moment, yes. If or when Bevy supports multiple authoring formats then that assumption may not hold. I believe a PR that adds FBX support is floating around. Granted, I think it tries to parse the FBX data into a similar or the exact same structure that the GLTF loader does. But don't quote me on that. I didn't look into the PR too deeply.
I would ask whether the word "scene" is used consistently in Godot and Unity, or whether the term is used to mean multiple contradictory things.
It kind of depends on the direction that Bevy wants to take here. In Unity, FBX and GLTF files are just considered assets. Assets that may contain entire hierarchies of inner assets, but they're merely assets nonetheless. The engine let's you compose what it considers "scenes" out of multiple FBX or GLTF assets (and other types that I'm omitting, of course). As well as engine specific assets that don't come from external files at all. Most notably UI, as I'm sure you're well aware having done some fantastic work on it, if I may say so :)
Those scenes are serialized to written format via YAML in the case of Unity and TSCN in the case of Godot. I'm not sure for Godot, but for Unity, the Scene class for an instantiated scene contains very little data about the objects inside said scene. It's mainly metadata about the scene. So, yes. In that sense the term does have multiple contradictory meanings.
I think the biggest reason why an engine like Unity doesn't consider a GLTF file by itself as scene is exactly because you want a scene format that can represent engine-specific assets like the UI in addition to everything that the GLTF contains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another way of viewing it, that I think applies to a lot of people, is that a Scene is primarily an idea that can be represented in multiple forms.
Let's say a dev creates an "enemy scene" for one of the enemies in their game. That scene starts as an idea in their head that they translate into a scene file. Then they load that scene file in their game code into an entity hierarchy.
During this process they see the scene file and the entity hierarchy as different forms of the same thing so they use the word "scene" to refer to both.
So if a Scene is defined as just the entity hierarchy that might break people's mental model even if it's currently technically referring to two different things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nor do I think this is mere bikeshedding; I think picking the right words will help people learn the relationships between the parts more quickly.
Thank you for writing this out. I think its very important to be clear anout terminology, not only to avoid needing to change the name of sucha big feature after its already released. You've put into words what for me was a gut feeling i couldn't describe that well before.
Maybe because I haven't done much gamedev before Bevy, and grew up around photography a lot (multiple family members, including my dad), but to me Scene
doesn't fit the way its used here at all.
To me, a Scene is the (in photography often accidental) arrangement and look of multiple things in the world. The way a landscape looks right now, including very temporary things like lighting conditions, clouds, and animals. But importantly its unique and local - similar scenes may exist, but they are at a different place or time.
I agree fully that ResolvedScene
fits what I would think a Scene
is far more. I think the Scene trait only having patch
and dependency stuff as methods is a good indication: Why is that all a scene does? Without having read the explanation, i would expect Scene to be in some way relevant (primarily) after the entities have spawned. Maybe just for metadata, maybe because it contains all the entities (obviously not in a ECS architecture, but generally). For this reason i also like the idea of bsn
meaning bevy scene notation
far more: its a notation which creates scenes. its not a scene in itself.
I'll freely agree that Scene in non-technical language is very vague too, but looking at the rather long list of definitions in Wiktionary: https://en.m.wiktionary.org/wiki/scene a common factor is that a scene involves a specific event, time, or circumstances in some way. From how i'd interpret them, none of these definitions would lead one to think that a scene could just be a small change applied to something else. To me this is especially obvious with the heading The on() Observer / event handler Scene. It seems strange that something which can be described as "adds a event handler function to something" should be called Scene.
I'd prefer any of the suggested names over Scene: Recipe, Schematic, Pattern, Blueprint, SceneSpec, or even just Patch
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, scene comes from old Greek skēnē, where it means more or less "stage": The place in an amphitheater where they would hang up some painted cloth and have actors in front of.
In computer graphics terms both that original meaning as well as "stage" would be empty, abstract, world space, scene in the modern theatrical sense as populated, concrete, world space. The proposed meaning of Scene would rather be Scenography, which is literally the description of a scene and (in English) the practice of crafting scenes, which is more specific than "Pattern" or "Blueprint". "Scene" could then be justified, by abuse of semantics, by saying "Painters use paint, scenographers use scenes". Not hating it, not loving it, I'm a firm meh.
The theatrical scene, in Bevy types, would be "everything that has a GlobalTransform
or influences something with a GlobalTransform
", which is almost everything in the ECS. SceneSpec
would imply the presence of a Scene
type which Bevy calls World
, and WorldSpec
immediately sounds wrong because that'd be speccing the whole world, not individual bits and pieces. The world is made of entities, entities are made of components. A collection of components is a bundle, and a collection of entities is... a cluster?
I like it (and grudgingly have to credit LLMs for being good at being a thesaurus): While generic it's not hopelessly overused as recipe, blueprint, etc, and unlike scene it makes semantic sense, especially having a name for "collection of entities" when there's one for "collection of components" is nice and tidy. .bsn
for "Bevy scene notation" could become .bcd
for "Bevy cluster descriptor".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SceneSpec would imply the presence of a Scene type which Bevy calls World
No, how did you come to that conclusion? The World is everything. Inside it you can currently have any number of GLTF scenes. Which use the Component SceneRoot
.
This PR currently proposes a second Scene type: ResolvedScene
.
The proposal is a simple renaming, nothing as grande as redefining what World is:
Scene
-> SceneSpec
(or any of the other terms)
ResolvedScene
-> Scene
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
World
is what scene, in the theater use of the term, means: A stage, filled or empty. At least most of the stuff in World
will be that, there's also odds and ends like refresh rate settings but then occasionally audience seating gets used as theater props so I'm not going to sweat that point.
What's proposed, technically, is a systematic way to describe a bunch of data of (approximate) type Vec<(Entity, Vec<Component>)>
: That's in essence the same type that World
has, though any actual World
will be constructed from more than one of these things, that's where this very Scene
/World
semantic friction comes form: Because they are the same, and yet are not.
Oh, last thing, the Scene
vs. SceneList
distinction. Whelp
Too much talking with LLMs later: I suggest Composition
for Scene
, and either Compilation
or Anthology
for SceneList
. Or CompositionList
to only have one "fancy" term. The traits, that is, ResolvedScene
is a more or less behind-the-scenes plumbing type and would be ResolvedComposition
(or rather BakedComposition
if naming was solely up to me)
Works both from the technical and artistic level: A Composition
is a collection of entities and their components under a common idea / lead entity (one hierarchy root). Both Compilation
and Anthology
are suitable plurals for that, granted the latter is rather eclectic. But fitting.
Is patch composition going to be commutative? If it's not (if patch ordering matters) then it's going to be very easy to write very fickle code, that's why Nickel's merge is symmetric. Long story short it works by requiring one patch or the other to specify that it has priority, otherwise composing clashing values will error out. |
SceneExpression(TokenStream), | ||
InheritedScene(BsnInheritedScene), | ||
RelatedSceneList(BsnRelatedSceneList), | ||
ChildrenSceneList(BsnSceneList), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been unable to find what exactly causes this, but currently when trying to spawn an entity with an empty relationship target the relationship component isn't actually added as far as I can tell.
fn scene() -> impl Scene {
bsn! {
Name("Tester Entity")
// This entity won't have an inventory component
Inventory [
]
}
}
} | ||
} | ||
|
||
pub struct InheritScene<S: Scene>(pub S); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inheritance - a redundant concept?
I am finding it hard to see the usefulness of inheritance as a concept here. To me it just looks like syntax sugar for inlining a Scene
(patch) within a scene.
I just see patches as patches - layered on top of each other. And I feel like inheritance disorients me a bit on patches. "Inheriting a scene" == "Applying a patch"? If that's the case, I would prefer if we could just call it the latter.
Say we want to use the button
widget from feather. With inheritance syntax it would look like:
bsn! { :button(ButtonProps { ... }) }
Wouldn't this be equivalent to the following?
- Inline function syntax
bsn! { button(ButtonProps { ... }) }
- Expression syntax
bsn! { {button(ButtonProps { ... })} }
Maybe I am missing something, but what is the purpose of having a dedicated inheritance syntax - or even the concept itself? It seems to make things look more complex than they are 🤔 I find that confusing
"Patching in".bsn
(or other scene formats) assets could still use the :
-syntax if we feel that it has benefit. Though personally I would be fine with just writing out the templateScene
type name for those cases, e.g UsePatch("my_scene.bsn")
or Load("my_scene.bsn")
.
As a matter of convention, inheritance should be specified first. An impl Scene is resolved in order (including inheritance), so placing them at the top ensures that they behave like a "base class". Putting inheritance at the top is also common practice in language design, as it is high priority information. We should consider enforcing this explicitly (error out) or implicitly (always push inheritance to the top internally).
This convention seems a bit flawed to me. I could see myself wanting to "inherit" a base scene, then apply some defaults inline , and then apply another patch I don't control on top of that.
And if we were to enforce this, wouldn't one just be able to circumvent it by moving the "forbidden" lines to another scene and inherit that? 🤔
Bad by convention:
bsn! {
:base_scene
Node { width: Val::Px(50.0 }
:"other.bsn"
}
Good(?) by convention:
fn circumvention() -> impl Scene {
bsn! { Node { width: Val::Px(50.0 }}
}
bsn! {
:base_scene
:circumvention
:"other.bsn"
}
|
||
impl_downcast!(ErasedTemplate); | ||
|
||
impl<T: Template<Output: Bundle> + Send + Sync + 'static> ErasedTemplate for T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible that the Output: Bundle
bound could lead to unexpected behavior if a Template
is/outputs a Bundle
that contains more than one component? That template would have a different TypeId
so patching may not work as expected.
Maybe the bound should be Output: Component
instead? 🤔
Edit: Or maybe it is in ResolvedScene
(get_or_insert_template
/ push_template
) the bound should be tightened. ErasedTemplate
probably shouldn't care about how ResolvedScene
patching works.
Co-authored-by: Carter Anderson <[email protected]>
Just merged with main |
You added a new example but didn't add metadata for it. Please update the root Cargo.toml file. |
* Restore the panes in the feathers example * Migrate tool_button to use EntityCursor * Remove unused imports * Fix AlphaPattern with bsn! and simplify handle initialization
Sprite { size: b } | ||
Team::Green(10) | ||
{transform_1337()} | ||
Children [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to talk about the Marker [ ... ]
vs. Relation [...]
ambiguity.
The first solution that comes to mind is to add a delimiter character:
Relation: [
...
]
So grammatically, the sequence "identifier colon left-bracket" means that the list of items is an argument to the identifier, not a standalone item.
However, there's another idea that looks even more visually distinctive, which is to place the name of the relation inside the brackets:
[Observers: foo, bar]
[Effects: this, that]
[child1, child2]
In this variation, the sequence "left-bracket identifier colon" means a list of items that are related via the given relation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A related issue is whether relations should replace or append. I think most of the time append is the right choice:
[Observers: o1, o2]
[Observers: o3, o4]
...would produce an entity with 4 observers.
The reason I think append should be the default is because of the patching nature of templates: often times a scene will be a composition of multiple templates, and we want to encourage compositional thinking where possible.
In cases where we want to do a replacement, a method that triggers a specific bundle effect can be used:
[Observers: o1, o2]
Observers::clear()
[Observers: o3, o4]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed: #19715
This has caused quite a few headaches when children!
surprisingly yeets your preexisting hierarchy
Can bsn's relationship work with |
Just merged changes from cart#43. Thanks @tim-blackbird! |
) -> impl Bundle { | ||
( | ||
pub fn button(props: ButtonProps) -> impl Scene { | ||
bsn! { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few points discussing different options around how best to implement inheritance, including:
Explore struct-style inheritance (ex: :Button { label: "hello" })
Investigate supporting inherited scenes "intercepting" children from the inheritor. This may also tie into "inline scene inputs".
I'd like to suggest a different paradigm that I think would be ideal for this format: Merging, ala Nickel or Cue.
Both of these use the "merge operator" to combine two containers. Merging is commutative, which means the order doesn't matter, this enables understanding and debugging the end result much easier, and makes it possible to need workarounds for the directed nature of inheritance.
I will let these projects explain why their approach is a good idea:
Cue: relation to inheritance
Nickel: overriding
Both languages philosophy are applicable here, but possibly Nickels approach might match your current thoughts better.
There will be things to solve in this usecase though, as both Cue and Nickel assume a dictionary style layout. One path forward would be to merge based on the components an entity already has, this would likely use Name
a lot.
Here is a small example.
Given the following two bsn!
declarations:
let button = bsn! {
Button [
Text(text)
]
}
let button2 = bsn! {
:button
BackgroundColor(RED)
}
This works as you would currently expect. The difference would be if you then tried to merge the same component but with a different value:
let fail = bsn! {
:button
BackgroundColor(Green)
}
This system couldn't know which is preferable, as order cannot matter. A motivating case where this helps catch things is if you had two scenes containing a Name("player1")
entity that gets merged with conflicting items. Instead of being confused why the Outfit
isn't what you expect, you can see why this was chosen. If we use Nickels data model, we would then resolve this with merge priorities, or more likely, realise that our reusable scenes aren't defined well.
This goes the other way quite easily: a scene that is meant to be the prototype can have its own requirements that it can enforce, no intercepting of inheritors necessary.
Please read the docs I linked, they make the case so much better than I could, but I do think this style of composition has a lot of merit for something like bsn!
Welcome to the draft PR for BSN (pronounced "B-Scene", short for Bevy SceNe), my proposal for Bevy's next generation Scene / UI system. This an evolution of my first and second written proposals. Much has changed since then, but much is also the same. I'm excited to see what everyone thinks!
First some expectation setting: this is not yet a shippable product. It is not time to start porting your games to it. This is unlikely to land in the upcoming Bevy 0.17, but very likely to land in some form in Bevy 0.18. It is starting to become usable (for example I have ported the work-in-progress Bevy Feathers widgets to it), but there are still gaps left to fill and likely many rounds of feedback and tweaking as others form opinions on what works and what doesn't.
What should the "review approach" be?
This PR is not intended to be merged in its current form. Now is not the time to polish things like docs / tests / etc ... save those comments for the next phase. This is a "public experimentation phase" where we can collectively evaluate my proposed approach and flexibly pivot / iterate / tweak as necessary.
If functionality is missing, it is worth discussing whether or not to implement it at this stage, as the goal is to reach an agreed-upon MVP state as soon as possible. Likewise for bugs: if something critical is broken that should be called out and fixed.
Please use threaded discussions by leaving comments on locations in code (even if you aren't talking about that line of code). I'm going to aggressively clean up top level discussions to avoid burying things ... you have been warned.
If you would like to propose changes or add features yourself, feel free to create a PR to this branch in my repo and we can conduct a discussion and review there. This will be a long-living branch that I regularly sync with main.
When we're ready to start polishing and merging into main, I'll create new PRs (with smaller scopes) so we can iteratively review / polish / merge in smaller chunks.
Note that next week I'll be on vacation. Feel free to continue discussing here while I'm gone and I'll respond when I get back.
What is currently included?
From a high level, this draft PR includes Templates, core Scene traits and types, scene inheritance, and the
bsn!
macro. This is enough to define scenes in code, and also provides the framework for scene formats to "slot in". This also includes a port of the Bevy Feathers widget framework tobsn!
, so look there for "real world" usage examples.What is not currently included?
This does not include the
BSN
asset format / loader (ex:asset_server.load("level.bsn")
). I've implemented this in my previous experiments, but it hasn't yet been updated the latest approach. This will be done in a followup: not including it allows us to focus on the other bits first and perhaps get a usable subset of non-asset functionality merged first.This also does not include any form of "reactivity". The plan here is to allow for an experimentation phase that tries various approaches on top of this core framework. If the framework cannot accommodate a given approach in its current form (ex: coarse diffing), we can discuss adding or changing features to accommodate those experiments.
See the MVP Stretch Goals / Next Steps section for a more complete list.
Overview
This is a reasonably comprehensive conceptual overview / feature list.
Templates
Template
is a simple trait implemented for "template types", which when passed an entity/world context, can produce an output type such as aComponent
orBundle
:Template is the cornerstone of the new scene system. It allows us to define types (and hierarchies) that require no
World
context to define, but can use theWorld
to produce the final runtime state. Templates are notably:The poster-child for templates is the asset
Handle<T>
. We now have aHandleTemplate<T>
, which wraps anAssetPath
. This can be used to load the requested asset and produce a strongHandle
for it.Types that have a "canonical"
Template
can implement theGetTemplate
trait, allowing us to correlate to something'sTemplate
in the type system.This is where things start to get interesting.
GetTemplate
can be derived for types whose fields also implementGetTemplate
:Internally this produces the following:
Another common use case for templates is
Entity
. With templates we can resolve anEntityPath
to theEntity
it points to (this has been shimmed in, but I haven't yet built actual EntityPath resolution).Both
Template
andGetTemplate
are blanket-implemented for any type that implements both Clone and Default. This means that most types are automatically usable as templates. Neat!It is best to think of
GetTemplate
as an alternative toDefault
for types that require world/spawn context to instantiate. Note that because of the blanket impl, you cannot implementGetTemplate
,Default
, andClone
together on the same type, as it would result in two conflicting GetTemplate impls.Scenes
Templates on their own already check many of the boxes we need for a scene system, but they aren't enough on their own. We want to define scenes as patches of Templates. This allows scenes to inherit from / write on top of other scenes without overwriting fields set in the inherited scene. We want to be able to "resolve" scenes to a final group of templates.
This is where the
Scene
trait comes in:The
ResolvedScene
is a collection of "final"Template
instances which can be applied to an entity.Scene::patch
applies the current scene as a "patch" on top of the finalResolvedScene
. It stores a flat list of templates to be applied to the top-level entity and typed lists of related entities (ex: Children, Observers, etc), which each have their own ResolvedScene.Scene
patches are free to modify these lists, but in most cases they should probably just be pushing to the back of them.ResolvedScene
can handle both repeated and unique instances of a template of a given type, depending on the context.Scene::register_dependencies
allows the Scene to register whatever asset dependencies it needs to performScene::patch
. The scene system will ensureScene::patch
is not called until all of the dependencies have loaded.Scene
is always one top level / root entity. For "lists of scenes" (such as a list of related entities), we have theSceneList
trait, which can be used in any place where zero to many scenes are expected. These are separate traits for logical reasons: world.spawn() is a "single entity" action, scene inheritance only makes sense when both scenes are single roots, etc.SceneList
is implemented for tuples ofSceneList
, and theEntityScene<S: Scene>(S)
wrapper type. This wrapper type is necessary for the same reasons theSpawn<B: Bundle>(B)
wrapper is necessary when using thechildren![]
macro in Bundles: not using the wrapper would cause conflicting impls.Template Patches
The
TemplatePatch
type implementsScene
, and stores a function that mutates a template. Functionally, aTemplatePatch
scene will initialize aDefault
value of the patchedTemplate
if it does not already exist in theResolvedScene
, then apply the patch on top of the current Template in theResolvedScene
. Types that implementTemplate
can generate aTemplatePatch
like this:Likewise, types that implement
GetTemplate
can generate a patch for their template type like this:We can now start composing scenes by writing functions that return
impl Scene
!The
on()
Observer / event handler Sceneon
is a function that returns a scene that creates an Observer template:on
is built in such a way that when we add support for adding additional observer target entities at runtime, a single observer can be shared across all instances of the scene.on
does not "patch" existing templates of the same type, meaning multiple observers of the same event are possible.bsn!
Macrobsn!
is an optional ergonomic syntax for definingScene
expressions. It is built to be as Rust-ey as possible, while also eliminating unnecessary syntax and context. The goal is to make defining arbitrary scenes and UIs as easy, delightful, and legible as possible. It was built in such a way that Rust Analyzer autocomplete, go-to definition, and doc hover works as expected pretty much everywhere.It looks like this:
I'll do a brief overview of each implemented
bsn!
feature now.bsn!
: Patch SyntaxWhen you see a normal "type expression", that resolves to a
TemplatePatch
as defined above.This resolve to the following:
This means you only need to define the fields you actually want to set!
Notice the implicit
.into()
. Wherever possible,bsn!
provides implicitinto()
behavior, which allows developers to skip defining wrapper types, such as theHandleTemplate<Image>
expected in the example above.This also works for nested struct-style types:
Note that you can just define the type name if you don't care about setting specific field values:
To add multiple patches to the entity, just separate them with spaces or newlines:
Enum patching is also supported:
Notably, when you derive GetTemplate for an enum, you get default template values for every variant:
This means that unlike the
Default
trait, enums that deriveGetTemplate
are "fully patchable". If a patched variant matches the current template variant, it will just write fields on top. If it corresponds to a different variant, it initializes that variant with default values and applies the patch on top.For practical reasons, enums only use this "fully patchable" approach when in "top-level scene entry patch position". Nested enums (aka fields on patches) require specifying every value. This is because the majority of types in the Rust and Bevy ecosystem will not derive
GetTemplate
and therefore will break if we try to create default variants values for them. I think this is the right constraint solve in terms of default behaviors, but we can discuss how to support both nested scenarios effectively.Constructors also work (note that constructor args are not patched. you must specify every argument). A constructor patch will fully overwrite the current value of the Template.
You can also use type-associated constants, which will also overwrite the current value of the template:
bsn!
Template patch syntaxTypes that are expressed using the syntax we learned above are expected to implement
GetTemplate
. If you want to patch aTemplate
directly by type name (ex: your Template is not paired with a GetTemplate type), you can do so using@
syntax:bsn!
: Inline function syntaxYou can call functions that return
Scene
impls inline. Theon()
function that adds an Observer (described above) is a particularly common use casebsn!
: Relationship Syntaxbsn!
provides native support for spawning related entities, in the formatRelationshipTarget [ SCENE_0, ..., SCENE_X ]
:Note that related entity scenes are comma separated. Currently they can either be flat or use
()
to group them:bsn!
also supports[]
shorthand for children relationships:It is generally considered best practice to wrap related entities with more than one entry in
()
to improve legibility. One notable exception is when you have one Template patch and then children:Ultimately we should build auto-format tooling to enforce such conventions.
bsn!
: Expression Syntaxbsn!
supports expressions in a number of locations using{}
:Expressions in field position have implicit
into()
.Expressions are also supported in "scene entry" position, enabling nesting
bsn!
insidebsn!
:bsn!
: Inline variablesYou can specify variables inline:
This also works in "scene entry" position:
Inheritance
bsn!
uses:
to designate "inheritance".You can inherit from other functions that return a
Scene
:You can pass arguments to inherited functions:
You can inherit from scene assets:
Note that while there is currently no implemented
.bsn
asset format, you can still test this by registering in-memory assets. See thebsn.rs
example.Related entities can also inherit:
Multiple inheritance is also supported (and applied in the order it is defined):
Inheritance concatenates related entities:
As a matter of convention, inheritance should be specified first. An
impl Scene
is resolved in order (including inheritance), so placing them at the top ensures that they behave like a "base class". Putting inheritance at the top is also common practice in language design, as it is high priority information. We should consider enforcing this explicitly (error out) or implicitly (always push inheritance to the top internally).bsn_list!
/ SceneListRelationship expression syntax
{}
expects a SceneList. Many things, such asVec<S: Scene>
implementSceneList
allowing for some cool patterns:The
bsn_list!
macro allows defining a list of BSN entries (using the same syntax as relationships). This returns a type that implementsSceneList
, making it useable in relationship expressions!This, when combined with inheritance, means you can build abstractions like this:
bsn!
: Name shorthand syntaxYou can quickly define Name components using
#Name
shorthand. This might be extended to be usable in EntityTemplate position, allowing cheap shared entity references throughout the scene.Name Restructure
The core name component has also been restructured to play nicer with
bsn!
. The impl onmain
requiresName::new("MyName")
. By making the name string field public and internalizing the prehash logic on that field, and utilizing implicit.into()
, we can now define names like this:BSN Spawning
You can spawn scenes using
World::spawn_scene
andCommands::spawn_scene
:For scene assets, you can also add the
ScenePatchInstance(handle)
component, just like the old Bevy scene system.template
Scene functionIf you would like to define custom ad-hoc non-patched Template logic without defining a new type, you can use the
template()
function, which will return aScene
that registers your function as aTemplate
. This is especially useful for types that require context to initialize, but do not yet use GetTemplate / Template:template_value
Scene functionTo pass in a Template value directly, you can use
template_value
:There is a good chance this will get its own syntax, as it is used commonly (see the
bevy_feathers
widgets).VariantDefaults derive
GetTemplate
automatically generates default values for enum Template variants. But for types that don't useGetTemplate
, I've also implemented aVariantDefaults
derive that also generates these methods.Bevy Feathers Port
This was pretty straightforward. All widget functions now use
bsn!
and returnimpl Scene
instead of returningimpl Bundle
.Callback
now implementsGetTemplate<Template = CallbackTemplate>
. I've added acallback
function that returns a CallbackTemplate for the given system function. I believe this wrapper function is necessary due to how IntoSystem works.I highly recommend checking out the widget implementations in
bevy_feathers
and the widget usages in thefeathers.rs
example for "real world"bsn!
usage examples.MVP TODO
This is roughly in priority order:
MarkerComponent [CHILDREN]
vsObservers [OBSERVERS]
ambiguity:Button { label: "hello" }
)#Name
syntax to be usable in EntityTemplate position for cheap entity references throughout the scene. This will likely involve replacingTemplate::build(entity: EntityWorldMut)
with a wrapperTemplateContext
, so we can cache these entity references.bsn!
can accommodate them.bsn!
. This may tie in to reactivityT: GetTemplate<Template: Default + Template<Output = T>>
. This is similar to the Reflect problem, but uglier. The derive should be adjusted to remove this requirement.touch_type::<Nested>()
approach could be replaced withlet x: &mut Nested
for actual type safety..Default::default()
and the upcoming..
)Thing<X> {}
in addition toThing::<X> {}
. We can defeat the turbofish!codegen.rs
fileworld.spawn_template()
,entity.insert_template()
, etc)bsn.rs
example sometimes hangs (race condition!)MVP Stretch Goals / Next Steps
bsn!
hot patching via subsecondOn<SceneReady>
, which is fired when an entity's scene templates have been applied and all of its children have firedOn<SceneReady>
)<Transform as GetTemplate>::Template::from_transform()
Discussion
I've created a number of threads with discussion topics. Respond to those threads and/or create your own. Please do not have top-level unthreaded conversations or comments. Create threads by leaving a review comment somewhere in the code (even if you can't find a good line of code to leave your comment). Before creating a new thread for a topic, check to see if one already exists!