#Next Generation Scenes

1 messages · Page 7 of 1

rare whale
#

@split harness Something I am wrestling with is how to do the conditional spawning of the menu contents. There are a couple of different approaches:

  • Externalize the "open" flag, so that the menu template has an "if open" conditional. Inside that block is a menu popup container and the individual menu items. This means that the caller is responsible for ensuring that the menu has the proper wrapper entities.
  • Have the menu button widget take a callback / closure which generates the menu content, so that the "open" state is internalized. There are two variations:
    • The callback only generates the menu items (returns a SceneList), and the menu button is responsible for creating the popover frame. This requires the callback to return an impl SceneList, which is tricky / not dyn-safe.
    • The callback generates both the frame and the items within it. This is simpler, implementation-wise, but more work for the caller and less opinionated.
#

I'm also wracking my brain right now trying to deal with the loss-of-focus state machine (so that clicking the menu button properly toggles the menu) but that's not really BSN-related so I won't discuss it here.

rare whale
#

@split harness So, I thought perhaps I could punt on the issue of spawn_related_scenes not being synchronous, but turns out nope. I'm seeing flashing even with very simple scenes:

if_then_else(
    |cx: &Cx| *cx.resource::<State<GameState>>().get() == GameState::Play,
    || bsn_list![Text("Yes: Playing")],
    || bsn_list![Text("No: Not Playing")]
)

This creates a reaction which uses a ghost node to delete the branch and replace it (using spawn_related_scenes) each time the input condition changes. That is, it calls despawn_related and then immediately calls spawn_related_scenes. Even with this, I see a 1-frame flash: that is, one frame between the despawn of the old content and the spawn of the new content.

#

I'm almost templated to do something evil, which is to make a Template impl which nukes all of its sibling entities. This would ensure that the despawning happened in the correct time frame.

split harness
#

Thats a "bigger" thing though. Won't be done in a day

#

Also today I'm doing some entity lifecycle / allocation PR reviews I've been promising to do.

rare whale
# split harness "immediate bsn" (in combination with "fast path bsn") is next on my list

I feel like the "immediate bsn" solution contains the assumption that we're never going to want to use "non-immediate bsn" for anything except greenfield spawning. (I'm making up terminology as I go along here, because I'm not sure what to call it. "greenfield" would be a scenario where you're loading a scene for the first time, whereas "brownfield" would be any case where you are overwriting a scene that already exists.)

I think perhaps a more general solution would be something like a "pre-ready" event: an event which is dispatched just before the scene list is installed in its new home, that lets me sweep up the old mess and make room for it. I could listen for this event on the parent entity and despawn all the old children just in time.

rare whale
#

I've been working on a new trait abstraction, Lens<T>, which represents a callable object which produces an output value given a reactive context. This can be an ordinary closure or a signal object.

#

This means that both:

if_then_else(|cx: &Cx| cx.resource::<MyResource>().0, ..., ...)

and

if_then_else(my_boolean_signal, ..., ...)

are valid

split harness
split harness
# rare whale I feel like the "immediate bsn" solution contains the assumption that we're neve...

I'll give this some thought. Events do seem like a reasonable approach here. Although I'd also like to explore ways to do all of the prep work ahead of time so a scene can be completely "ready". We'll want to support both scenarios, as some situations will prefer lazy on demand loads (ex: very large / expensive / asset heavy scenes could be lazily loaded with a loading symbol, whereas small/cheap scenes could be preloaded and ready to go, or vice versa)

#

One of the motivating benefits of the "up front, scene as full descriptor of initial state" approach is that we can know what the scene needs (and prepare that content) before trying to spawn it

#

I think for bevy_reactor we'll want up-front variants of the control flow options that accept generatedbsn! directly and report the dependencies as if they were part of the main bsn! body

#

I'm just stoked that we have reactivity + bsn working in a way where reactivity can evolve in a 3rd party crate

rare whale
rare whale
#

I'm looking over your MVP list of priorities. None of them are blockers, but there are some things I would like to have:

  • Struct template syntax
  • Slot parameters (templates that take scenes as params and use them to fill in slots)
  • Improving the API for menu popups (issues around dyn-safety of functions that return bsn_lists, not so much a bsn feature as just design advice)
    Things I'm not much concerned about:
  • Syntax bikeshedding (you know I like to do the sugar last, although I realize this is a prerequisite for wider distribution)
  • Performance issues - I assume those will be gotten to eventually, and my uses are small scale
split harness
#

Slot parameters (templates that take scenes as params and use them to fill in slots)
Presumably that is different from the already-supported "scenes as inputs to scenes"?

fn foo(scene: impl Scene) -> impl Scene {
    bsn! {
        Node [
            scene
        ]
    }
}

fn root() -> impl Scene {
    let scene = bsn! { Node };
    bsn! {
        Node [
            :foo(scene)
        ] 
    }
}
#

Struct template syntax
Yeah I really want to reconcile this, ideally in concert with tying scenes to components (ex: define a Player component, associate a scene with it, and perhaps require Player to be spawned by that scene)

#

Open question though

copper dragon
split harness
#

Which I don't loooove doing for traits, as it makes it possible to keep adding levels of boxing if you aren't careful

#

But perhaps worth it here / not too risky in this context

#

In the interim you can just create your own wrapper

#

impl Scene for SceneWrapper where struct SceneWrapper(Box<dyn Scene>)

rare whale
copper dragon
copper dragon
split harness
rare whale
#

Other than that, I don't care.

#

Consider that there are some apps that have lots of menu buttons

copper dragon
rare whale
#

And we don't want to pay the costs for all those hidden menus.

rare whale
split harness
split harness
#

in that context I'd deem it acceptable

copper dragon
rare whale
#

There's also the issue of conditionally visible menu items, but I can do that with ghost nodes

rare whale
split harness
#

However its worth noting that currently commands.spawn(bsn! { :my_scene_containing_menu } ) will currently create a brand new one-off scene, which will be "re-baked" each time commands.spawn is called

rare whale
#

Like for example a font picker - a dropdown menu of all fonts - won't rely on the menu being a closure, but instead will have an embedded foreach node in the menu itself

split harness
#

Which is not the end goal here.

rare whale
#

This means that if a font gets added while the menu is open, we don't have to close and re-open to see it

split harness
#

We want to pre-bake as much as possible into a single shared Scene instance, which we then instantiate

rare whale
#

Anyway, I think you get where I am aiming at

split harness
rare whale
#

After all, menu is feathers, and opinionated, so it's allowed to dictate what the frame looks like

#

OTOH, there are counter-arguments

rare whale
#

:menu_popup is the best place to put alignment options

split harness
#

Yeah i can see both sides / don't have strong opinions

rare whale
#

This is actually going to get more pertinent when we do ScrollView

#

Because scrollview also has a double parent situation

#

But intuitively people think of it as a single entity with children

#

Same with modal dialog barriers and frames

#

In other words, there's extra hierarchical structure that is not part of the friendly interface

lime veldt
#

in contrast to the box that the user can manually construct

somber rivet
#

I remember there was some talk of releasing a variant of 0.17 with bsn! macro. Is that a thing? How do I access it?

thick slate
split harness
rare whale
#

Bikeshedding question, which is preferable:

  • if_then, if_then_else, for_each, for_each_cmp
  • If::then, If::then_else, For::each, For::each_cmp
#

These are all usable in place of a child entity

silver bramble
lethal juniper
#

:: is annoying to type, so I will always prefer to avoid it, where is possible.

rare whale
# silver bramble Is there a larger example to look at? or could you provide a larger example for ...
commands.spawn_scene(bsn!(
    Node {
        left: ui::Val::Px(0.),
        top: ui::Val::Px(0.),
        right: ui::Val::Px(0.),
        position_type: ui::PositionType::Absolute,
        display: ui::Display::Flex,
        flex_direction: ui::FlexDirection::Column,
        border: ui::UiRect::all(ui::Val::Px(3.)),
    }
    BorderColor::all(css::ALICE_BLUE)
    [
        Text("State: "),
        switch(|cx: &Cx| *cx.resource::<State<GameState>>().get(), |cases| {
            cases
                .case(GameState::Play, || bsn_list![Text("Playing")])
                .fallback(|| bsn_list![Text("Not Playing")]);
        }),
        Text(" - "),
        if_then_else(
            |cx: &Cx| *cx.resource::<State<GameState>>().get() == GameState::Play,
            || bsn_list![Text("Yes: Playing")],
            || bsn_list![Text("No: Not Playing")]
        )
    ]
));
silver bramble
#

I would be in favor of the if_then syntax, apart from being more pleasant to type, it also is a clearer distinction from other components, and fits better with the current bsn_list macros.

lethal juniper
#

also this communicates better the idea of BSN everything is functions and structs/enums. If::then would looks like there is a module named If with a function named then

rare whale
#

@split harness I'm trying to build a closure that returns a bsn_list, and I'm confused. This maybe isn't a bsn problem but a rust issue but I am still stumped. Here's what I have:

|label: &String, _index| {
    bsn_list![
        (
            Node {
                border: ui::UiRect::all(ui::Val::Px(3.)),
            }
            BorderColor::all(css::GREEN)
            [
                Text::new(label.to_owned()),
            ]
        )
    ]
}

This gives me the error "Borrowed data escapes outside of closure" - it's referring to label. However, I would have thought that calling .to_owned() would fix that. (I've also tried a bunch of other approaches, calling .clone(), making a temporary var, etc.)

#

Note that if I replace label with a constant string it stops complaining.

royal python
rare whale
royal python
rare whale
#

The input of the second closure is derived from the output of the first, and we can't assume that the list items are copyable or cloneable.

rare whale
#

@split harness There appears to be a parsing bug in the bsn macro, it doesn't accept a leading minus sign on numeric constants: Val::Px(-6.0). The error is expected Expr.

split harness
#

It might be that something else is "claiming" the peeked - before we get to the Lit branch

rare whale
#

The headless menu widget is considerably improved over the one in the bsn branch

#

For example, clicking on the menu button while the menu is open now properly closes it. It also has simplified menu event structure.

#

I'm trying to focus on examples that deliberately stress the design of bsn 🙂 Probably scrollview next.

rare whale
#

@split harness I am now at the point where I need name references. Specifically, Scrollbar requires a target entity id:

Scrollbar {
    orientation: ControlOrientation::Vertical,
    target: #listview,
    min_thumb_length: 8.0,
}

Unfortunately, this does not compile:

mismatched types
expected `Entity`, found `EntityReference<'_>`

Changing it to #listview.id() is not syntactically valid, nor is {#listview.id()}.

split harness
#

You are likely relying on Default + Clone for your Scrollbar type's template impl

rare whale
split harness
#

Actually it would just require resolving that parameter to the reference

rare whale
rare whale
#

@split harness The listview widget is an example where being able to hijack the children syntax would be helpful. Currently the way I have to do it is to pass in the children as a parameter:

:listview(
    bsn_list! [
        :listrow [(Text("First World") ThemedText)],
        :listrow [(Text("Second Nature") ThemedText)],
        :listrow [(Text("Third Degree") ThemedText)],
        :listrow [(Text("Fourth Wall") ThemedText)],
        :listrow [(Text("Fifth Column") ThemedText)],
        :listrow [(Text("Sixth Sense") ThemedText)],
        :listrow [(Text("Seventh Heaven") ThemedText)],
        :listrow [(Text("Eighth Wonder") ThemedText)],
        :listrow [(Text("Ninth Inning") ThemedText)],
        :listrow [(Text("Tenth Amendment") ThemedText)],
        :listrow [(Text("Eleventh Hour") ThemedText)],
        :listrow [(Text("Twelfth Night") ThemedText)],
    ]
)

The reason we can't use the normal children syntax here is because listview has two levels of parents - the actual rows are grandchildren. This is because we need a fixed location to anchor the scrollbar that is not a child of the scrolling entity. So the structure looks like this:

  • listview
    • scrollbar
    • listview_inner
      • row
      • row
      • row
rare whale
elfin surge
#

I'm not involved enough to comment on the rest but I love the --- separators

lime veldt
#

it certainly is an interesting idea I can get used to

dapper sky
#

It feels a bit verbose, there's not many three-character delimiters, but also it's perfectly workable and very clear as to how things are separated at a glance.

autumn bane
#

it's unusual i think, but i definitely like it more than <> delimiters (i know they're along slightly different axes, but i like the overall --- design space more)

#

it looks very nice visually, which is kind of a great quality for scene notation

dapper sky
rare whale
queen oak
#

I love it

dapper sky
#

two dashes would just confuse newcomers

magic belfry
#

--- is wayyy prettier and easier to read to me than the <> syntax at least

#

it's also more concise in many cases, as there is only one line between children instead of two (with <> you have the closing > and opening < on separate lines in multi-line scenarios)

dapper sky
#

very easy at a glance

magic belfry
#

but personally I don't care much about that, and find one-liners like that harder to read anyway (in general)

nocturne lotus
copper dragon
#

Might also make git compatibility with .bsn easier

rare whale
nocturne lotus
#

i think the corrected example is very readable with --- fwiw, despite taking up more space

lethal juniper
#

I like the idea of ---, it communicates better what it does. The only problem I have with the overall proposal is the ambiguity between bsn_list![ and Children [ when using ![,

While most of the times this should work fine, when it doesn't, it can be a pain to debug, since users needs context to know if it is a bsn_list! or Children.

river geode
#

It's worth noting that line separators between entities also makes the format more self synchronizing. Which makes it significantly easier to parse with multiple threads.

fallow cloak
#

I wonder if that same idea would work here actually. Basically, we could say any instance of [] defines a new 'scene scope', either for one entity or many, whether in template argument position or not

native mesa
cerulean mango
keen patio
# cerulean mango You are not alone 😄 once you go asset side you really don’t want to go back.

For me its the opposite 😆 the arrival of subsecond has completely shifted the way I envision optimal DX for authoring scenes, having access to full Rust is just so powerful for making utilities and keeping things colocated, and with subsecond I get that and hot reloads! The asset format would mostly be for placing things spatially, and tweaking certain things where a GUI is more productive, thus for editor only. Meaning the only motivation (for me) to keep BSN assets human-readable is VCS stuff.

white lichen
#

i think it would be possible to do GUI-tweaking of rust code a la inline_tweak! style macros

#

you'd have to mark the fields though

split harness
rare whale
#

I'll write it up in the discussion

dapper sky
rare whale
#

There are two different creative processes for building UIs, one where the developer functions as an artist, and the other where they function as an art director (and the computer is the artist):

  • In the first approach, you specify exactly where you want each element to be positioned. This is much easier if you have an editor.
  • In the second approach, you give the machine general directions and rules, and the algorithm decides where to place things. This approach benefits less from an editor, as it's often quicker to describe the rules textually.
silk lava
#

Not a fan of --- or ![], why 3 dashes? guaranteed to be asked, and also it creates lot of separation/space, too much.
I dont like double character tokens, especially when the single character variant is also valid syntax.

magic belfry
#

--- is used as a separator in both markdown and YAML/frontmatter, for example

slender lion
#

3 dashes was because its also used as a markdown separator (frontmatter + hrules), which tbh is pretty reasonable

magic belfry
#

a single - wouldn't really work imo, and -- would just look weird (and looks like the decrement operation in C/C++ and JS)

#

I would then ask "why not ---?"

white lichen
#

why not just put the , on its own line

magic belfry
#

lonely commas look very strange and ugly to me

bsn! {
    Node { width: px(100) }
    BackgroundColor(GREEN)
    [
        Node { width: px(10) }
        BackgroundColor(RED)
        ,
        :checkbox
        InteractionDisabled
        Children [
            Text("Hello there")
        ]
        ,
        Node { width: px(10) }
    ]
}
#

makes me wonder if the formatter broke or smth

white lichen
#

this is just a big comma thats given more space by convention

silk lava
#

This is just a bigger, noisier comma though

magic belfry
#

This would get lost to me, I wouldn't see it just scanning through code

white lichen
#

im a fan of this style

bsn! {
    Node { width: px(100) }
    BackgroundColor(GREEN)
    [   Node { width: px(10) }
        BackgroundColor(RED)
    ,   :checkbox
        InteractionDisabled
        Children [
            Text("Hello there")
        ]
    ,   Node { width: px(10) }
    ]
}
magic belfry
#

yeah I cannot read what's even happening there

small current
#

It's also nothing people would expect.

--- are a strong visual separator (like a wall) while commas connect the items before and after them.

They are used that way in plain text.

silk lava
#
|----------|   |-----------|
|#root Node|   |#child Node|
|----------|<->|-----------|<Owner>...

Gotta box each entity 🤡
Then draw arrows for relations ↕️

slender lion
#

hand me an autoformatter and I'll type pretty much anything tbh

white lichen
slender lion
#

😆

#

(but tbh, I'd deal with that if you handed me an autoformatter)

small current
#

In examples like these the dashes are good at breaking the vertical flow.

In my daily experience they are not easy to miss like a single character and don't blend into the text like a </tag>

bsn! {
    Node { width: px(100) }
    BackgroundColor(GREEN)
    [   Node { width: px(10) }
        BackgroundColor(RED)
        FontSize (8)
        Evil
        Transform(x,y,z)
        ---
        :checkbox
        FontSize(20)
        Background (YELLOW)
        InteractionDisabled
        Children [
            Text("Hello there")
        ]
        ---
    ,   Node { width: px(10) }
    ]
}
queen oak
#

honestly i think the dash is even better than what we had before

#

it's an incredibly beautiful solution

white lichen
ocean junco
#

Hey everyone, I've just been looking at the latest implementation of the new scene functionality and was wondering if anyone knows anything about the direction of loading bsn assets. I know it's something that's gonna be tackled later, but I'm kind of interested in how non statically defined scenes can be built using the new stuff or if it might be currently possible with what's available in 0.17

dark crater
# rare whale <@153249376947535872> & everyone else: I have posted an alternative syntax propo...

Jesus I love this syntax so much

However, my vision, for UI anyway (and possibly other things) is that we'll have a rich set of inheritable scenes and mixins, and that users will mostly be working at that level: that is, most of the syntax will be a composition of mixins and inherited scenes, with actual raw components and relations being the minority case. This means that I want to optimize BSN syntax for that mode of authoring.
can you elaborate?

#

I don't quite understand your meaning

thick slate
#

Since the reusable unit is bigger and more complex than that

dark crater
thick slate
#

The argument is to prioritize the UX for those use cases

#

Which I'm not sure I agree with, but I can at least paraphrase it!

rare whale
# dark crater Jesus I love this syntax so much > However, my vision, for UI anyway (and possi...

Here's a snippet from the BSN version of the feathers example:

:pane [
    :pane_header [
        :tool_button(ButtonProps {
            variant: ButtonVariant::Primary,
            ..default()
        }) [
            (Text("\u{0398}") ThemedText)
        ],
        :pane_header_divider,
        :tool_button(ButtonProps{
            variant: ButtonVariant::Plain,
            ..default()
        }) [
            (Text("\u{00BC}") ThemedText)
        ],
        :tool_button(ButtonProps{
            variant: ButtonVariant::Plain,
            ..default()
        }) [
            (Text("\u{00BD}") ThemedText)
        ],
        :tool_button(ButtonProps{
            variant: ButtonVariant::Plain,
            ..default()
        }) [
            (Text("\u{00BE}") ThemedText)
        ],
        :pane_header_divider,
        :tool_button(ButtonProps{
            variant: ButtonVariant::Plain,
            ..default()
        }) [
            :icon(icons::CHEVRON_DOWN)
        ],
        :flex_spacer,
        :tool_button(ButtonProps{
            variant: ButtonVariant::Plain,
            ..default()
        }) [
            :icon(icons::X)
        ],
    ],
#

The thing you will notice is that, except for text entities, it's entirely made up of inherited scene fragments, with nary a component to be found.

#

My prediction is that in a future world where we have a rich ecosystem of prefab scenes and complex widgets, much of the labor of assembling a BSN scene will be composing those prefabs into larger hierarchies.

rare whale
#

Most of the actual low-level component definitions will be inside reusable modular scene fragments - LEGO bricks - and a typical scene will be putting those bricks together.

#

Now, one might make the argument "well, that may be true for UI, but not for X"

#

But that really depends on the level of maturity and available abstractions for a particular domain

dark crater
dark crater
rare whale
#

It's just sugar

cursive parrot
#

Triple dash separators looks so appealing that they can be used in examples for marketing purposes.

viral roost
rare whale
#

Most tool buttons have icons, but I didn't want to add icon assets, so I stuck in a unicode symbol for example purposes.

viral roost
#

Ah sorry I meant what does that BSN expand to as a "regular spawn API"

rare whale
#

This means, for example, if I say in BSN:

Node {
    border: UiRect::all(px(4))
}

and then later in the same entity say:

Node {
    flex_grow: 1.0
}

Instead of the second Node replacing the first, the properties of the second will be merged together with the first.

#

This means that you can inherit from a scene function like tool_button and then tweak the layout with overrides - very useful!

keen patio
# rare whale <@153249376947535872> The listview widget is an example where being able to hija...

Another alternative could be to use relationships for adding slot content:

:listview()
Items[
    :listrow [(Text("First World") ThemedText)],
    :listrow [(Text("Second Nature") ThemedText)],
    :listrow [(Text("Third Degree") ThemedText)],
    :listrow [(Text("Fourth Wall") ThemedText)],
]

// or this even, if all items are expected to be `:listrow`s (listview would set this up internally somehow)

:listview()
Items[
    (Text("First World") ThemedText),
    (Text("Second Nature") ThemedText),
    (Text("Third Degree") ThemedText),
    (Text("Fourth Wall") ThemedText),
]

Though I'm not sure what the internal implementation of listview would look like to attach the Items as ChildOf the slot parent node, and (in the second example) define a "base template" for each item.

On the upside:

  • It could make inspection neat as you would have a "shortcut" to the contents by following the relationship instead of direct children.
  • Optimizations like "virtual scrolling" could use Items as the source, while only including visible items in the Children hierarchy, reducing layout/rendering overhead.

Although I guess both of these could also be done with the "slot contents as parameter" variant by using an internal relationship... 🤔

And not sure I like the separation when moving it out of the parameters, as it decreases discoverability/contract "tightness". And adding slot input (scope) would need extended syntax, as opposed to using a closure in the props.

rare whale
#

@split harness ImageNode needs a GetTemplate derive.

rare whale
#

@split harness I've been pondering what the APIs for dynamic scenes should look like. By "dynamic scenes" I mean things like if/switch/foreach and so on. Originally I started with closures that returned SceneList, and this was fine in most cases. However, for some of the more advanced scenarios, having a generic return type meant that the function wasn't dyn-compatible. So for these types, I decided to just pass in an EntityWorldMut into the closure and let it spawn the scene however it liked. However, this has some downsides:

  • It cuts against your desire to be able to predict what dependencies the scene has by inspecting the scene result.
    • However, note that in many of these advanced cases, you wouldn't be able to do that anyway, because in a dynamic scenario the scene result doesn't get used right away.
  • It opens the door to doing other things (like entt.world_mut()) which are temptingly useful.
rare whale
#

An example of an advanced scenario:

dyn_scene(move |cx: &Cx| {
    subject.get_reflect_tracked(
        cx,
        &ParsedPath::parse_static("").unwrap())
            .unwrap().reflect_kind().to_owned()
}, move |builder, kind| {
    match kind {
        ReflectKind::Struct => {
            builder.spawn_related_scenes::<Children>(
                struct_members(subject.clone()));
        },
        ReflectKind::TupleStruct => {
            builder.spawn_related_scenes::<Children>(
                tuple_struct_members(subject.clone()));
        },
        ReflectKind::Tuple => {
            builder.spawn_related_scenes::<Children>(
                tuple_members(subject.clone()));
        },
        // (more cases omitted)

dyn_scene is a generalized control-flow mechanism that is lower level than "if" or "switch": it computes a value, which is then memoized, and the memoized value is then used as a parameter to spawn a scene. Whenever the memoized value changes, the scene is despawned and a new scene is spawned in its place.

In this case, we are calling the reflection API to get the ReflectKind of the target, and then using that to decide what kind of editor UI to spawn.

rare whale
#

Now, in this particular case, there's no benefit to making this calculation reactive, since the reflection type of an object can never change at runtime. However, there's a different reason why I am using it here: because subject points to a resource or component, I need a world in order to be able to access it. I don't want to prop-drill world in all my template function signatures, so making a custom BSN Scene impl lets me hook in to the various hidden state available. In a sense, this is like the BSN template() function, execpt that it operates on whole entities rather than just components.

#

This makes me think that BSN needs something like template_scenes which operates similarly to template, but returns a SceneList.

split harness
#

ex: something like:

#[derive(GetTemplate)]
#[get_template(no_default)]
struct ImageNode { /* ... */ }

impl Default for ImageNodeTemplate {
    fn default() -> Self {
        ImageNode {
            color: Color::WHITE,
            texture_atlas: None,
            image: "uuid://d18ad97e-a322-4981-9505-44c59a4b5e46",
            flip_x: false,
            flip_y: false,
            rect: None,
            image_mode: NodeImageMode::Auto,
        }      
    }
}
#

Note that expressing a UUID in a handle template isn't (yet) supported (ex: uuid://XXXX)

#

Part of the plan is to remove default handles and make asset handles "always strong"

#

Meaning this pattern will no longer be supported:

pub const TRANSPARENT_IMAGE_HANDLE: Handle<Image> =
    uuid_handle!("d18ad97e-a322-4981-9505-44c59a4b5e46");
split harness
#

We can cross that bridge when we come to it

#

I don't want to prop-drill world in all my template function signatures, so making a custom BSN Scene impl lets me hook in to the various hidden state available. I
Good call. This would also defeat one of the primary purposes of having a Template::apply(world) -> Output approach: the ability to specify scenes without needing world access

split harness
rare whale
split harness
#

Why not reactive_scene

rare whale
#

BTW I also have insert_dyn which is "a component whose value is derived from a reactive formula" so that would need to be renamed as well

#
BorderColor::all(Color::BLACK)
BorderRadius::MAX
insert_dyn(|cx: &Cx| {
    let entity = cx.owner();
    match (
        entity.contains::<InteractionDisabled>(),
        entity.contains::<Pressed>(),
        entity.get::<Hovered>().map(|hovered| hovered.0).unwrap_or(false),
    ) {
        (true, _, _) => NORMAL_BUTTON,
        (false, true, _) => PRESSED_BUTTON,
        (false, false, true) => HOVERED_BUTTON,
        _ => NORMAL_BUTTON
    }
}, BackgroundColor)
#

I guess that would be reactive_component?

split harness
rare whale
split harness
rare whale
#

Another possibility is derived_component or insert_derived.

#

Or insert_computed

split harness
#

Or do you need the currently spawned entity?

#

Because if so, you're no longer operating at the scene level, which has no access to that

rare whale
#

Or something like it

split harness
#

Can you write a small code example to illustrate the use case?

rare whale
#

Sure, but first some explanation

split harness
#

With whatever idealized API you want

rare whale
#

Right now I have a bunch of reactive primitives: if/switch/dyn_scene and so on. All of them depend on ghost nodes to do the replacement. However, what if, instead of reacting to changes, I wanted to do conditional logic one time only - at spawn time - using ordinary if statements. For this case, I don't need ghost nodes since I'm not erasing the previous entities. Now, I could just embed a match statement or something in an expression, but what I lose is access to the world or entitymut.

#

This example is a bit ridiculous, but it shows the shape of what I am thinking:

bsn!(
    Node::default
    [
        {|context| {
            match context.world.resource::<R> {
                // produce different structures based on what's in R
            }
        }
    ]
)
#

One might ask, why do I need world?

#

In this case, I'm generating widgets that reflectively edit some data structure, which could be a resource or a component. Access to the target struct is encapsulated behind an Arc<Inspectable> which provides both reflection (PartialReflect) and change detection. However, widgets hold on to the Inspectable instance for as long as they are alive, but we can't have widgets holding on to World (or DeferredWorld). So Inspectable is not a reference to the target object, it's a recipe for how to access the target field given a world. (Internally it contains a ParsedPath and a resource or component id).

#

So when a widget needs to update its visuals when the target has changed, it uses its own inspectable instance, combined with some kind of short-lived world reference (usually either DeferredWorld or Cx) to get a fresh copy of the field value.

#

This is all predicated on the idea that we're not passing down some kind of signal-like thing down the hierarchy, but rather that each field editor widget is responsible for fetching its own state.

#

And of course because structs can contain struct containing structs, the hierarchy of field editors isn't just a flat list.

#

@split harness BTW, I really don't want to jog your elbow or create extra work, but one roadblock I ran into today was that I couldn't debug my code with bevy_inspector_egui because it's locked to bevy 0.17.2, and your branch is on 0.17.1. I tried messing around with cargo patch, but couldn't quite get it to work.

#

Maybe there's an earlier version that will work, will have to check

split harness
rare whale
split harness
#

I don't think we have this API yet, but it could do something like context.entity.insert_scene(bsn!{})

rare whale
split harness
#

I don't think a higher level template_scene API that looks and (kind of) behaves as if it is generating a piece of the scene using a world context would be doing developers any services, as it wouldn't really be doing that

#

But yeah I think insert_scene is a reasonable API to have

#

Although that would create a world where an entity would be the result of multiple scene projections on top of it

#

Which would not be meaningfully hot-reloadable

rare whale
#

Let me pop the conversation up a few stack levels

#

I'm currently working on two bsn-related projects:

  • In bevy itself, working on more complex feathers widgets like dropdown menus and list boxes. Potentially next I could do a tree view or modal dialog. These might be nice from an eye candy standpoint, but they wouldn't be pushing the envelope of bsn.
  • In a seperate bevy_reactor repo, I'm doing a bunch of bsn+reactivity experiments. The particular one I am working on is a reflection-based property inspector
#

Writing code that combines UI and reflection is a lot trickier, and involves a lot more complex logic

dark crater
keen patio
rare whale
#

@split harness OK so I was able to debug my problem. There seems to be an issue using ghost nodes in scenes:

bsn! {
    Node {
        display: Display::Flex,
        flex_direction: FlexDirection::Column,
        row_gap: px(2),
        min_height: px(10),
        min_width: px(10)
    }
    [
        GhostNode
        Children [
            Node {}
        ]
    ]
}

I get this warning:

warning[B0004]: Entity 58v0 with the GlobalTransform component has a parent (57v0) without GlobalTransform.

If I replace GhostNode with a regular Node the warning goes away.

Other than the warning message, everything appears to work as expected.

#

This feels like some kind of race condition, I don't get this warning when I used GhostNode normally.

#

A separate issue: I'm able to make a bsn_list with invalid syntax:

bsn_list![
    [
        Text({field.name.to_owned()}) ThemedText
    ],
    :slider(SliderProps {
        min: 0.,
        max: 100.,
        value: 0.,
    })

This creates a parentless text entity

silk lava
#

It should create an empty entity, with an Text child, no?

rare whale
#

Still seems kind of weird

silk lava
#

In the Dev-Docs, GhostNode requires Transform and not UITransform could that be it?

keen patio
#

However that would remove the useful but not so correct ability to use GhostNode in spatial hierarchies

rare whale
#

Maybe I just didn't notice the warning before

keen patio
#

Probably not, other than the fact that BSN/reactivity is the only known (to me at least) use case of the ghosts

rare whale
keen patio
rare whale
keen patio
#

Possibly, yes. At least with the current style of ghost node

crude pawn
#

did I break ghost nodes again with the UITransform changes 😓

crude pawn
#

I'd like us to be able to add non-Node entities to the UI node tree as well.

#

with cascading styles ideally I think you'd be able to add an intermediate entity with only a TextColor component and children to propagate the color downwards

#

The style components could require Node but then you couldn't use them with Text2d which might be awkward

rare whale
#

The problem is more complex than that. Given a sequence of child entities:
S0 S1 S2 d0 d1 d2 S3 S4 S5, where:

  • S0..S2 and S3..S5 are static entities
  • d0..d2 are entities created by a conditional block
    ...the goal is to replace the sequence d0..d2 with another sequence, one which might have a different length, or might be empty. You also can't assume that the offset in the child array is constant, as there might be other dynamic sections earlier or later. You also can't assume that the sequence d0..d2 is of constant length, as it might have nested dynamic sections.

Ghost nodes provide a really elegant solution to this problem, and have tons of uses. There are two other solutions worth considering:

  • Explicit BEGIN / END marker entities:
    S0 S1 S2 BEGIN d0 d1 d2 END S3 S4 S5 - this requires linearly searching the children for the markers.
  • Keeping a separate non-flat list of children and then transforming it into a flat list. The question is (a) where do you store it, and (b) how do you know when it needs to be re-flattened.
keen patio
crude pawn
#

oh right yes so an ad hoc display contents implementation wouldn't be sufficient

keen patio
#

For example, I would love to be able to organize my root entities into different groups, with the groups showing up as roots in an inspector, but the entities (children) of the group are treated as roots of their contextual hierarchies

crude pawn
#

I've always disliked ghostnodes just because I think if it's essential it shouldn't be behind a feature and work seamlessly with parent-child hierarchy navigation

#

it's very easy to forget about ghostnodes and break something naively

keen patio
#

Yep I agree, that's part of the reason I added UiChildren and UiRootNodes with the ghost nodes, but that doesn't prevent anyone from using regular parent-child

rare whale
crude pawn
#

I would definitely use them or some equivalent in any relatively complicated UI implementaton

keen patio
#

I expect a lot more people will want something like this as bsn! arrives and new patterns appear

crude pawn
#

it's just again because it's behind this feature hardly anyone is even aware of them

#

but they are super useful if you have to do any sort of remotely dynamic UI

split harness
#

We could choose to detect that case and make it an error, but I don't see it as inherently problematic / most people wouldn't think to reach for it

#

Just "a bit odd"

#

But it is a behavior that derives from the basic rules of the syntax, so it should be predictable

keen patio
#

@split harness I noticed that the entity scoping only pushes new scopes when using InheritedScene/InheritedSceneAsset, and not for scene expressions. Is that correct?

That would mean the comment I made on the PR regarding the redundancy of :other_scene and other_scene() is no longer completely true.

I am curious about your vision concerning these two, and what motivates keeping them as separate syntaxes

keen patio
#

I have merged the latest next-gen-scenes into the reconciliation PR
It was a bit tricky to handle the new entity references, in the end I split the reconciliation up into two stages:

  1. Walk the tree to reconcile the anchors and spawn/despawn entities, and populate ScopedEntities to ensure the references get resolved properly
  2. Walk the tree again to insert relationships and apply templates (components)
rare whale
#

@split harness I'm struggling with the #name identifiers, and particular, where I am allowed to reference them. There are two primary use cases, one which compiles and one which doesn't:

  • Inserting an entity id into a component prop
  • Capturing an entity id in a closure.
    An example of the latter is a tree view, which is trying to decide whether or not to display children based on whether the expansion toggle widget has Checked:
if_then(|cx: &Cx| cx.entity(#toggle).contains::<Checked>(), || bsn_list!(...etc...)

The compiler complains with expected Expr.

#

I've attempted a number of workarounds - such as putting the logic in its own scene function and passing the name as a parameter - but I haven't hit on any formula that is acceptable to the compiler.

keen patio
#

I don't think it's supported for closures (yet)

keen patio
keen patio
#

I have converted my little pet project to bsn!. I must say I am warming up to the current syntax.

Note that I am not using the recommended "wrap in ()" convention here, as I prefer to avoid the extra indentation. Instead I imagine with a custom syntax highlighter, we could make #Name have a very distinct color to make the separation more clear.

(screenshotting because VSCode does a better job with syntax highlighting)

#

Almost looks like markdown, the names being headings, and the entries the "paragraph"

queen oak
#

damn

#

waow

rare whale
keen patio
#

I'd be okay with getting rid of the comma and just saying "# starts a new entity, name is optional"

split harness
rare whale
# keen patio I have converted my little pet project to `bsn!`. I must say I am warming up to ...

My style usage looks a lot like yours:

:disclosure_toggle
#toggle
on(checkbox_self_update),

Text("")
ThemedText
effect::memo_effect(move |cx: &Cx| {
    let reflect = field_copy.reflect_tracked(cx).unwrap();
    reflect.reflect_short_type_path().to_owned()
}, |entity, path| {
    if let Some(mut text) = entity.get_mut::<Text>() {
        text.0.clone_from(path);
    }
}),

Text("[]")
ThemedText
effect::memo_effect(move |cx: &Cx| {
    let reflect = field_copy2.reflect_tracked(cx).unwrap();
    if let ReflectRef::List(value) = reflect.reflect_ref() {
        return value.len();
    }
    0
}, |entity, x| {
    if let Some(mut text) = entity.get_mut::<Text>() {
        text.0 = format!("[{x}]");
    }
}),

:flex_spacer,

:add_button()
on(move |_: On<Activate>, mut world: DeferredWorld| {
    ...etc.
split harness
#

(sorry for the delay, i've been sorting out some foundation stuff)

split harness
split harness
split harness
#

That would throw a massive wrench in the macro code though. You can do it, but it requires expensive / gross hacks

rare whale
split harness
#

(once I've sorted out the big outstanding functional issues)

rare whale
#

Although, I shouldn't rely toooo heavily on that code being "typical" - that is, while I think it's typical for the kind of work that it's doing, I can well imagine that BSN used in different domains might not stylistically match. One thing you'll notice about my code and villor's is that it's very heavy on the inherited scenes. Other uses might not be.

keen patio
#

Here's another sample, and I'm hoping it makes a case for not enforcing inherited scenes to come first:

pub fn player() -> impl Scene {
    bsn! {
        Player
        MainCameraTarget
        PlayerInput

        :humanoid(HumanoidProps {
            gltf_path: "models/player.glb",
            capsule_radius: PLAYER_RADIUS,
            model_scale: 0.4,
            ..default()
        })
    }
}
rare whale
split harness
rare whale
#

I generally like to put the primary defining components first

keen patio
rare whale
#

Hypothetical example:

Image {
    handle: "..."
}
:alt_text("Protagonist")
#

Where alt_text inserts an appropriate AccessibilityNode component.

keen patio
#
Node
:space_children(px(5)
:shadow_sm
#

"mixins"

split harness
#

Yeah reasonable arguments

rare whale
# split harness Yeah reasonable arguments

Honestly, these are just logical extrapolations of the principles you already laid out in the original bsn post. Specifically, I recall a discussion about composble styles like CSS, and the answer at the time was that the BSN patch mechanism would be sufficient.

#

So we're just taking you at your word 🙂

split harness
#

Multiple inheritance is kind of a pain in the ass from a "make this efficient" perspective. If you have Node :some_expensive_scene :some_other_expensive_scene you can't just use the final ResolvedScene for each of those and layer Node on top in a copy-on-write fashion. Instead in that instance, you need to recompute the whole scene

split harness
keen patio
keen patio
keen patio
keen patio
#

@split harness A couple minors, and a larger one:

  • Enums are not followed by .into() in value positions (e.g. order: CameraOrder::World, where CameraOrder implements Into<isize>).
  • Support template constructors with method chaining like Transform::from_xyz(...).looking_at(...) would be nice. Using template_value as a workaround, but not very pretty.
  • The way the closures work (for example PatchGetTemplate::patch and template) makes it a bit inconvenient to capture variables defined outside the invocation site. Not sure how to solve this nicely. In bevy_proto_bsn I made scene construction consume self so I could use FnOnce for the closures, but that is not really applicable here...
    // error: lifetime may not live long enough, returning this value requires that `'1` must outlive `'static`
    bsn! {
        Mesh3d({meshes.add(Cuboid::new(1.0, 1.0, 1.0))})
    }
    // error: cannot move out of value, a captured variable in an `Fn` closure
    let mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0));
    bsn! {
        Mesh3d(mesh)
    }
    // This works, though it's verbose, and for types that are more expensive to clone it might be inefficient.
    bsn! {
        Mesh3d({mesh.clone()})
    }
    
cobalt stone
#

I have been using egui in a very large project at work, and I'm going to have a lot of opnions once BSN is ready to test lol

#

(egui has not been fun in regards to layout and event handling)

keen patio
rare whale
slender lion
#

at least there's work upstream to make captures like that more ergonomic 😅

silk lava
#

||Unless they go with the var.use syntax, which seems equally as horrid 😅||

pale edge
#

I want to understand how does the new ui system reactivity work compared to react /dioxus.
And is there conditional rendering ?
And the equivalent of Array.map(()->Component)

rare whale
pale edge
#

(the end goal or the current state)

split harness
# keen patio <@153249376947535872> A couple minors, and a larger one: - Enums are not followe...

Biggest issue I've ran into is problems with hooks/required when inserting the components one-by-one. So I might tackle proper dynamic bundle insertion soon

Yeah I'd like to sort this out on the bsn side as well. Planning on making resolved scenes behave like dynamic bundles.

Enums are not followed by .into() in value positions

I vaguely remember this being problematic in some cases. If its not, I'm definitely down.

Support template constructors with method chaining like Transform::from_xyz(...).looking_at(...) would be nice. Using template_value as a workaround, but not very pretty.

Yeah this should be possible with some additional smarts

The way the closures work (for example PatchGetTemplate::patch and template) makes it a bit inconvenient to capture variables defined outside the invocation site.

To a degree this is a Rust problem, but there might be a way to improve some cases.

split harness
# split harness > Biggest issue I've ran into is problems with hooks/required when inserting the...

Also why background color is not part of Node ?
Deciding the lines between components is an art and not a science. In general a component is a "unit of functionality". My personal rule of thumb is that fields / properties should be added to existing components by default, unless you have a good reason not to. Fewer components means a smaller, more understandable API surface and better autocomplete / discoverability of fields. Usually we break components out when the functionality is necessary in multiple contexts, when we need more granular change detection, or when we want to optimize queries by breaking out data that isn't needed by hot paths.

The BackgroundColor case is kind of interesting: it provides functionality that is only used by Node, it is a "required component" of Node (so all Node entities have a BackgroundColor), and the Node entity isn't so hot that we need to break out that data. By the rules I mentioned there is a strong case for merging it into Node. I believe the split is largely a historical choice. I think there is a strong case to merge it into Node.

#

Oops replied to the wrong message. @pale edge ^

#

I've actually made the case to merge BackgroundColor into Node in the past

keen patio
rare whale
# split harness > Also why background color is not part of Node ? Deciding the lines between com...

As you know, I've been lobbying for the opposite: breaking up Node into smaller pieces (although, not too small). Historically, Node has been defined as "everything needed by Taffy, and nothing else". But there's a cost: it means that you end up paying memory for every optional layout feature (which, in the case of grid, is particularly noxious - grids are rarely used, yet every UI node has to have these fields, some of which are Vecs.) Unfortunately, we've never been able to achieve consensus around this.

#

Seriously, 40 named members for a struct that every UI entity has is excessive.

keen patio
keen patio
#

Imagine placing items on a shelf in 3D space, but we can use flexbox to arrange them in two dimensions 😀

rare whale
# keen patio I’m leaning towards the opposite too. But in a different way I guess. I’d like t...

I like the fact that background color is a separate component, because often I have other ways to draw nodes:

  • Some nodes are purely for layout and have no rendering
  • Some nodes are drawn using images instead of solid colors
  • Some nodes are drawn using custom materials
    The ability to "plug in" different rendering strategies seems like the poster-child use case for ECS; making background an Option<Color> in a larger struct seems counter to the Component philosophy.
glossy tide
#

+1 on keeping a separation of concerns between layout and paint. My understanding is that Bevy does change detection on a per-entity basis. And it is important that paint-only properties can be updated without triggering layout (so they can be cheaply animated)

#

I suspect going all the way to having everything be individual components would make lookup too slow (but someone should measure)

#

Regarding memory usage, there are some easy non-structural optimisations like replacing Vec with thin-vec or similar.

rare whale
#

Consider also that Text requires Node, which means that every Text fragment has 4 Vecs (grid_template_rows, grid_template_columns, grid_auto_rows, grid_auto_columns), none of which can be used for a text node. This is crazy!

glossy tide
#

For layout styles there is also the possibility of running Taffy directly over the Bevy ECS rather than duplicating all the styles into TaffyTree (we'd also need to measure perf, and deal with computed styles (voewport units. etc) somehow)

glossy tide
#

Required components might actually make splitting up the layout styles quite a bit nicer. Because you could have the split components all require the "CoreLayoutStyles" component

keen patio
#

It would be interesting to know how many layout nodes a fairly complex UI would have in total. To know how much of a real difference these kind of optimizations would make in practice

copper dragon
split harness
#

However I think it really comes down to the specific proposal

#

Enums are very good at handling the "can only be one type of thing" case. Naively, breaking components out for each layout type makes specifying two conflicting layouts expressable

split harness
#

If you can come up with a case that breaks I'm all ears, put I'm pretty sure it is done correctly

#

Filing issues on my fork is probably the best way to keep things organized + tracked

split harness
keen patio
split harness
pseudo fern
# split harness I've actually made the case to merge BackgroundColor into Node in the past

I have some thoughts on this ive been collecting since i started looking jnto bevy. Its GREAT you can do Node {arbitrary properties} and not have to already know how to separate them into five components in bevy, which is laborious in rust. But theres very little separation between declaration, defaulting, value resolution (which happens at different times for different values) and concrete values (the px displayed on screen) right now

#

There is a lot of low hanging fruit for ergonomics and some not so low hanging fruit in bevy ui, below worrying about reactivity/scenes/widgets

#

Which im hoping to work more on

#

And also discoverability which is not so great either 😅

split harness
#

Which is I think the strongest argument for consolidating components as much as possible

pseudo fern
#

That’s a nice aspect to only having one large component with a ..default at the end, but then you have to actually parse it out and make it useful, which is very spaghetti-ish right now

copper dragon
pseudo fern
#

Separation declaration into a phase of creating the actual machinery components might help with this

#

By that i mean you could write Node { grid-columns: x, border: y} or Node {} GridLayout {columns: x}

split harness
keen patio
# split harness AH. Yeah that might actually be a problem

I was fighting this whole “identifying a specific entity in a specific invocation” a bit while experimenting with macro hot reloads in proto_bsn.

I ended up using a hash of file!, line! and col! as an invocation id:

quote! {
            #bevy_proto_bsn::EntityPatch {
                inherit: (#(#inherits,)*),
                patch: #patch,
                children: (#(#children,)*),
                key: #key,
                hot_id: #bevy_proto_bsn::hot_macro::EntityPatchId::new(
                    file!(),
                    line!(),
                    column!(),
                    #_my_entity_index
                ),
                hot_patch: None,
            }
        }

(Sorry about formatting, on phone)

rare whale
#

This seems like a digression, I'd rather get back to discussing bsn.

split harness
#

In the context of BSN, im kind of biased toward Button being a component, which has a scene associated with it

#

Essentially adding the concept of a "required scene" to the component

rare whale
split harness
#

(when compared to random UUIDs)

keen patio
#

Not sure if it helps with scoping though

split harness
#

Yeah it would need to replace scoping I think. And it would force references to be "scoped" to a specific bsn! invocation

#

But at that point, it might be better to just make embedded bsn! rev the scope

#

("better" being "cheaper")

keen patio
#

There was something about those macros pointing to the outermost invocation too I think 🤔 can’t remember exactly

keen patio
queen oak
#

and the macro repeats the bsn

#

you'll have repeats

thick slate
#

Sharing a background color across your nodes based on a theme is a very reasonable thing to do for example

#

But doing that for "layout strategy" is much less so

#

I also think that this is likely to do bad things for performance on the rendering side

pale edge
lime veldt
#

Are there so many nodes that you would win performance when splitting it up? I think this discussion should weight arguments differently depending on that

rough cave
#

I can't remember the name of the trait in BSN (Template?), but either through implementations of that trait, or through BSN "mixins", can we reduce the importance of components' fields for ergonomics/discovery?

My reasoning is that mixins/Template acts as an abstraction for authoring ergonomics, which allows for short-hand (presumably with auto-complete) for common use-cases, whilst still allowing the runtime representation to be made of 0..* components for flexibility?

#

Effectively my point is, if a core argument for "don't break up a component" is "so we can find the fields in our IDEs", if you have a top-level HandyButtonTemplate or :handy_button mixin (or whatever), the IDE can provide auto-complete for that as a convenient abstraction (yay!), but that template/mixin would expand out to the N components/entities needed to represent it.

(Which I believe moves us into a similar world as Unity DOTS' Authoring Components)

pseudo fern
#

I would argue the status quo is that there is a second "hidden component zoo" to learn, its just not formalized

#

If theres a solution for templates like that, its kind of ideal

split harness
#

Although I do suspect we'll want to break out things like flex and grid layout from Node, in the interest of supporting arbitrary layouts

split harness
split harness
split harness
rare whale
split harness
#

(right now)

rare whale
#

I am much more interested in: how's it going? 🙂

split harness
# rare whale I am much more interested in: how's it going? 🙂

This week sadly hasn't been particularly productive for me. I've been largely caught up with foundation issues, and I took a sick day. I spent some time thinking about making spawn speeds reasonable, as the current "rebake the whole scene if you tweak anything / use spawn_scene(bsn!{})" approach isn't going to be viable

#

Nothing definitive yet. I have a conception of how we could fast path single-inheritance, but multi-inheritance is a bit more complicated

rare whale
queen oak
thick slate
pseudo fern
#

I do see a logic where "everything related to painting a box inside of its parent" is in Node and everything else is out. this would be keeping dimensions, positioning, borders-margin-padding, etc, removing layout, merging in bg color and bordercolor, but strangely keeping outline out.

#

sorry to keep poking at this side-questy thread 😅

copper dragon
crude pawn
#

I think it might make more sense to combine BackgroundColor + Gradient + ImageNode into one component than add a background color field to node. We could add all of those to node too, but then where does it stop.

pseudo fern
rare whale
#

This lets me draw a pattern with alpha on top of the solid color

crude pawn
#

Well text is different though it's not just a decoration, the space it takes up in the layout is determined by the text's arrangement etc

crude pawn
pseudo fern
pale edge
#

This will make it easier to find whzt uou want.
But styling split in differznt components will just make it so you get less intelisence and more doxs to read instead of just reading a glovzl struct stylesheet

copper dragon
magic belfry
#

there are many cases where you want both a background color (or gradient) and an image, ex: a png with transparency

#

or a partially transparent gradient over a solid color

#

css supports these

#

it even supports multiple layered images on a single element's background :P

crude pawn
#

yeah really common, you can do it using multiple overlayed UI nodes, but that's a lot more clumsy and awkward

keen patio
#

and sometimes you also want different blend modes for the layers and such

rare whale
#

We really ought to take this discussion to #ui-dev as it has very little to do with BSN (also, it should be in a thread, which we can't do in a WG topic).

somber rivet
#

I think a lot of the discussion about bsn has justifiably centered around UI, but it's a general scene format so I think some discussion on other usecases could be useful. That said, while I have followed this thread, a lot of the goings-on in here go a bit over my head.. so instead of making suggestions I just want to talk about my usecase and how I see bsn as being useful outside of a UI context.

bevy_gearbox is my statechart/machine library and uses an entity hierarchy to define a state machine. State machines are very data driven and composable through that hierarchy- at least in theory. Without bsn actually composing state machines from smaller parts requires a lot of custom glue. As an example, a character state machine can have 3 subcharts:

  1. Loading - for player characters
  2. Control -maps intentions to movement
  3. AI - if the character is controlled by anything but a player

Attached is an example of a player character state machine and an AI characters state machine. You'll notice that the "loading" subchart is parallel to the life region while the "ai" subchart is parallel to the "control" subchart. So there is composition at different levels in the hierarchy, not just adding children to the parent.

Like I said, this requires a lot of custom glue. I think it's also fairly easy to imagine how regions become scenes, with the overall statechart being a composition of those subchart scenes. In theory entirely different AI "brains" could be swapped out by changing a single line of text.

When I think of bsn I am most excited about using it as a dsl for general purpose hierarchy composition, which I am most excited for. bsn's other features, like reactivity... tbh I'm not even sure how it could be productively applied to statecharts. Swapping out the AI brain at runtime based on some game state?

rare whale
#

@split harness I'm finding that there's a number of places where I have to do double-cloning in order to satisfy the compiler when making scenes:

pub fn label(text: impl Into<String>) -> impl Scene {
    let text = text.into(); // Clone once
    bsn! {
        Text({text.clone()}) // Clone again
        ThemedText
    }
}
copper dragon
rare whale
#

@split harness I recently ran into a source of frustration with the #name feature: because the names are strictly local to the bsn block, there's no way to export the names outside of the scene function.

In react, the ref= param can be used to capture a reference to a DOM node. An advanced usage of this allows ref parameters to be passed to component functions: that is, a component that has a complex internal structure can export child element references to the caller. This is an important feature for building wrapper components: sometimes we need to establish a direct connection between elements outside the wrapper and elements inside; it's not always practical for the wrapper to always be a perfect proxy/firewall for what's inside it, especially if what we are wrapping is a collection of entities with their own distinct identities.

rare whale
#

In the specific case I'm thinking of, it's not easy to say "hey, :scene_template_01, I need access to the GlobalTransform of your attachment hardpoints."

dapper sky
#

not to be caught up in "where bsn" but do we have a "list of things that need to be determined/decided before BSN is stabilised" somewhere?

rare whale
#

@split harness Observers make it easy to run into the limits on the number of bsn components:

Node {
    position_type: PositionType::Absolute,
    width: px(12),
    height: px(12),
    border: UiRect::all(px(3)), // Invisible border increases picking area
    left: px(-14),
}
template_value(Terminal::Input)
BorderRadius::MAX
BackgroundColor(color)
on(on_terminal_drag_start)
on(on_terminal_drag)
on(on_terminal_drag_end)
on(on_terminal_drag_cancel)
on(on_terminal_drag_enter)
on(on_terminal_drag_leave)
on(on_terminal_drop)
split harness
split harness
#

We could try to make clone() implicit, but I suspect that would introduce its own challenges and weirdness

#

Open to suggestions. Not currently my highest priority. FYI I'm going to protect my attention pretty aggressively this week in the interest of making progress on the core scene performance / lifecycle stuff

rare whale
#

This is going to be important in feathers, because we're going to have various kinds of static text labels

#

And as you know I want to define bsn functions that are mapped to semantic concepts, not just an assemblage of arbitrary styles

split harness
rare whale
#

So things like :label("etc"), :alert("etc"), :field_title("text") and so on.

split harness
#

Because a Text(String) API is inherently going to require the string clone for each spawned entity

#

The clone() in your example above is just making that explicit

#

Eh actually nevermind. Slightly different context. That clone() is ensuring the patch is repeatable (ex: in the context of inheritance)

rare whale
copper dragon
native mesa
split harness
split harness
# rare whale That might be good enough

This is the minimum amount required 🙂

fn test(val: impl Into<String> + Send + Sync + Clone + 'static) -> impl Scene {
    bsn! {
        Text({val.clone().into()})
    }
}
#

No &str only &'static str

#

Actually you can drop the into()

rare whale
split harness
rare whale
split harness
#

Into<Cow<'static, str>> would avoid the need for Send + Sync + Clone

#

However Text doesn't work with Cow

rare whale
split harness
#

/ that would require converting the Cow back into a String

#

/ to_owned

rare whale
#

I don't think we need to over-optimize Text, it's simple and it works. I just wanted to avoid the poltergeist allocation (anti-pattern where you allocate an object which immediately disappears)

split harness
split harness
#

Although for "one-off" bsn! instances that you don't reuse, that is redundant

#

And will be freed after the one use

split harness
#

Theoretically we could have a once_bsn!, with a different set of traits that consume the patch

#

I don't LOVE that

split harness
split harness
#

I'm currently working on making commands.spawn_scene(bsn!{}) performance viable, and one important consideration when it comes to "mixins vs inheritance" is performance. With inheritance (always resolved first), we can pre-compute and cache a ResolvedScene for a given list of inherited scenes. With "mixins", any interleaving of template patches and inherited scenes would mean we can't directly rely upon a cached result.

// This can cache the result of `:red :blue` and just do a
// copy-on-write of BackgroundColor(BLUE) in a layer on top
commands.spawn_scene(bsn! {
    :red
    :green
    BackgroundColor(BLUE)
})

// This cannot be cached and is subtly very expensive
commands.spawn_scene(bsn! {
    :red
    BackgroundColor(BLUE)
    :green
})


fn red() -> impl Scene {
    bsn! {
        BackgroundColor(RED)
        /* imagine there is more "expensive" work here */
    }
}

fn green() -> impl Scene {
    bsn! {
        BackgroundColor(GREEN)
        /* imagine there is more "expensive" work here */
    }
}
split harness
#

Hmmm maybe that should be the distinction between :scene and scene. :scene is always evaluated first / cached "inheritance" and scene is a "mixin" and not cached

#

Because theres no real harm or major cost to doing mixins of small things

#

That also potentially solves another issue at hand: function inputs are also not really "cacheable" in the general case

#

Ex: consider some expensive_scene(name: String) -> impl Scene. If we're spawning that scene 1000 times each with a different name, we shouldn't be computing and caching 1000 versions of the scene

#

I believe @karmic rock solves this problem in Flecs via the template vs prefab distinction. A prefab is "cached", not parameterized, and inheritable, whereas a template is evaluated for each spawned instance

#

@karmic rock :

  1. Is that an accurate assessment?
  2. Are there interactions between the two? I'm reasonably certain a template can instantiate prefab. I could also see how a prefab could instantiate a specific instance of a template. Can a prefab inherit from a template? Can a template inherit from a prefab?
rare whale
split harness
split harness
#

That would have implications for the planned :Button { prop: Foo } support though, as that essentially has the same cachability problems as :button(prop)

#

I suppose we could also just say that "scenes with inputs are never cached"

rare whale
#

I've been trying to come up with a consistent rule for what order I write the components in a bsn block. Obviously children are last. Observers typically second to last. For the rest, I try to sort based on putting the components that define the essential character of the entity earlier. However, it's not clear what's "essential" - if I have an entity which is a slider widget, is the most essential component Node (which functions kind of like the "base class") or is it Slider which is just a marker but clearly defines its character more narrowly.

#

This ordering isn't entirely orthogonal to the cacheable / non-cacheable axis, but it's not entirely correlated either.

karmic rock
# split harness I believe <@260273550726922240> solves this problem in Flecs via the `template` ...
  1. Yeah more or less:
  • A prefab is an entity with a Prefab tag that causes it to be ignored by queries. When a prefab is instantiated, all of its components are copied over to (or inherited by) the prefab instance. Prefabs cannot be parameterized. They're instantiated by adding an (IsA, prefab) relationship.
  • A template is a set of inputs that gets converted to a single component, and logic that instantiates the template which gets executed when that component is assigned (basically a hook).
  1. Prefabs can inherit from each other, templates can't (yet). You cannot cross-inherit templates and prefabs.
split harness
#

In addition to the terminology differences

#

The benefit on our side (given what I listed is largely downsides in the comparison) is that the system is cohesive / cross compatible

#

I think template vs prefab is easier to discuss in some cases though when compared to "inheritance" vs "mixin" and "cached" vs "uncached"

#

But theres the benefit of "everything is a scene"

karmic rock
#

Yeah, in my case it boils down to prefabs usually representing a specific asset (like a texture, material, mesh), and a template instantiating those assets into a (partial) scene. Like:

prefab Horse {
  Mesh: {"horse.obj"}
  Texture: {"horse.png"}
}

template Herd {
  prop count: 3

  for i in 0..count {
    _ : Horse {
      Position: {i * 10, 0}
    }
  }
}

Herd my_heard(count: 10)
split harness
karmic rock
#

(damn I should've gone with a bird + bevy for my example)

split harness
#

(and will likely continue to blur over time)

#

@rare whale if we go down the :cached vs uncached(prop) route, we'll need to sort out our plans for the :Button { prop: Foo } scenario. Following the rules that have already been established, Button { prop: Foo } doesn't work, as that is ambiguous with a normal template patch

#

Silently making :Button { prop: Foo } uncached doesn't feel ideaaaal from a predictability / learnability standpoint

rare whale
split harness
#

Although "parameterized inheritance is uncached, unparameterized inheritance is cached" is kiiind of teachable

#

especially in a world where mixins are completely unsupported

rare whale
split harness
rare whale
#

In other words, is "zero parameters" the same as "unparameterized"?

split harness
#

But there could be

rare whale
karmic rock
#

From a puritan pov I'd like them to be more integrated, but from a practical pov I think it's OK to have two different mechanisms for "static list of components with default values" and "dynamic partial scene"

split harness
#

() or {} in the context of inheritance (and mixins) means uncached

rare whale
split harness
#

And we'd want to disallow:

Node
green
split harness
#

And require:

Node
green()
rare whale
#

Having a bare function name is just weird

split harness
#

And it leaves the door open for them to be different, when that is beneficial

#

But I do quite like the "cohesive" approach / I suspect we will stick with it

karmic rock
#

In case it matters: one advantage of prefabs is that they can be accessed, queried for, serialized just like regular entities. It provides a level of introspection that you typically don't get with something that describes X vs. something that is X

#

You can also modify prefabs at runtime which can immediately affect all instances (in case components are inherited)

rare whale
#

OK so how does this dovetail with your previous statements about requiring the named component to be a real component that is inserted, as opposed to a phantom template (one like on which inserts something different). Do we have a rule, and does this rule apply equally to all the various :forms?

split harness
rare whale
#

For example, although it's possible under the current system to have ForEach() rather than for_each(), I resisted that temptation. This does not insert a ForEach component, but rather creates a ghost node with a reaction.

split harness
split harness
karmic rock
#

Yup that makes sense. Can't argue with it increasing complexity of the code 😅

split harness
split harness
#

Although the dust is still settling in my mind on everything here

karmic rock
#

Inheritance does allow for a lot of neat stuff though, since inheritance is used more broadly across the ECS to mean "treat this entity as if it's this entity":

// prefab inheritance...
world.prefab<Tree>().add<HasLeaves>();
world.prefab<Oak>().is_a<Tree>().add<HasNuts>();

// ... uses the same representation as component inheritance
world.component<Unit>();
world.component<MeleeUnit>().is_a<Unit>();

// which can be used by queries
world.query<Unit>(); // matches entities with Unit, MeleeUnit
split harness
karmic rock
split harness
karmic rock
#

Yep, except that the results of the hopping is cached, so in practice there's minimal overhead

#

You will see overhead when evaluating uncached queries

split harness
karmic rock
#

I basically track per queried for component where it comes from (for queries that need it)

split harness
karmic rock
#

Yup it's done per archetype

split harness
#

Rad yeah that seems like a very stomachable cost

karmic rock
#

Yup, it typically ends up being a net positive on cost, because you're accessing less memory

#

On the downside, you do need to make sure that your cache stays up to date. If you remove the inherited component from a prefab, the cache needs to be updated

split harness
#

Do you invalidate the cache? Are is_a relationships removable / replaceable?

split harness
karmic rock
#

Yep :p It's a good sign haha, that should be the first question anyone is asking

#

But for prefabs it's usually ok, since they're rarely modified after instantiation

split harness
#

Seems like you could cache the location on the archetype itself?

#

Rather than per-query

#

And then you could get the benefit in "uncached" queries / single-entity-lookups too

karmic rock
#

I experimented with that for a long time, but couldn't get it to quite work the way I wanted it to

#

The tl;dr is that the memory cost can become very high as there can be many tables, and that if a cache is not confined to a query (which has very predictable access patterns) and rather is something that the ECS can access at any point in time, it's much harder to guarantee the cache is up to date

#

I literally spent years experimenting with this 😛

karmic rock
#

What I did land on though is a cache that improved performance for observers. In short, when you add an is_a<Prefab>() to an entity, I generate OnSet events for every inherited component. Getting that list of inherited components is expensive, so I have a cache for that

split harness
#

Fragmenting relationships probably help make that less viable

karmic rock
#
  • there is a cache I construct and tear down when evaluating uncached queries, so I don't evaluate the same path multiple times per query
#

so caches upon caches upon caches 🙃

split harness
#

UncachedQuery LessCachedQuery

karmic rock
#

This is btw the same mechanism that queries for a component from a parent entity. The generic version of it is "follow relationship X until you found component Y". In the case of inheritance that relationship is IsA

silk lava
#

Missed so much interesting discussion 😋
If: :prefab is the cached/inherited, and was shorthand for IsA [ copy(prefab.entity()) ]
So it could copy the cached prefab entity to an new instance that is IsA related.
This could have the benefit of allowing :prefab(prop: ComponentA{})-> IsA [ (copy(prefab.entity()) ComponentA) ] or something 🤔
IsA being archetypal inheritence instead of compositional inheritence I think actually helps with the separation of concerns with respect to the prefabs components and the user's bsn components on their entity.
It also makes it that much easier to dynamically change the inheritence later on as well, just swap the IsA relations.

split harness
# silk lava Missed so much interesting discussion 😋 If: `:prefab` is the cached/inherited,...

Actually encoding IsA in the ECS and supporting component inheritance isn't reeeally on the table at the moment. Maybe in the future.

:prefab(prop: ComponentA{}) -> IsA [ (copy(prefab.entity()) ComponentA) ] doesn't work in the context of what we've defined (or at least, doesn't solve the problem), as the value of prop: ComponentA could have aribtrary effects on the scene being produced. So we'd need a different cached copy of the scene for each potential value of prop

silk lava
#

Ah I see what you mean, so a prefab kinda refers to -> impl ResolvedScene and template to -> impl Scene .
Could/does an Resolved Scene store some metadata after resolving, such as its props types/destinations/effects(?), so that when used in the future at least less work could be done instead of a full scene resolve 🤔 (I am not knowing the internals/limitations sorry 😥 )
So if prefab is for full cached, and templates for uncached, what about partial cached? (maybe its less efficient then just using a template):
prefab = full cached, just copy-paste // Like entity.duplicate()
prefab-lite = mostly cached, copy-paste what you can, then inject what was given using metadata // Like entity.duplicate() but reapplying bundle effects thonk
template = uncached, basically just bsn recursion, resolved alongside the host scene // Like insert(bundle)

rare whale
#

@split harness Having slept on this, I have some additional thoughts. I'm going to adopt Sander's terms "prefabs" and "template" for the purposes of this discussion. So from what I understand a BSN block consists of prefabs, templates, and bare components. I assume that bare components are not cacheable because most components are parametric (I guess you could cache markers but why make an exception). So in terms of ordering, you would want to put the prefabs first, followed by a mix of components and templates.

  • I guess that prefabs that contain templates include a snapshot of the template in the cache, while templates that contain prefabs get a separate cache of the prefab.
#

In any case, using the : prefix character to mean both "prefab" and "template" (in the case of :Button {} is problematic - yes, it might be teachable, but I suspect it's going to create a lot of confusion.

#

Some alternatives might be :: (double colon) for prefab and : for template. Either that or : for prefab and @ for template.

#

If we weren't limited by Rust's macro capabilities, the ideal solution would be type-based: prefabs would return a different result type than templates.

copper dragon
keen patio
# split harness I'm currently working on making `commands.spawn_scene(bsn!{})` performance viabl...

Loving this direction! ♥️

I always felt a bit uneasy about BSN inheritance and the way two different use cases fight for its functionality. It makes a lot of sense to separate into into two concepts (unparameterized/cachable and parameterized/non-cachable), to not end up in a situation where one has to pay performance/ergonomics costs for the sake of supporting the other.

Been thinking a bit about how this affects entity scopes, some loose thoughts:

#

With inheritance (cached), it makes sense to have access to the inherited "members" (entities), for example:

bsn! {
  :"superfast_car.gltf"
  // Observer for specific target, with access to inherited scope
  on(#DriverDoor, |interact: On<Interact>| {
      // put the player in "drive mode"
  })
  // Some kind of syntax for "deep patching"
  * [
      // Patch by entity name
      #DriverSeat (
          Seatable
      ),
  
      // Patch by made up regex-like syntax
      #Wheel.* (
          Suspension {
              stiffness: 10.0,
              damping: 1.0,
          }
      ),
  
  ]
}

With the "cachable" constraint, we can also explore different ways of pre-indexing the inherited scene to make such deep patches efficient and ergonomic, and allow matching on other things like "patch all inherited entities with component X".

#

Mixins feel more like "composition over inheritance", where you build up a scene from different (abstracted) pieces without necessarily having access to the "members" of those pieces. Instead all customization could be done through the "props".

With that in mind, maybe entity scopes should not cross mixin boundaries? And all kinds of overrides/slotting in new entities would go through props only. Abstracting away the internal structure of the mixins entirely. (functional style, kind of similar to React components, but also like CSS classes, because there can be more than one)

// Mixin
fn input_dialog(input_ref: EntityRef, content: SceneFn) -> impl Scene {
    bsn! {
        Node { ... }
        [
            Node [
                // Slotting in content through props
                {body_content()}
            ],

            // Tying internal entity to outside ref through props
            #{input_ref} TextInput,
        ]
    }
}

// Usage
let name_input = EntityRef::new();
bsn! {
    input_dialog(#name_input, || bsn! {
        Text("Enter your name:")
    })
    on(#name_input, |input: On<Input>| {
        // handle input
    })
}
split harness
#

@rare whale I'm actually going to use Bevy terms when possible, as I think using the flecs "prefab" / "template" terms ends up being lossy here. They prevent us from discussing this in detail (and they create confusion as bevy templates are a very different concept than flecs templates). For the purposes of this discussion, I will not use flecs terms without a "flecs" prefix.

I assume that bare components are not cacheable

In a way, there is no such thing as "bare components" in a scene, only patches of Templates that produce components. These templates can and should be cached when they are defined inside of an "inherited / cached scene" (flecs prefab). A cached scene is just a "flattened" collection of Bevy template patches layered on top of each other (and the same for related entities). This is a "full" hierarchy of templates, ready to be spawned.

However for one-off commands.spawn_scene(bsn!{}), we would prefer "uncached" behavior for templates (ex: component templates) defined there, with copy-on-write behavior.

So in terms of ordering, you would want to put the [flecs] prefabs first, followed by a mix of components and [flecs] templates

Yeah I do think "inherited cached scenes" (flecs prefabs) should come first, and in the case of multiple inheritance (if we choose to support it in this case), we should consider caching the combined "inherited cached scenes" to avoid redoing that work on every spawn.

If we weren't limited by Rust's macro capabilities, the ideal solution would be type-based: prefabs would return a different result type than templates.

If these are different concepts, I think from a "language design" perspective it makes sense for them to appear different, rather than trying to mash them together / infer that they happen to be different. That type of "what trait does this happen to implement" inference is not good for understability / legibility.

#

@keen patio

With inheritance (cached), it makes sense to have access to the inherited "members" (entities), for example:

One solution here is to use the EntityPath for this, although I suppose we could have a cached-template-time equivalent.

patch all inherited entities with component X

Perhaps, but as we live in a world where a Template can return arbitrary bundles and produce arbitrary effects with EntityWorldMut, this would have some limitations. In practice I believe the majority of people will use single-component Templates / returning those might be good enough.

Mixins feel more like "composition over inheritance", where you build up a scene from different (abstracted) pieces without necessarily having access to the "members" of those pieces. Instead all customization could be done through the "props".

"mixins" are still impl Scene currrrrently, which means they are still arbitrarily patchable. This is important, as a common use case is "theme-like" mixins that set specific fields, while leaving others untouched.

I'm not sure I see the need to make them fully abstracted over.

With that in mind, maybe entity scopes should not cross mixin boundaries?

This would help solve the EntityRef scope / indexing bug currently present. Seems reasonable. But it does make the "factored out" / "code-gen-ed" bsn! scenario less powerful.

keen patio
rare whale
# split harness <@301060831314182146> I'm actually going to use Bevy terms when possible, as I t...

Well, I do like the idea that "cached templates" and "uncached templates" would have different names, whether or not we use the word "prefabs" (and I admit there's a risk in claiming that term, since "prefab" means something specific in the game editor domain.)

Which brings us to a related question: what's the relationship between cachable template, parametric template, and scene assets? For example, in the simplest case one could declare that only cachable / non-parametric templates can be assets, and that might be enough for an MVP. Because otherwise, you'd need a way to pass parameters to the asset, which raises the spectre of the dreaded scripting language. At the same time, however, I suspect that this is a problem we'll be forced to address eventually. (I'm lobbying for a minimalist, pure-functional, expression-only evaluator which shouldn't be too hard to write).

Which is why I think that making it obvious that "parametric" and "non-parametric" templates are two distinct kinds of animal makes sense.

rare whale
#

The template system enables a pandora's box of possible abuses, but at the same time I've been able to leverage that flexibility to do a lot of clever things.

keen patio
# rare whale Consider that the `on()` helper doesn't return a component at all.

What I mean by that is an inherited scene containing a bundle template might not be patched the way one would expect.

fn base() -> impl Scene {
    bsn! {
        // The TypeId of this template is of `(Node, B)`
        template_value((Node { width: px(100.0), height: px(100.0), ..default() }, B))
    }
}

bsn! {
    :base
    // The TypeId of this template is of `Node`
    Node {
        width: px(200.0)
    }
}

// Making the final bundle (since templates are keyed by their TypeId):
((Node { ... }, B), Node { ... })

// Since the "patched" node comes last, I would guess that all values from the inherited Node are lost, meaning we don't get the height we would have expected.

That's why I'm a bit worried by Templates within scenes are allowed to return Bundle rather than Component.

#

patch all inherited entities with component X

Though I would probably want to re-phrase this as: "patch all inherited entities with template struct/enum X" (syntax-wise), meaning on would be excluded from that index

keen patio
# split harness <@164707123626770432> > With inheritance (cached), it makes sense to have acces...

"mixins" are still impl Scene currrrrently, which means they are still arbitrarily patchable. This is important, as a common use case is "theme-like" mixins that set specific fields, while leaving others untouched.

To clarify, I still think they need to produce patches. I just like the idea of them being more constrained and similar to pure rust functions producing patches, rather than trying to make them fit into the (rather complex, discoverability-challenged, and possibly mistake-prone from type system absence) scene inheritance that I feel is a better fit for the asset (on file or static in-memory) use case.

And possibly, it would be really cool if this more constrained type of patch could somehow be produced from either a TemplateContext or something similar, allowing injecting dependencies like a theme resource. And possibly have a "lifetime" of its own. I imagine that could be a missing piece for things like modifier-driven patches like WhenHovered(/*partial patch */) or Tailwind-style utilities using those "mixins". I do realize this is a hard problem to solve though... The chicken and the egg and all that 🐣

This would help solve the EntityRef scope / indexing bug currently present. Seems reasonable. But it does make the "factored out" / "code-gen-ed" bsn! scenario less powerful.
How so? If the caller scene is able to reserve an entity name/ref and then pass that in as a token of sorts through the props, the mixin could still use that to allow the caller to reference entities internal to the mixin. It would just be more "black box"/less leaky 🤔

split harness
# keen patio > but as we live in a world where a Template can return arbitrary bundle Does th...

Yeah these step on each other. Currently (due to the individual evaluation and insertion of template output bundles), whichever is defined "last" would overwrite whichever is defined "first". Ultimately, I'd like the final collection of Templates to implement Bundle / be insertable together. Using the current rules, that would mean that the duplicate components would result in an error. There is a reasonable argument to constrain the output to Component instead of Bundle, although that would mean bsn! would no longer support bundles.

split harness
# rare whale Well, I do like the idea that "cached templates" and "uncached templates" would ...

"parametric" and "non-parametric" templates
The things we are talking about here from a Bevy perspective are Scenes not Templates (ex: "parametric scenes", "cached scenes", "non-parametric scenes"). Sorry for being pedantic, but I think precision is required here. Templates are like world-access constructors for Component types, and the fields that make the up. Scenes define hierarchical patches of these templates.. Some of them have input parameters. Some of them are cached. Happy to discuss renames, but those are the names of the concepts until something changes.

what's the relationship between cachable template, parametric template, and scene assets? what's the relationship between cachable template, parametric template, and scene assets?
I see no reason why a "cached scene" cannot include usage of an "uncached scene", or why a template cannot instantiate a cached scene. They were designed to work together in that way / cross compatibility is one of the main selling points of the current approach. I think the big limitation is that cached scenes currently do not have parameters. If we were to add them, in a way they would "become" uncached scenes, as we would need to regenerate the scene as each spawned instance would have different parameters. Naively this would be the whole scene, but in practice there could be granularity.

#

From an "asset scenes vs code scenes" perspective, for the MVP scene assets are very unlikely to support parameters (just to keep the complexity down), and therefore they are "cache-able" by default.

split harness
rare whale
#

@split harness - @keen patio makes an important point here:

If the caller scene is able to reserve an entity name/ref and then pass that in as a token of sorts
Many of the use cases for named entities can be solved equally well - or in some cases better - by pre-reserved entity ids. Both are aiming for the same end, which is having two parties agree on a common entity id so that they can use it as the basis for coordination; the difference between the two methods is which party is responsible for reserving it.

I've used this technique quite a lot in the non-BSN spawning world, using commands.spawn_empty(): create a blank entity, capture it in closures as needed, then fill in the components.

I think it's interesting that nothing like this is available in React or Solid - while you can create a bare DOM node, you can't later use it as a JSX element. This means that there is the possibility to look past the common idioms of those frameworks and break new ground.

Unfortunately, this method isn't available in bsn either, not unless you want to pass in some context param able to reserve ids.

#

At the moment, I'm finding that limitations around id-sharing are one of the more acute pain points in my node-graph prototypes. The graph has a fixed structure which looks like this:

  • Graph
    • GraphContents (inner part for scrolling)
      • GraphNodes (contains all node entities)
        • GraphNode1
          • Terminal
          • Terminal
        • GraphNode2
          • Terminal
      • GraphConnections (contains all connection entities)
        • Connection1
        • Connection2
          Lots of operations require access to specific entities from below or above in the hierarchy. Events need to be forwarded to the root so that clients can listen for them. Drag calculations need to calculate coordinates relative to ancestors, and picking coordinates need to be mapped to local transforms. Without a robust way to share ids, I'm forced to fall back on marker components, which means that more than half the code of every observer is just traversing the hierarchy to find the right parent or uncle. And given that the graph, graph node, and graph connection each have half a dozen dragging observers, the amount of boilerplate starts to really pile up.
keen patio
rare whale
rare whale
rare whale
#

Let me get back to my node graph example for a moment. The structure now looks like this:

  • Graph
    • GraphDocument (contains all nodes and connections)
      • (nodes and connections)
    • GraphScrollbar (vertical)
      • GraphScrollbarThumb
    • GraphScrollbar (horizontal)
      • GraphScrollbarThumb
        (Note that I had to write special scrollbars here, can't use the feathers ones because they use ScrollPosition which doesn't support negative scroll coordinates.)

OK, so now imagine that I'm dragging the scrollbar thumb element via On<Pointer<Drag>>. However, in order to do the calculations, we need to know (a) the visible size of the viewport (which is obtained from the Graph element), and (b) the current scroll position (which is obtained from the GraphDocument element).

To get the document entity, we need to go up two levels from the thumb (using ChildOf), and then down one level to the document entity (by iterating over Children). This code has to be repeated for both drag_start and drag. Similar traversals have to be done for track click and scroll wheel events, although not quite the same since the starting entity is different.

#

So the first question is, why not just give #names to the various entities?

#

Like, why not have a component at the Graph level that stores the ids of all the various parts?

#

Unfortunately, this isn't possible because of the way that Graph is constructed:

:node_graph()
// Caller gets to customize how the graph is positioned
Node {
    position_type: PositionType::Absolute,
    left: px(0),
    right: px(0),
    top: px(0),
    bottom: px(0),
}
// Many details omitted
[
    :node_graph_document()
    [
        // nodes and connections go here
    ]

Both the Graph and GraphDocument builders are functions which internally contain many components and observers. In order to pass the id of the node_graph_document entity to the graph function, I'd have to make it a parameter to that function, which would expose more details about the internals of the graph than I want.

#

Now, ideally, the user wouldn't have to call node_graph_document at all, it would be automatically added by node_graph and hidden as an internal detail. This would let me grab the entity ids and build a reference component. Unfortunately, if I do that, I can't use the bsn children syntax to add children to the document. Instead I would have to pass the children as a separate bsn_list parameter.

#

This gets even more complicated because I want:

  • users to be able to easily know where in the hierarchy to add new nodes and connections
  • all nodes should be drawn in front of all connections
    So for example, we could tell the user "always add nodes to the end of the document's children list, and connections to the front" but that's a potential footgun. Instead, it's more reliable to have separate parent entities, one for nodes, and one for connections. However, this means that there are now two separate sets of children to be inserted into the document.
rare whale
#

Anyway, I'm not sure what's the right answer here, or even if the answer lies within the scope of BSN.

#

The larger point I am trying to make has to do with the overall developer experience of bevy_ui. I can validate from personal experience that BSN significantly improves the DX of working with bevy_ui; however, even with BSN it still falls far short of parity with other UI frameworks in terms of ease of use, and that is because many of the difficult aspects are outside of the scope of BSN. When I combine BSN with a reactive framework, it gets a little closer to parity but is still not where I want it to be.

rare whale
#

If I were writing this in React or Solid, there are a couple of ways I could structure this. One example would be like this:

<Graph className="my-graph">
  <GraphConnections>
    // Put connections here
  </GraphConnections>
  <GraphNodes>
    // Put nodes here
  </GraphNodes>
</Graph>

There's no GraphDocument element shown here, because the Graph component creates it automatically. The connections and node as passed into the Graph component via the children parameter, so it can insert those elements inside the GraphDocument element, allowing them to be scrolled.

Because the GraphDocument is responsible for creating the document, as well as the scrollbars, it can add ref parameters to each element:

// doc_ref is a mutable cell which will be filled in with the element reference
let doc_ref = useRef<Element>();
let on_graph_click = useCallback(...);
let on_graph_drag = useCallback(...);
return (
  <div 
      className={clsx("graph", props.className)} 
      on_click={on_graph_click}
      on_drag={on_graph_drag}>
    <div className="graph-document">{props.children} ref={doc_ref}</div>
    <GraphScrollbar orientation="vertical" />
    <GraphScrollbar orientation="horizontal" />
  </div>
)

Because the doc_ref cell is declared before any of the callbacks, it's able to be captured by the callbacks even though the cell isn't filled in until later when the dom nodes are actually created. With BSN, by comparison, #name ids are only available to callbacks that are inlined within the bsn block.

#

The net result is that from a developer experience standpoint, the React version provides a more encapsulated API that leaks fewer implementation details to the caller.

rare whale
#

Hmm, if we had AtomicEntity, then we could maybe use Arc<AtomicEntity> to implement something like React's useRef.

#

This would have the following properties:

  • It's a cell containing space for an entity.
  • It can be captured by closures, and cloned (so that it can be captured by more than one closure).
  • The entity doesn't need to be filled in before the closures are defined.
  • The entity does need to be filled in before the closures are executed - panic if not.
  • The cell can only be initialized once.
viral roost
#

I'm wondering though if there can be some type guarantee around it, so a Ref<T> that you can pass around that says "this is a Entity reference, and you can expect that to have a T component on it at some guaranteed point in the future

rare whale
split harness
split harness
split harness
#

Although that introduces new usability and cache-ability concerns

#

One of the "points" of the current BSN approach is that it is context-less / can be defined as a standalone bsn!{} snippet

rare whale
torpid hemlock
heady latch
#

tasks!, conditions!, and effects! are just related! wrappers for relations of type Tasks, Conditions, and Effects, so they're hopefully obsolete once we have BSN

#

for convenience, here's a screenshot

#

note this part in particular:

#

The three operators here have the same "rank", but one is indented because it's a bundle

#

which is... confusing

#

I think we already discussed the indentation of BSN in the past, just want to add this as a data point 🙂

elfin surge
# heady latch note this part in particular:

I quite like the --- based separation with this example

Tasks [
    Operator::new(choose_bridge_to_check)
    ---
    Operator::new(navigate_to_bridge)
    Effects [ Effect::set("location", "bridge") ]
    ---
    Operator::new(check_bridge)
],
rare whale
# heady latch In case y'all want some sample code for thinking about BSN in a non-ui, non-chil...

That looks very similar to the reflex system I implemented in JavaScript, and which I was planning on porting to bsn eventually. Reflex had a somewhat different set of control-flow primitives: sequence, contingent, loop, random, etc.. Also it was designed to support composable behaviors, so that you could define a character with multiple prefab behavior packages ("combatant" + "territorial" for example.)

magic belfry
silk lava
#

With () you can do same line bundles, and it not look too bad, but with -- that would look weird.

Tasks [
    Operator::new(choose_bridge_to_check)
    --- Operator::new(navigate_to_bridge) Effects [ Effect::set("location", "bridge") ]  ---
    Operator::new(check_bridge)
],
autumn bane
#

I would argue the canonical way to do that would be

Tasks [
    Operator::new(choose_bridge_to_check)
    --- 
    Operator::new(navigate_to_bridge) Effects [ Effect::set("location", "bridge") ]
    ---
    Operator::new(check_bridge)
],

Yes, the delimiters still own a whole newline, but I don't mind it personally. I'm also not sure it's all that important in the first place.

rare whale
# heady latch same here I believe 🙂
export enum GoalPriority {
  ULTIMATE = 25 /** Goals which the character would be willing to die for. */,
  SAFETY = 20 /** Reactions to threats and immediate danger. */,
  MISSION = 15 /** Goals in the furtherance of our life's purpose. */,
  SOCIAL = 10 /** Basic character interaction goals. */,
  HEALTH = 5 /** Life maintenance - food, sleep, etc. */,
  IDLE = 0 /** What we do when we have nothing else to do. */,
}
heady latch
heady latch
rare whale
#

BTW I think I first encountered this kind of logic at a talk at CGDC some time in the 90s.

shell dragon
weary tapir
#

Hi, is the draft PR planned to be rebased on 0.17.3? I made a library that relies on the new Scene, I'd like to use my library together with avian3d, but avian3d relies on Bevy 0.17.3... or do I have to wait for 0.18?
https://github.com/bevyengine/bevy/pull/20158

GitHub

Welcome to the draft PR for BSN (pronounced &quot;B-Scene&quot;, short for Bevy SceNe), my proposal for Bevy&#39;s next generation Scene / UI system. This an evolution of my first and s...

echo moat
#

I don't think BSN will necessarily be included in version 0.18. Aren't they preparing to release version 0.18rc? But BSN hasn't been merged into the main branch yet.

crisp garden
#

Don't think we should nearly be at the next rc process yet 🤔

native mesa
#

A lot of us as expecting the rc to start around dec 14th. Based on current progress, I think there’s a reasonable chance this wont make it in for 0.18

#

Which is fine, we explicitly don’t want to do crunch or hard deadlines.

weary tapir
#

Given the specifics of my project, I'm totally ok working on an unreleased branch of Bevy for now. But I need scenes

split harness
#

Or rather, the next set of changes will be syncing with main

weary tapir
safe cloud
#

Is the only thing holding it up just that it needs to be sync'd/merged with main?

autumn bane
safe cloud
#

Ah that's disappointing I thought it was a sure thing for 0.18 I've been holding out on starting a new project just for BSN haha

thorn bison
weary tapir
twin blaze
#

Thoughts on splitting this PR into two?

#

Have a base which can get merged and the implementation still being worked on remaining in the draft PR?

coral marsh
#

it would be nice to have some basic scene format so that experimental editor implementations have something to serialize scenes into

safe cloud
#

I would definitely take something even if it’s not the complete perfect solution.

I feel like it’s been baking for a while and a wider dogfood-ing audience might be helpful anyway

cobalt stone
#

The virtual geometry processor is basically blocked on not being able to write scenes with assets atm

#

Just having that would be huge

weary tapir
signal hearth
dapper sky
#

it is the clear lynchpin of a lot of progress, which also means a lot of pressure is placed on it to be Good From The Outset.

raw mesa
#

I really wish that we would have merged this experimentally, and then iterate. Even ignoring dev velocity, mega PRs (like Assets V2) are a nightmare to deal with because A) it's not as thoroughly reviewed, and B) when looking back at the git blame, there's much less history - it's unclear whether something was done intentionally, or if it was an accident that wasn't noticed. Richer git history can make that more clear.

#

We also could have just made this its own crate, and then make its version number like v0.0.1 to ensure users don't use it. If they do, I have no sympathy for breakages - they signed up for it

signal hearth
#

I feel like making it a separate crate that gets merged in when ready is probably the best of both worlds, people who want to take the risk can use it, but it's not in the core engine feature set either

dapper sky
#

From how I understand it there is a fair chunk of internals reworking tied up in it's implementation, especially when considering reactive bsn

smoky sandal
#

This is holding back a lot of stuff. What's the holdup on it? Is there a list of unresolved issues somewhere?

karmic rock
coarse patrol
heady latch
#

I imagine you also have some natural way of serializing scenes?

heady latch
#

I hope BSN doesn’t have as many undocumented breaking changes to existing code, but if it does, I am not looking forward to scrolling through the diff

echo moat
#

Recent updates have been easy to migrate, with most warnings indicating obsolescence. I don't think BSN will be a major issue either; older data should still be usable.

thick slate
#

In terms of migration, it might actually be best to ship both scene formats in the same release

#

And then you can migrate them by using the ECS representation as an intermediate state

quartz meteor
slender lion
signal hearth
#

I didn't even know there was a current format NGL

slender lion
#

(on disk anyway, its Scene at Bevy runtime)

native mesa
#

Would it be possible to get an update on the remaining blockers for merge? Last I recall, there were performance issues with spawn_scene. Is that still the focus?

split harness
#

(this is my first day back from vacation / sickness)

split harness
#

Alrighty from my perspective this is the must-have todo list before merging:

  • Improve spawn performance: Avoid fully re-resolving scenes on every spawn. Cache resolved inherited scenes and spawn in layers. Not finished, but should be able to wrap up in short order.
  • Fix [] Ambiguity: Lots of options to choose from, just need to choose one. Most proposals could be implemented pretty quickly. I'm kind of leaning toward landing a conservative fix first like @[], Children: [], or [] sugar removal, then following up with bigger syntax changes later if we decide to go that route (ex: --- separators or <>).
  • Fix #Name reference bug for inlined scenes: patching/inheritance and inline bsn! have a corner case that can break references. Current indexing scheme might be non-viable. Need to investigated fixes and-or alternatives #1264881140007702558 message
  • Merge with main: No major issues anticipated here
  • Cleanup pass: Remove placeholder examples. Fill in docs. Strip out Bevy Feathers port (which should be reviewed and merged separately ... I'd default to letting @rare whale drive that, but I can also do it).

I think this must be fixed before next release containing BSN, but doesn't need to block the initial merge:

  • Fix BackgroundColor(palette::Z_AXIS) syntax case, which is currently broken / requires BackgroundColor({palette::X_AXIS}) or BackgroundColor(Z_AXIS)
#

And ideally these land before the next release containing BSN (ex: if BSN lands in 0.19, these ideally also land in 0.19):

  • Feathers BSN Port: Largely already done. Just need to reconcile with current state of main. This will help BSN land well, so landing it alongside BSN is a high priority.
  • #Name references in more places: The UI eventing scenario really wants #Name to be usable in closures. This would functionally be expressed as a template that returns a closure that accesses a specific entity. This unlocks a lot of value for UI devs, so ideally it lands alongside BSN.
  • Top-down vs bottom-up spawn order: Currently BSN follows the normal bevy top-down spawn order. I think we should heavily consider spawning bottom-up, in the interest of making scene contents available to "higher level" components in their lifecycle events (ex: a Player component accessing nested entities like "equipment" when inserted). If we decide to keep things as they are, we probably want to introduce additional "scene ready" entity events that trigger "bottom up".
  • Struct-style inheritance: It would be nice to be able to do something like :Button { prop } instead of :button(prop). I'd really like us to explore this being component-tied (ex: associate a scene with a Button component).
  • ResolvedScene-as-dynamic-bundle: ResolvedScene should insert all of the components at once as a single bundle, rather than one-by-one, which is really bad from an archetype move perspective.
  • Inline field value expressions: Support cases such as px(10).all()

There are plenty of things to do after that: BSN asset format, reactivity, etc. But those can be built on their own time.

#

Note that Bevy 0.18 release prep, sorting out the 2025 Bevy Foundation annual report, and planning out our 2026 development strategy with the other maintainers needs to happen before I start working on this again.

I'd welcome help with the #Name reference bug if someone wants to help unblock the BSN merge / is interested in picking that up. I'd love a solution that doesn't force us to pay a high runtime performance cost. The current impl costs about as much as I'd like to pay.

viral roost
#

Highly appreciate the fix, merge then iterate approach, is the intention to keep the crate as bevy_scene2 for now?

steel oak
rare whale
rare whale
rare whale
exotic panther
#

one thing that would be nice to add is being able to chain operators in fields, currently px(10).all() requires an expression block which is a bit of an annoyance to deal with

heavy valve
# rare whale This gets even more complicated because I want: * users to be able to easily kno...

Sorry if this is too much of a necro, but I ran into this problem in my project and "solved" it thusly, and wanted to get your thoughts on this approach -

I described a form-control kind of relation using component hooks to create a "deferred relationship" - the individual form elements are defined with basic marker components that navigate up the heriarchy to find a 'pin' that directs them to a specific entity responsible for form state management. The hook then deletes the marker and forms a form control relation between the marked entities. Systems / observers can then just use relationship traversals to manage behaviors.
Neither root nor leaf needs to know about the other, and can therefor be defined separately.

It feels like it could be kind of free-ing to have a method of solving this problem without requiring explicit reference to a future entity somewhere.

split harness
# viral roost Highly appreciate the fix, merge then iterate approach, is the intention to keep...

TBD. The contents of bevy_scene in its current form needs to stick around unless it is replaced by an alternative "world serialization" option. The options in my mind are: (1) leave it as-is, with some renames / rebranding to avoid confusion (2) bi-directional BSN (3) a separate less-opinionated bevy_ecs + bevy_reflect serialization system.

I'm thinking bevy_scene is probably the best place for the core scene system (as opposed to bevy_bsn, which could be the home of the macro + asset format that is built on top of it).

I don't yet have strong opinions about the transition plan or the final homes for these things.

split harness
split harness
rare whale
split harness
#

Or perhaps devising as scheme like "family: FONT_FAMILY" "asset: PATH_TO_ASSET"

#

Although I would not advocate for that. Pretty gnarly

rare whale
split harness
#

Yup that would work

exotic panther
#

one bit of convenience that would be nice is a template attribute on GetTemplate for custom templates similar to component hooks.

fn get_entity_from_path(path: String, context: &mut TemplateContext) -> Result<ReferenceEntityFromPath> {
    ...
}

#[derive(GetTemplate)]
#[template(get_entity_from_path)]
pub struct ReferenceEntityFromPath(Entity);

not certain how this would play out with different types on the template, but it's something to consider

crude pawn
# rare whale Maybe not. It just needs to be clear that when you use a font name in a bsn temp...

I think probably fonts don't work as assets anyway. When a Font's asset handles are dropped the font data isn't deleted because Cosmic Text clones the Arc with the font data and stores it in its font database, and once a font is added to the database you can't delete it. So the only way to remove fonts is to create a new FontDb instance, minus the font you want to remove. And creating a new Database changes all the font IDs, so you have to regenerate all the text data and layouts. Which is all very expensive and not something you want to happen automatically usually.

crude pawn
#

Also it leaks. If a font handle is dropped and its font asset is unloaded, if you reload the same font again later the new font data is added to cosmic text's FontDb as a new font alongside the old one.

#

And Font's are normally static data, there isn't a lot of need for applications to load and unload them dynamically.

#

And font assets can contain multiple faces, but the handle can only be used to reference one of them.

copper dragon
copper dragon
crude pawn
#

It's unusual for an application to unload a font and the data isn't that large, the individual faces, texture atlases, layouts etc are much more expensive

#

and immutability makes it easier to manage the font IDs and keep everything consistent

#

if you could remove a font, that would mean all the font queries and layouts would be out of date and potentially invalid.

crude pawn
rare whale
#

@split harness @thick slate It would be helpful, I think, if there was a tracking issue for BSN that contained all the relevant links to discussions, discord channels, branches, and so on. Right now the info is scattered in a lot of places, and if a newcomer were to come along and want to know more about BSN it would be hard to point them to the relevant background material.

#

Kind of like a "BSN Awesome List" 🙂

thick slate
#

I expect we'll use a project board rather than tracking issue but this is in the works 🙂

rare whale
#

@split harness Any notion of a likely ETA for a new BSN branch? I imagine that merging with main will take some work.

#

Also, I saw you mentioned my work on reddit, thanks for the shout-out 🙂

signal hearth
#

I am interested to see BSN get developed more ZabbyBongo

split harness
# rare whale <@153249376947535872> Any notion of a likely ETA for a new BSN branch? I imagine...

I suspect that the big challenge with the main merge will be reconciling the Feathers differences, which we might want to tackle separately. Outside of that I expect it to be pretty quick.

I need to spend the next few days reviewing other peoples' code, but theres a reasonable chance that I can be back on the "focused BSN train" this week. So an optimistic estimate of some time this week, but also maybe some time this next week.

From there, the remaining changes will take the time that they take, but I suspect we can move forward pretty quickly on those

rare whale
split harness
#

Although losing Feathers UI as a test case / proving ground is a big loss from a development and review standpoint

#

But we'd probably only be in that state for a short period of time

rare whale
rare whale
#

@split harness I don't want to distract you, but I wanted to mention: in my reactivity experiments, there's a lot of repetitive boilerplate around the making of BSN templates. I have a bunch of different primitives, such as effect, effect_memo, insert_computed_when, conditional, foreach, and so on; and each of those has:

  • a generic struct and a Template + Scene impl which is used as the template
  • another struct which is actually inserted into the Reaction component on the satellite entity, plus an impl Reaction
  • a wrapper function to make the BSN syntax nicer
    Moreover, the Template and Scene impls are largely the same (but just enough different that abstracting them into a single generic eludes me, although I'm sure a better Rust programmer than I am could do it). Here's an example: https://github.com/viridia/bevy_reactor/blob/main/crates/bevy_reactor/src/effect.rs#L405
rare whale
#

This pattern appears in 8 different places:

impl<
    Condition: Lens<bool> + Clone + Send + Sync + 'static,
    C: Component,
    Factory: Fn() -> C + Clone + Send + Sync + 'static,
> Scene for InsertWhen<Condition, C, Factory>
{
    fn patch(&self, _context: &mut PatchContext, scene: &mut bevy::scene2::ResolvedScene) {
        scene.push_template(InsertWhen {
            condition: self.condition.clone(),
            factory: self.factory.clone(),
        });
    }
}

You would think that you could just replace the inner part with self.clone(), but no; it complains that it's not a bundle.

autumn bane
# rare whale This pattern appears in 8 different places: ```rust impl< Condition: Lens<bo...

You would think that you could just replace the inner part with self.clone(), but no; it complains that it's not a bundle.
This is just a small point, but that's likely because clone() is just cloning and returning a reference to the InsertWhen. Did you derive clone on InsertWhen? If so, the derived implementation unnecessarily expects C to be clone, so you should instead implement Clone manually.

split harness
# rare whale <@153249376947535872> I don't want to distract you, but I wanted to mention: in ...

Yeah implementing custom "combo" Scene / Template impls is definitely boilerplatey. I don't see this as being an immediately pressing issue, as the system was designed for the vast majority of people to implement their logic by deriving GetTemplate or Default. That is the intended "user facing app developer data model"

If you are doing a custom Scene trait impl, you are probably "expanding" the scene system capabilities in some way (ex: reactivity, on / observe, etc). At this stage, I am ok with people in that category taking on additional boilerplate.

I've tried unifying Scene and Template in the past, and failed every time. They serve different roles. Scene is for "scene resolution / baking" (how does your "piece" of the scene get baked into the "whole" of the scene). Template is for "scene instantiation" (how does your scene data become components). That subtlety does come into play, just not for the simple types of Scene impls you are doing.

I'm very open to anything that simplifies the system without losing something, but I don't think that is low hanging fruit, and the fruit might not be there at all. No harm in investigating, but I don't personally think it should be a priority for us at this stage, given the sentiment to get something in. I think the thread to pull on here, for interested parties, is that the system is increasingly "dynamic". We're no longer directly taking advantage of the (TA, TB, TC, TD): Template<Output = (A, B, C, D)> capability to combine multiple templates statically and we are instead producing a dynamic collection of TA: Template<Output = A>. There might be a way to unify things with the statically compose-able Template::Output design constraint removed.

karmic rock
split harness
# karmic rock > Scene is for "scene resolution / baking" (how does your "piece" of the scene g...

Scene (resolution / baking) is doing things like resolving inheritance, defining the entity hierarchy / relationships, defining what Templates will be added to each entity / initializing their defaults, applying Template patches to existing Templates, etc. The output is a ResolvedScene, which is a dynamic entity/Template hierarchy (defined by relationships).

This step is ideally done once to create a final reusable ResolvedScene.

When instantiating (aka spawning) a ResolvedScene, you have a context containing things like the current Entity, the current World, current "entity references", etc. A Template reads this context and produces zero or more Component to add to the current entity being spawned.

#

This step is ideally done once to create a final reusable ResolvedScene.
For smaller scenes, it is ok to create one-off / one-time-use "on demand" ResolvedScenes. However for "expensive" / large scenes you should really be reusing them.

karmic rock
#

Ah interesting 🤔 that sounds a bit similar to the distinction I have between prefabs/templates

split harness
karmic rock
#

Can you have multiple Scene instantiations per actual scene?

split harness
#

Yup!

split harness
karmic rock
#

Ah ok, so is a Scene more like an asset hierarchy then? The naming always confuses me a little bit

#

Like say I have a Turret asset and a Cannon turret that inherits from Turret. Would that be a Scene?

split harness
split harness
#

A Bevy Scene is conceptually very similar to a "Godot scene"

#

(as in, the tscn file)

karmic rock
#

I've never worked with godot, I'm more used to unity-style prefabs. Should probably experiment a bit with it

split harness
karmic rock
split harness
#

They'd just rename prefab to scene

#

(ditto fo us)

karmic rock
#

Nice gotcha 🙂 thx that’s helpful

polar walrus
#

Out of curiosity, were there some other considered candidates for the naming of Templates ? I initially had the same question as Sanders when reading the messages above.

rare whale
steel oak
#

I don't care about naming as long as I can yeet my glXF crate 🙂

steel oak
#

Would BSN's design make it easy to add a "Save to BSN" option to bevy_inspector_egui? That would be something the artists at my studio would appreciate quite a bit in lieu of an editor

#

Like, right click on a hierarchy subtree, say "Save to BSN" as a prefab, so that you can load it later

thick slate
#

It's also really nice for testing if you can quickly generate snapshots of scenarios

rigid adder
#

Cool, so BSN will also replace the current dynamic scenes? They saved in .ron format.

exotic panther
#

not replace, bsn is just a human editable scene format, you can still use dynamic scenes for saving mid game

dapper sky
#

yeah, bsn is designed to be an asset format for design tooling as well as a jsx-style macro.

south lagoon
exotic panther
#

yes

wooden vine
#

I definitely wouldn't use BSN for that yeah

#

You most likely should use a format containing custom data for your game imo

wooden vine
south lagoon
wooden vine
#

BSN is for declaring structures of entities (prefabs/templates) and spawning them, simplifying stuff like spawning logic.
Eventually the plan is to get a BSN asset format that would be a serialised version of this, and then to get an editor that would let you have a nice interface for creating these templates.

#

In theory you could try converting entities in the ECS into some BSN asset that is able to recreate the values, but I reckon this is not a great approach, definitely not for save games, save games most often should store more abstract gamestate than the actual entities in the world. (This might vary depending on the game.)

plush pier
#

I can imagine some kind of BSN diff plugin crate being made, if you want to use it to serialize the changed state of an object and load it again

wooden vine
#

Tbh with all the "nice features" BSN has, I see this as a needlessly difficult problem

#

though we don't know how the BSN asset will look, but I personally would dread making any kind of automated manipulation of BSN-macro-like data.

#

There are many ways to represent the same resulting entities, and it's not very easy to figure out a diff between them. If I wanted a diffable format, I would use something simpler and closer to the ECS. (Depending on how complex the instantiation logic is, different ways of representing the same end-result might be actually not equivalent, for example if an entity has a child template, you can either represent the template invocation, or you "inline" the result of the template into the file directly.)

(Note that the flexibility of the BSN macro syntax is actually a major benefit when you're writing it manually, which is the current application of BSN.)

#

But take what I say with a grain of salt, I'm not an expert or an authority on this subject.

south lagoon
#

does anybody know if there exists an example among the official ones about save games? I can't find any at a first glance

native mesa
#

but imo, we should try to merge and iterate on a prototype rather than get it all right in the first go.

#

So I am holding off on voicing these concerns (or, at least, not calling them blockers) until it's merged and we can test it.

wooden vine
#

Personally I do not see how BSN translates into an asset format. That said I think the BSN macro is a good tool that I hope ships soon (even in its current state)

#

(And also I haven't spent nearly as long in this problem space as cart, so he definitely has more ideas than me)

thick slate
dapper sky
#

This can exist in a file, and strategies emerge for how to best create / modify that data.

#

We're just in a position where what we're doing is theorycrafting more than anything.

native mesa
#

But there's are not today's concerns, or really even tomorrow's concerns.

white lichen
#

just throw all the unsightly things in .meta

#

lol

split harness
#

Which would also be useful as an organizational tool for non-tool-generated bsn

#

Migration strategies have been discussed in my proposals

#

(in the context of the asset format)

native mesa
#

"hoist out" that makes sense. i still do expect we will want a flat topologically-sorted list with references, and that does seem like something that could be an extension. you've covered migration strategies at a high level and the plan there makes sense to me as well, given what godot and other engines are doing. these are hard problems though, and even knowing the plan I don't think I can personally say with full confidence that bsn as an asset format will end up being something people want to use. my positions is basically, it's worth a shot. let's see how it goes.

thick slate
#

(I wish Rust used these, so then it could auto-format and handle imports)

split harness
thick slate
split harness
#

Fair point!

#

Probably doesn't help for things like github diffs, or tooling that uses its own diffing algorithm (which a lot of IDEs do)

dusky plover
#

is there a summary on the current status of the next gen UI somewhere in this thread recently? I was somewhat surprised more didnt happen in 0.18 for UI so I'm just curious what's coming/in-work

slender lion
dusky plover
#

❤️

signal hearth
quartz meteor
#

Top-down vs bottom-up spawn order

There are valid cases for both of these, Player might want to know its Equipment children, but may also want to know its Team ancestor.
Whichever spawn order we go with it would be lovely to have spawn-order independent events for both top-down and bottom-up.

shrewd pasture
#

Is .bsn asset format coming to 0.19 with BSN itself?

#

I just have hopes that with BSN an it's asset format in place we can have a more productive work on the editor

#

Today I looked at Figma file with current concept, it looked so good!

#

So I got even more impatient to have it, at least in some basic form

near warren
karmic rock
crisp garden
#

I have read such intentions, but there were also intentions to land BSN for 0.18, sooo thonk

vast relic
#

only what is already merged is guaranteed to ship in the 0.19

near warren
shrewd pasture
#

So .bsn asset format has chances of landing in 0.19
But don't count too much in it ?

vast relic
#

we have two months to merge it, it's almost ready and a lot of people want it

#

so it should be in the 0.19, but...

silk lava
#

No, I highly doubt it, the macro syntax itself isnt finished, and putting that into a file format is even harder.

vast relic
#

or not 😄

#

achieving consensus in a highly distributed environment is slow

split harness
#

The macro / in memory asset representation is very likely to land in 0.19, as it is largely complete. The asset format on the other hand has a lot of work left to do, as I haven't even begun to port my older work to the newer BSN implementation.

unique fulcrum
split harness
#

Next Generation Scenes

split harness
#

I've closed out the current draft PR to prepare for the next phase ... upstreaming! I've prepared a new branch https://github.com/cart/bevy/tree/next-generation-scenes. This ports BSN to Bevy main and removes the Feathers BSN port (which we have agreed will be done separately).

I still have a short laundry list of things to do before the upstreaming PR. I'll start coordinating that effort over the next couple of days.

rare whale
#

I didn't have to make any bsn-related changes to my code but I did have to migrate changes because of mainline bevy

dapper sky
#

Really exciting ^^

split harness
#

Alrighty here is my "pre-main-merge" todo list:

#

Ignore Consider <> syntax

#

The current plan is to just remove the sugar and punt "better syntax" to the future

cobalt stone
#

Are all of these macro parsing work?

split harness
#

I'm working on "improve spawn performance" (see #engine-dev message). If anyone is interested in "Fix #Name reference bug" or "Fix BackgroundColor({palette::Z_AXIS}) case" , let me know

split harness
#

The #Name reference bug ties into the macro, but also ties into scene resolution

#

The BackgroundColor({palette::Z_AXIS}) bug is "just parsing"

#

The #Name reference bug is hairy / hard to describe. I'd appreciate if someone solves it before I get to it, but it would also likely take a bunch of my time to sync someone up

#

So BackgroundColor is probably the best place for someone to hop in

native mesa
split harness
#

Currently bsn! { BackgroundColor(palette::Z_AXIS) } is misinterpreted and produces the following output:

(<BackgroundColor as bevy_scene2::PatchGetTemplate>::patch(
    move |value, _context| {
        bevy_scene2::touch_type::<palette::Z_AXIS>();
    },
),)

This can be worked around by doing bsn! { BackgroundColor({palette::Z_AXIS}) }, but that obviously doesn't feel great

#

I suspect its an easy fix. But theres a small chance its actually a big deal / some sort of grammar ambiguity

native mesa
split harness
loud mural
karmic rock
loud mural
karmic rock
loud mural
#

Nice!

white lichen
split harness
split harness
white lichen
#

reading

split harness
#

Alrighty I have a working "unflattened scene inheritance" implementation. Here are some benchmarks:

immediate_function_scene uses "function inheritance", which is not pre-cached / is resolved inline / recomputes the full scene when inherited. This is essentially a measure of re-baking the entire scene.
immediate_loaded_scene uses "asset inheritance", which is pre-resolved / cached. This trades a small amount of per-spawn overhead (to follow the inheritance and resolve it for each spawn), but it gains a lot because it can reuse inherted "resolved scenes" (it uses copy-on-write behavior when it needs to touch a Template that the parent scene has). For pretty much any inherited scene, we come out on top here.
raw_bundle_no_scene spawns equivalent scene using raw bundles. this provides the baseline which allows us to understand the "overhead" we're incurring with the scene system

#

This is spawning medium sized-scenes:

fn ui() -> impl Scene {
    bsn! {
        Node
        Children [
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
            (:button Node {width: Val::Px(200.) }),
        ]
    }
}

fn button() -> impl Scene {
    bsn! {
        Button
        Node {
            width: Val::Px(150.0),
            height: Val::Px(65.0),
            border: UiRect::all(Val::Px(5.0)),
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Center,
        }
        [
            Text("Text"),
            Text("Text"),
            Text("Text"),
            Text("Text"),
            Text("Text"),
            Text("Text"),
            Text("Text"),
            Text("Text"),
            Text("Text"),
            Text("Text"),
        ]
    }
}
#

^ this is the immediate_function_scene version

#

I believe the majority of the overhead between immediate_loaded_scene and raw_bundle_no_scene is due to archetype moves (currently each template is inserted one-by-one)

#

This cost is easy to see (and fear!) when we add a Marker component to each Text entity:

#

Notice how the bundle approach is unaffected

#

Spawning ResolvedScenes as if they were bundles was something I was going to wait to do until after the main merge. I'll get these changes onto my branch first, but I'm going to take a peek to see how straightforward the bundle stuff will be

#

As the one-by-one component approach is pretty untenable

#

"Fully crunched" (faster individual spawns, but inflexible / not dynamic) vs "inheritance resolved on spawn" (more expensive individual spawns, but you can use BSN inheritance to tweak "large" scenes inexpensively) is something that we can configure per-scene

#

Well probably want that configurable in the AssetLoader per-scene

#

And we might want special syntax to make it configurable inside bsn!

#

Although in a way we already have that with :inherited_scene vs inerited_scene (which is always evaluated immediately / applied directly to the scene)

#

We'll probably want some sort of pre-crunched equivalent for function-style scenes (ex: :inherited_scene, as right now, this only works for asset paths: :"inherited_scene.bsn")

loud mural
#

Oh silly me

#

It’s ref?

#

Sorry just re read

#

I thought the two functions were seprate

#

My bad

split harness
#

All good!

loud mural
#

So is button what you were discussing as Name the other day or are refs and names different?

split harness
loud mural
split harness
loud mural
#

Ah interesting, thank you

split harness
loud mural
#

Thanks!

crisp garden
#

That said, since it's a problem that exists in the current impl, I don't think it's the highest priority, since everyone is kinda used to these issues happening

rigid adder
split harness
#

So it will be adopted people people who aren't currently using bevy_scene

split harness
#

Provided we still allow omitting () at the top level, this would also work, but its still way worse imo:

Node,
[
  Text("hello"),
  (
    Node,
    [
      Node
    ]
   )
]
white lichen
#

the [] shorthand for Children is not being included right

rigid adder
#

Broken link, sorry

#

Edited 🙂

split harness
#

The current syntax is likely to change, either a small amount or a large amount

rigid adder
split harness
#

Its worth building up your own opinions / arguments for the final round of discussions

brittle mortar
#

0

dapper sky
#

My general opinion on bsn syntax is: it's quite minor, tooling should minimise hand-written bsn anyway (and leave complex macro'd bsn inheriting from on-disk scenes a lot of the time). if it's a solid formal language without (much) ambiguity it'll be fine. If people are consistently complaining about syntax that's a situation where we're suffering from success.

#

The shed will need to be painted but anything will be better than our bikes staying sat out in the rain :p

split harness
split harness
shrewd pasture
#

Sorry if i'm haunting you with this (i'll stop if you ask),
but now that bsn! is in upstreaming, do we have more chances on getting .bsn asset format in 0.19?

native mesa
#

I certainly hope so, but timelines and open source are oil and water. We’ll see.

shrewd pasture
#

Okay, I understand, thanks
Won't bring this up again

exotic panther
split harness
split harness
# exotic panther will the #<name> syntax allow for paths? I believe the prevailing idea on how to...

#Name syntax does not allow for paths. It behaves like a variable scope in Rust, where #Name is like let name = X; and thebsn! declaration is the "scope" (ex: A curly brace {} scope like: { let name = X }) . This behavior is there to:

  1. Protect against namespace issues (ex: where some inherited bsn! might define the same #Name in a different context)
  2. Make it "easier" to reference up and down the hierarchy (ex: You can reference some #Name ten levels deep in the hierarchy all the way at the top of the scene without needing to "walk down" the hierarchy)

For paths, just use EntityPaths via"A/B/C", which are resolved post-spawn using the final Name component value

exotic panther
#

ahhh

#

thanks

#

that makes a lot more sense now

split harness
#

Today I wrapped up Template cloning and fixed queued scene spawn breakage that I introduced when sorting out immediate spawning. These are both included in the changes above

#

Next I'm going to prevent multi-inheritance in the context of :scene as that breaks down in the cached approach

#

We could consider re-introducing it by adding a second cache, which we compute on demand for an ordered list of inherited scenes. But I'm not sure we want / need that when we also have normal patching. I kind of like having : indicate that you won't pay the price of recomputing the scene

split harness
#

Just wrapped up and pushed "preventing multi-inheritance with :" and "removing [] sugar to fix ambiguity"

#

@white lichen are you looking into the #Name stuff or should I pick that up?

white lichen
#

im swamped with work atm

split harness
#

Cool Ill start working on that then

split harness
#

Alrighty I believe I've sorted out the #Name stuff. I was able to put together a test that repros the issue.

I've solved it by making every bsn! block return a EntityScope<S: Scene>, which is a Scene impl that wraps a given S and revs the entity scope. This is just a wrapper over what we already returned.

I've removed the implicit scope rev that happened as part of inheritance. Instead, each scene is responsible for deciding if / where it decides to create a new scope. This also leaves room for a future bsn_inline!{} macro that doesn't rev the scope, although that would be buggy by default so idk.

I've only written one test so far and i haven't yet determined if it works correctly with :scene, but I think it should.

unique fulcrum
split harness
#

But yeah that should be pretty straightforward

#

This is the current state of my todos once the #Name fix is wrapped up:

#

So yeah pretty much cleanup and tweaks after #Name

raw mesa
split harness
#

It lets me load scenes defined in functions as if they were stored in an asset source somewhere

raw mesa
#

Ok, I'll take a look at the branch to learn more, thanks!

rare whale
#

@split harness What's the prognosis on :Struct { ... } syntax (not sure what to call it). E.g. :Button { props }

#

(for those listening in) Our plans for feathers presume that this is possible; The BSN-compatible feathers surface will be structs, while the non-BSN API will be functions, same name, different case. So :Button for BSN, and button() for people using the regular spawning API.

#

If it turns out that this doesn't work, then we'll need to bikeshed alternate names for the BSN and non-BSN entry points.

copper dragon
rare whale
#

I'd also like to improve the non-BSN API, but that's another topic and has already been discussed elsewhere

split harness
rare whale
rare whale
# split harness Can you provide an example?

Sure, let's take the existing button function from feathers:

#[derive(Default)]
pub struct ButtonProps {
    /// Color variant for the button.
    pub variant: ButtonVariant,
    /// Rounded corners options
    pub corners: RoundedCorners,
}

ButtonVariant is already a component, it gets inserted directly.
corners is used to calculate the border radii on the node.

#

Similarly, for slider props:

/// Slider template properties, passed to [`slider`] function.
pub struct SliderProps {
    /// Slider current value
    pub value: f32,
    /// Slider minimum value
    pub min: f32,
    /// Slider maximum value
    pub max: f32,
}

This gets turned into SliderValue and SliderRange components.

#

The whole point of these "structs" is to bundle up a bunch of related functionality - with a bunch of different components working in tandem - in a way that hides the complexity of the machinery from the caller

#

If we were instead to say that the API for slider was that the user was required to provide their own SliderValue and SliderRange components at the top level, we'd be exposing that inner machinery to the user

#

Now, both button and slider have actual Button and Slider components that come from bevy_ui_widgets. But they share no properties in common with ButtonProps and SliderProps - the overlap is zero!

#

I think of

:Slider {
    value: 0.0,
    min: 0.0,
    max: 0.0,
}

As a virtual component: something that appears to be a component from the standpoint of the caller, but which is actually consists of multiple components working in partnership.

#

(We could also call them quasicomponents, welcome to bikeshed island... 🙂

#

At the risk of making my argument more complex (and therefore weaker), there's actually a long tail of optional components that could be appended to slider and other widgets like it. For example, the slider template function doesn't insert SliderStep or SliderPrecision components. I deemed that it was a less complex DX for the user to manually insert these components than it would be to make these optional parameters to SliderProps. Thus instead of having:

:Slider {
    value: 0.0,
    min: 0.0,
    max: 0.0,
    precision: Some(1),
    step: None
}

I decided it was better (and more Bevy-idiomatic) to have:

:Slider {
    value: 0.0,
    min: 0.0,
    max: 0.0,
}
SliderPrecision(1)
split harness
#

Ah just to be clear my intention for "component tied" was not a 1:1 scene:component relationship, but rather a 1:many. The primary rationale is that when someone spawns a feathers::Button scene, it produces a feathers::Button component / it is a Button from the runtime bevy data model perspective

rare whale
#

I get that you want queries after the fact to behave as if it were a real component; my feeling is that this limits the space of use cases more than it expands it

split harness
rare whale
split harness
#

If you are spawning a "feathers Button" symbol, you should be able to query for that at runtime in the ECS

rare whale
split harness
#

For clarity I'll call that symbol the FeathersButton rather than feathers::Button, which is ambiguous with bevy_ui_widgets::Button

rare whale
#

(An associated type? Meh...) [that was a reply to my own comment, not yours]

split harness
#

The scene would be tied to the FeathersButton component

#

/ that scene would be spawned (from the users' perspective) as a FeathersButton

rare whale
#

The button function is a way of bundling together a bunch of tiny components that work together to provide the user experience of a button

#

Currently, you can't query for these specifically - you can query for bevy_ui_widgets::Button but that's a wider net. You could add a specific marker component to button() but that would be a different type than ButtonProps.

split harness
rare whale
#

This is basically saying that the physical component need not have the same shape as the bundle of properties that is being passed into the template

split harness
split harness
split harness
#

Same idea as the Component <-> Template relationship, connected via GetTemplate

split harness
rare whale
split harness
#

on_spawn could be on_add if we embrace "bottom up" spawning (one of my TODO list items listed above)

rare whale
split harness
#

Alrighty I'm going back to wrapping up my "merge into main" todos 🙂

split harness
#

Just pushed the #Name fix changes. I'm now writing a test for #Name / bsn_list! interactions, which I believe has been (and is still) subtly broken. I'm adding a spawn_scene_list_immediate to make testing easier

split harness
rare whale
split harness
#

Today I've been working more on bsn_list! + #Name / testing various interactions. I've cleaned up some of the code, found and fixed an issue with unflattened inheritance (it was trying to reuse the entity scope when it actually needs its own). I believe there is only one remaining issue. Referencing a#Name defined in a later bsn_list! entity breaks down. Still trying to track that down.

split harness
split harness