#Next Generation Scenes

1 messages Β· Page 10 of 1

rare whale
#

But this will do for now

#

I'm thinking that it would be possible to do something like:

SceneListIf::new(condition, bsn_list!(...))

Where SceneListIf implements the SceneList trait.

#

This would check the condition and either forward the methods to the inner list, or ignore them

split harness
#

We came to essentially the same conclusions

rare whale
#

But yeah, an if_scene and if_scene_list would be useful.

cobalt stone
#

Is there any way we can make bsn! Do the boxing automatically? (As an outside observer just dropping into this conversation)

split harness
#

Its hard for me to justify spending time on this now when time before 0.19 is limited and I could be investing in BSN landing better there. I can try to do a quick pass to assess the general strategy, but this is the type of thing that normally takes me time to sort out whether its "on the right track" or not

rare whale
#

Note the downside of my workaround is that you pay the cost of a box even in the empty else branch

split harness
steel oak
# split harness tyty

I'm swamped as usual with plenty of other things too. I just wanted to get your feedback early before I do a bunch of work, that's all

split harness
rare whale
#

to your vscode settings πŸ™‚

split harness
split harness
rare whale
split harness
rare whale
split harness
split harness
rare whale
#

In any case, it sounds like if_scene / if_scene_list wouldn't be hard to implement - where would it live?

thick slate
split harness
thick slate
#

And merging πŸ™‚

split harness
#

Currently investigating treating ResolvedScene like a dynamic bundle to avoid archetype moves

rain gulch
#

Can I ask what the plan is for the gltf asset loader? Is it going to migrate to loading a bsn Scene, and is that planned for 0.19, or some future cycle?

echo moat
#

Both Sprite and ImageNode have an OptionTemplate with an Option property, but I don't know how to use it in bsn!, and I haven't found any examples that include it. Does anyone know how?

steel oak
#

Speaking of asset BSN, I did a bunch of work on the prerequisite patch to add ReflectConvert

raw mesa
raw mesa
rigid adder
# split harness I briefly explored this space here: https://github.com/bevyengine/bevy/pull/2353...

Has making bevy_feathers the default style for bevy_ui_widgets been considered?
The last time I spoke with @rare whale, I was told that Feathers is opinionated, to force consistent style for tooling.
It also wasn't possible back then without BSN, which allows patching hierarchies.

But maybe it's more reasonable now? I feel that the lack of any styling hurts the user experience. Every time user spawns a button, they need to style it just to make it visible.
In game development, it's very uncommon to design UI from the start. Usually, you prototype first and make it look good later.
It's possible to just use Feathers for it at the prototype phase, but that would require migrating to bevy_ui_widgets on later phases. And their API quite different from what I saw.

I was told that the plan is to develop separate styling for user widgets, but why not reuse the existing style? UI is one of the weakest points of Bevy, and I think developing separate styling will delay fixing one of its biggest inconveniences 😒

split harness
#

The gltf file is already filling the role of the "slow / dynamic" path

split harness
# rigid adder Has making `bevy_feathers` the default style for `bevy_ui_widgets` been consider...

I do see the appeal here / I've thought about this a number of times. From my perspective the big "problem" with treating feathers as the default widget "theme", intended to be used by arbitrary apps and games, is that it isn't themable. Its design benefits substantially from the assumption that people won't need to reach in and arbitrarily style everything.

If we present Feathers as "intended for abitrary use cases", it will immediately create design pressure to make every single aspect of it style-able and customizable in arbitrary ways, on a per-widget-instance basis. I don't think our current approach to styling is flexible enough for that (global Key->Value pairs, with top level root-of-widget-only BSN patching). With the current system, it would require significantly more parameterization of internal styles, exposed at the root.

I think this is largely an issue of features. Ultimately, we will have a more capable theming system. And BSN will be able to patch at arbitrary levels of the hierarchy

#

By having a separate set of "themed widgets designed for arbitrary use cases", we also buy the ability to be incremental. Less "nested" widgets like buttons could probably exist today without major limitations on styling, as style happens at the root

thick slate
split harness
#

Additionally, if the "editor theme" is the default theme for every widget, then everything will look like the bevy editor. I'm not sure thats a good thing for perceived quality

#

Ex: design fatique, many games (especially things like jam games) won't change the theme, spotting a "bevy game" gets easier, but only in the context of people too lazy to create their own style

#

I think the default widget theme should have almost no character. And perhaps be square / wireframey / canvas-ey by default

split harness
split harness
lime veldt
topaz ginkgo
buoyant venture
topaz ginkgo
buoyant venture
#

To be clear, I do appreciate when I recognize imgui or egui when used in devtools and I don't even think it's ugly but it feels weird when it's used for software intended for non devs.

rigid adder
somber rivet
#

Seems like it's a pretty hard goal to design things that are plug-and-play for people who want to iterate on an idea quickly AND avoid developing some kind of visual identity. Can you have a "no-theme" theme? I think people will start to pick bevy games out of a lineup even if you make plain looking defaults.

steel oak
somber rivet
#

Even with insanely high quality defaults (i.e., UE5's metahumans or whatever it's called) the defaults just start to look cheap / recognizable. UE5 managed to make photorealistic assets look like cheap asset flips because it's the "default" in that engine.

rigid adder
#

But I see your point on everything else you said.
I'm just a bit frustrated with the current UI usability and I feel like it will be like this for a while as a user 😒

somber rivet
#

I think it's probably better to just make things look "nice" for people who want to iterate quickly and easily customizable for people who want to do that. Prioritizing having "no visual style" seems like a near impossibility. Even the imgui stuff proves that. It's extremely plain but is still instantly recognizable if you know it.

#

I shouldn't put "no visual style" in quotes because no one actually said that I think, but it's how I read some of the goals here.

#

Don't want to put words in peoples mouths πŸ™‚

raw mesa
split harness
rare whale
#

What I have envisioned is to have a second UI toolkit, a sibling of feathers, which is also built on the foundation of bevy_ui_widgets. This second toolkit will be based around texture slicing / nine-patching, so everything is ready to go, you just have to supply artwork. There will of course be a default set of artwork, but it should be possible to easily replace the images with medieval / cyber / cartoon styles.

split harness
#

Which is what everyone should be using imo πŸ™‚

raw mesa
#

(maybe as part of gameplay systems, but that's still not editing the actual underlying data)

rare whale
#

This alternate ui toolkit, which I don't have a name for, would have different features than feathers, since it would be aimed at games rather than editors.

#

It would most likely be simpler, with fewer widgets.

split harness
rare whale
#

I am also interested in the development of "console-centric" widgets

split harness
#

(ex fn widget() -> impl Scene)

rare whale
#

Integrating the feathers style directly into the headless widgets in bevy_ui_widgets would make overriding the styles hard, because in bsn there's no way to delete a component or unregister an observer.

#

Not everyone wants focus rings, or tab navigation, or hover highlights (many modern ui designs intentionally do not have hover highlights).

split harness
rare whale
split harness
#

I think the default widget toolkit should probably default to simplistic vector rendering. That is easier for developers to tweak

#

I think a nine-patch-only widget library is a more "specific" thing

#

(also we should probably move this to #ui-dev)

rare whale
steel oak
#

Just like what happens when you drop an FBX into Unity.

raw mesa
split harness
raw mesa
#

Can we not generate the AST from a Scene? Like can we add something to the Scene trait to make this work?

steel oak
split harness
steel oak
#

I explicitly don't go straight from the LALRPOP productions to templates because I want the AST sandwiched in the middle there for the editor to use

split harness
#

I agree that arbitrary vector graphics would be nice though

raw mesa
#

Maybe I just don't understand what the BSN AST is for lol. I'll stop arguing haha

split harness
steel oak
#

In fact, the BSN AST is driven by the ECS: AST nodes are entities.

split harness
#

Whereas a ResolvedScene is

#

Which is what I think the glTF loader should output by default

steel oak
#

Ok, I'm fine with that for performance reasons

rare whale
# split harness I agree that arbitrary vector graphics would be nice though

This issue is a collection of a bunch of screenshots from various games that illustrate the kind of flexibility that we would eventually like to support: https://github.com/bevyengine/bevy/issues/22345

GitHub

This issue is meant to be an overview of a broad effort to unify the various modes for user interaction in Bevy: the current bevy_ui, with it's CSS-like box model, as well as "diagetic&quo...

steel oak
#

As long as you can go back to AST.

#

And yeah, you're right that forcing the AST in the middle would add a lot of use of Reflect where this isn't necessary.

split harness
split harness
# split harness Whereas a ResolvedScene is

This also has the benefit of being a much simpler port, as most of the types we're working with are already templates. So we just swap the World apis for the equivalent ResolvedScene apis

steel oak
#

BTW ReflectConvert is mostly ready, which is one of the blockers for asset BSN, since it's needed to convert a String to an AssetPath. I tested and it works great for that purpose.

topaz ginkgo
cobalt stone
#

@split harness I have a design for reactive-BSN worked out, but it's missing one critical flaw: Currently bsn transforms values into (value).into(). What I need is some way of having BSN do (value).my_custom_into(<foo>) where foo is something I can either add a template to, access &mut World, whatever. How feasible is this?

split harness
cobalt stone
#

I want that to turn into:

fn ui_root() -> impl Scene {
    bsn! {
        // Entity 1
        Text("Hello World")

        // Entity 2
        SignalState("Hello World")

        // Entity 3
        Observer(<Watches SignalState, and updates Text when changed>)
    }
}
#

(I forget if this syntax is entities or components on the same entity, but you get the idea)

#

There's no way to push additional templates or spawn entities based on value usage like this, though

rare whale
#

Yeah, this is my understanding too: You can't access the world during bsn construction; but you can access the world during the resolution phase, which happens when you submit the scene for spawning.

split harness
#

How would it know (from a type system perspective) that the text variable should be split up into those pieces, but another variable like let text = "some value" would just be passed in directly

cobalt stone
#

CustomInto would have a blanket impl on all types that also implement Into<T>

#

So it would be backwards compatible

split harness
cobalt stone
#

Which should hopefully compile into a no-op when it's an empty vec

split harness
#

Aka why take the dynamic path, generate a bunch of garbage, and hope the compiler optimizes this out, when we can just take the static path (which everything else is already using)

cobalt stone
#

an empty vec wouldn't allocate anything, it would basically be two u64's, but hmm

#

The other issue I'm realizing I have is that I need to track how many times custom_into() is called

split harness
#

Ah wait this is part of the "patch" infra, as into() is evaluted when resolving the scene

cobalt stone
#

Because the first time you need to do extra work

split harness
#

So its going to be dynamic anyway

cobalt stone
#

yeah it needs to be

#

Unless we do FirstCallTemplate and OtherCallTemplate as two seperate types

split harness
#

This is feeling pretty roundabout to me. Definitely feel free to explore the space though

cobalt stone
#

I'm kinda lost on the whole patch/template/resolved scene stuff tbh

#

Do you mind giving me a short overview? I read the code and your PR description, but im still lost 😭

rare whale
raw mesa
#

So far my understanding:

Template: basically a factory, produces a type given the world.
FromTemplate: a "hack" to let BSN pick the "default" template for a type - cannot be used by actual Rust code.

split harness
cobalt stone
#

I get those two parts, I think

#

I don't get the patch or resolved scene

#

But template and get template make sense to me

#

Like why does bsn make a patch?

#

It's not making templates apparently

#

When I expand the macro

split harness
# cobalt stone Like why does bsn make a patch?
  • Template: a "fancy constructor" that you feed into a World/Entity context to produce a specific type (ex: SpriteTemplate becomes a Sprite component)
  • ResolvedScene: A tree of related entity descriptions, where each entity has a collection of Templates
  • Scene: A composable, repeatable action that somehow contributes to a ResolvedScene. This is essentially a collection of write operations to be applied on top of a given ResolvedScene. This might do something like patch fields on an existing template if it already exists / create a new template if it doesn't, add a new related entity to the current root of ResolvedScene, add a new template, inherit from a scene asset / function, etc.
#

The idea being, you create a new empty ResolvedScene, then layer Scenes on top of each other to create your final ResolvedScene, which you can then spawn one or more instances of

cobalt stone
#

Ok that makes sense. And where is into() being called by the BSN macro?

#

I think what I could do is somehow every time you read the signal, BSN tells it what number of times it's been called, and then it adds patches to either create a new entity to hold the signal data, or on second call onwards, patch it to add additional relationships pointing towards the entity reading from the signal

rare whale
#

If we want to add some kind of incrementalization into BSN, the very first that has to be settled is: what parts of the template stick around, remaining connected to the spawned instance of the resolved scene?

  • In bevy_reactor, we embed these pieces into the final entity tree as hidden nodes (either special components or ghost nodes), which means that there's no persistent connection to the resolved scene, they are entirely separate once constructed
  • alternatively, you could have a permanent connection between the resolved scene and the thing that it spawns
cobalt stone
#

The way I was trying to get work, it's neither

#

The signals are seperate entities that hold state

#

And then bsn spawns observers or the signal itself does this, and every time the signal state changes it updates the watched entities

rare whale
cobalt stone
#

Some way observers work, relationships

#

I'm just having trouble figuring out how to thread all this through BSN's codegen

split harness
# cobalt stone Ok that makes sense. And where is into() being called by the BSN macro?

Consider this:

let fill: Val::Percent(100.0);

bsn! {
    Node {
        width: fill,
    }
}

Which becomes this scene:

<Node as bevy_scene::PatchFromTemplate>::patch(move |value, _context| {
    value.width = (fill).into();
})

The value is the Template being patched. Node is currently the template for itself (it doesn't need custom templating logic), so value is &mut Node.

The patch() function returns a TemplatePatch, which is a Scene. (which is why we accept an impl Scene).

The TemplatePatch Scene impl looks like this:

fn resolve(
    &self,
    context: &mut ResolveContext,
    scene: &mut ResolvedScene,
) -> Result<(), ResolveSceneError> {
    let template = scene.get_or_insert_template::<T>(context);
    (self.0)(template, context);
    Ok(())
}

Aka, it just gets the template in the scene ResolvedScene (or creates a default instance of it if it doesn't exist), then calls the function on it

cobalt stone
#

Does BSN always turn I into PatchFromTemplate? How does bsn know what to do with the input?

split harness
#

What is "the input" in this context?

cobalt stone
#

Sorry it*

#

Take my Text(variable) example

#

How does bsn know to turn that into a PatchFromTemplate?

#

There's this whole BsnEntry thing in the codegen I see

#

I can't figure out how it gets determined though

split harness
#

In BSN, any "struct / type" init syntax is semantically a patch. Therefore PatchFromTemplate is implied

#

Aka: determine the template using FromTemplate, then patch the given fields on the template

#

We also have @Text(variable) syntax, which would instead use PatchTemplate, which does the same thing, but assumes the given type is a template (aka FromTemplate is not used).

cobalt stone
#

Ahh I see

#

It's seeing a struct and assuming it's a patch

#

Then uses get template to get the template

#

Well, the type T of the template

#

And then making a PatchFromTemplate using that T

#

Which gives you access to &mut T

#

And then the patch modifies the template to set whatever fields you specify, and call into()

#

Ooook

#

Does BSN's codegen have a "leaf" type, such that I can, in the codegen, say "Hey, if this is type T, do this thing on it"

split harness
#

We do also support "full value patches", which write a whole struct on top, via the template_value(MyTemplate::new()) function (which returns a Scene that writes that whole value on top)

cobalt stone
#

Because I think the right way to do this is conetsuct additional templates in the BSN list whenever the macro sees a signal read

split harness
cobalt stone
#

Or uh, idk if templates is the right word

cobalt stone
#

Which I guess is an additional patch on the scene? Idk what the thing that takes a scene and produces a modified scene is called

split harness
#

When parsing, we have the BsnValue type:

#[derive(Debug)]
pub enum BsnValue {
    Expr(TokenStream),
    Closure(TokenStream),
    Ident(Ident),
    Lit(Lit),
    Type(BsnType),
    Tuple(BsnTuple),
    Name(Ident),
}

However it has no direct access to, say, the type information of the Ident

#

But this would leave room for having some special ~signal syntax, which adds additional contents to the output using the signal ident

cobalt stone
#

I was hoping to have it be type based

#

You call signal() and get a ReadSignal and WriteSignal

#

And then whenever read signal is used in a BSN scene, it creates th observers/whatever

#

Without you have to add explicit entities to the tree

split harness
#

and the associated jank

cobalt stone
#

I guess I could start there, and have the user add something to the BSN tree

split harness
#

No harm in experimenting

cobalt stone
#

And then we could figure out how to make it automatic in the macro

split harness
#

Just wrapped up an initial "spawn ResolvedScene as a dynamic bundle":

#

ui_raw_bundle_no_scene changing is very surprising to me, given that I haven't touched that path

#

But that is consistent across many runs both before and after

#

Probably due to the benchmarks getting compiled together / optimizations being applied?

#

The strategy is essentially "use a bump allocator and BundleScratch" (like we do in the clone infra), pass that in to ResolvedScene::spawn which passes that to ErasedTemplate::apply, which writes to the bundle scratch instead of inserting the final component directly

#

Once the scene (and its inherited scene, if it exists) has applied all of the erased templates, we insert the final dynamic bundle

#

It reuses scratch bundle space across each spawn, although it currently keeps "bumping" the allocator for each spawn

#

Resetting the cursor after the spawn is the move / should yield some perf wins

#

As a reminder, these are all tests that produce the same UI scene hierarchy through different means:

  • immediate_function_scene: a test of going through the whole "scene building" process, then spawning. this is the cost of ad-hoc scenes that don't reuse work from inherited scenes
  • immediate_loaded_scene: a test where we instantiate a bunch of inherited scene instances, where the inherited scene has already been computed / cached.
  • raw_bundle_no_scene: just spawning the raw bundle directly
split harness
#

(baseline: a few extra Mat4 wrapper components on main / current: a few extra Mat4 wrapper components with ResolvedScene-as-dynamic-bundle spawning)

#

Interestingly, raw_bundle_no_scene is stable across the changes with those extra Mat4 wrapper components, making the "weird optimizer schennanegans" more likely I think

#

I think with some elbow grease we could avoid the need to write to "scratch space" at all

#

Which would probably help close the gap further. Ex:

  1. Compute the final archetype and allocate space there before building the final component
  2. Build each component and write it directly to the archetype's table / the sparse sets
#

Aka currently we must do Template->Component (stack) -> Component (Scratch) -> Component (Archetype Table), but I think we can probably get away with Template-> Component (stack) -> Component (Archetype Table)

#

But it will require reworking some of our entity insertion code. There are some concerns that might get in the way, such as the fact that Template->Component(Stack) requires mutable World access and can perform arbitrary edits. So pre-allocating an entity before then is a bit "hairy"

#

Because if it exists in the archetype, it is queryable

#

But it wouldn't be initialized yet, which is bad / illegal

#

And I really don't want to go to jail

cobalt stone
#

After sleeping on it, I don't think signals are the way to go

#

I think it would be possible to get working, but would involve some changes to the BSN macro that idk if it makes sense

#

But also I don't know how you would do things like reactive for loops on queries...

#

For that reason, and because it makes BSN asset hot reloading easier, I feel like template diffing makes more sense (which I kinda already knew, but I wanted to try signals)

#

@split harness btw any thoughts on improving BSN ergonomics with queries/resource access? Something like automatically threading system params through nested BSN functions

cobalt stone
#

@topaz ginkgo / @steel oak did you have something that goes from entity tree -> ResolvedScene?

steel oak
cobalt stone
#

Like going from entities spawned into the world, into a BSN scene

topaz ginkgo
cobalt stone
#

Can I get a ResolvedScene out of it?

#

What is the type it generates?

topaz ginkgo
cobalt stone
#

What does your thing generate?

topaz ginkgo
#

you get a String from the serialization in that PR

cobalt stone
#

Ah :/

topaz ginkgo
#

it's basically seriliaze_ecs() -> String -> DynamicBsnLoader -> ScenePatch.resolve() -> ResolvedScene -> World

#

that's the data flow as per my understanding

#

so that's basically how you get from a scene, to a serialized scene file, then load it back in

#

but this is only because i want to generate a scene file to be loaded

cobalt stone
#

My idea to mimic React is:

  • #[reactive_bsn] attribute adds a ReactiveBsn component to the root scene, containing a copy of the pointer to the function it's defined on
  • A system that queries for ReactiveBsn components, and:
    • Generates a BSN thing (dyn Scene?) from the existing entity tree, starting at the queried entity
    • Re-runs the stored function, generating a new Scene
    • Diffs them to produce a series of changes to make to the entity tree
topaz ginkgo
#

so i think you would have to change the path of the file writer for world -> bsn for this

#

because you'd be going ECS -> String -> ScenePatch -> ResolvedScene on every diff

#

you probably want a ecs_to_resolved_scene() or something, but then how do you handle identity/key? how would you handle component and field-level diffs? ie, do you replace Transform each time a field changes, or patch the specific field - which then moves you back to the ScenePatch

#

instead of ResolvedScenes for reactivity could you maybe just store the original AST, rerun the function, diff, apply relevant patches, and then resolve the scene once done?

cobalt stone
#

No systems

topaz ginkgo
#

you would just take a snapshot of the function's last output, not of the ECS

cobalt stone
#

Right, but what if someone modified the ecs via a system?

topaz ginkgo
#

i believe React has a concept of controlled vs uncontrolled inputs (but it's been a while since i've looked into it)

#

but ultimately it would mean the "reactive" diff wins here

#

ie, for that attribute/component the source of truth is the "reactive" function

#

rather than the ECS

#

so something like:

  1. fn() produces Scene1 and spawn that into the ECS, store Scene1 as the last reactive snapshot
  2. system modifies Transform.y on a reactive entity - this is ok, sets it to 50 in the ECS
  3. Dependency change triggers rerun and fn() produces Scene2
  4. Diff Scene1 -> Scene2: reactive function changed Text from "hello" to "world"
  5. apply only that delta to ECS, the system's Transform.y modification is untouched on the resolve()
  6. Replace snapshot with Scene2 - ECS still see Transform.y at 50
#

does that make sense?

#

in this example, the AST still thinks Transform.y is whatever it was initialized as, because we haven't explicitly changed it in a reactive function

raven ginkgo
#

How do you intend to diff? Reflect over components?

topaz ginkgo
#

ie, closures in on() handlers don't impl Reflect so you might have to exclude these, or always replace

#

i think nested Vec fields would be problematic as well

rare whale
# raven ginkgo How do you intend to diff? `Reflect` over components?

That's my concern as well. In the case of React, AFAIK there is special case logic for certain DOM nodes which can't be diffed in the normal way (e.g. text inputs), but this is possible because the DOM is a fixed specification which doesn't change. This is unlike Bevy where new components are being invented constantly.

raven ginkgo
#

React provides useCallback(fn, dependencies) to effectively diff a closure by a different key (dependencies) *
(* They do this not because closures aren't comparable but because they are and can change too easily. It's also separate from the actual diff system; it's just a helper for caching fn when dependencies hasn't changed)
Though getting dependencies wrong in React is a very common footgun

Some escape hatch for non-Reflect would probably be necessary, not just for closures/on()

topaz ginkgo
#

i think you can kind of solve this with registering some new type data, ie ReflectReactive that components can register to control how they're diffed/applied

#

ie, you could have something like ReflectReactiveDiff for diffing asset contents, and not the handle ID (just an idea)

rare whale
#

At the risk of making Jasmine bored (since we've talked about this endlessly) I will describe my approach, which is to take inspiration from Solid.js rather than React.js. Solid doesn't use diffing or a VDOM, instead it uses fine-grained micro-reactions. That is, rather than regenerating an entire React component and then comparing the new version with the previous version, Solid has the ability to have reactions that modify a single DOM node, or even a single property of a DOM node. This is extremely performant - if you want to write something like a 120fps audio spectrum analyzer in TypeScript, Solid is a good choice. Because the reactions are so narrowly targeted, only updating the parts of the DOM that depend on the particular inputs, you don't have to spend time deciding what parts of the DOM haven't changed.

Applying this to Bevy, what I do is to insert special hidden entities and components (using the OwnedBy relation) into the output hierarchy that are responsible for managing reactions and mutating the tree. These hidden components are created by the bsn scene but are entirely independent of them once created, meaning that the bsn scene can be disposed afterwards.

#

That being said, my scheme is not perfect, because we still have to fit updates into the world of Bevy systems and schedules, and we still have to deal with Rust lifetimes in a way that is not applicable in JavaScript.

#

My system of reactions depends heavily on "poor man's trait queries", using component hooks to build an index of components that impl a trait so that we can iterate over all the components that have that trait.

#

And because my reactions run as part of the ECS schedule, it means that we only get to run reactions at a certain point in Update, not before or after. It's not like observers where the reactions happen immediately.

#

However, running as part of the schedule and using regular Bevy change detection does have the advantage that I don't have to worry about The Diamond Problem, which is something that any observer-based approach would have to address.

#

"The diamond problem" is where you have some entity D, that depends on inputs B and C, both of which depend on A. So the dependency graph is the shape of a diamond. The problem is that updating A causes an update on B and C, but you don't want D to update until both B and C have finished updating. This can lead to incorrect behavior because D might see a world in which B and C are inconsistent. This can cause some nasty bugs, something I have run into in my own work.

cobalt stone
topaz ginkgo
topaz ginkgo
#

It just gets synced to the ECS via Patch

karmic rock
rare whale
# topaz ginkgo I would argue that reactivity should not run in the ECS, but rather on the bsn A...

That is certainly a choice, but it's not the only choice. Also, since I am a pedant, I want to point out that what we are talking about is not really reactivity but incrementalization (that is the ability to update a hierarchy in place without rebuilding it from scratch). You can have incrementalization without reactivity, for example if you manually trigger a patch update by supplying new input parameters to the template.

#

In any case, I'm pretty happy overall with what this stuff does to the ergonomics of writing ui code. Feathers, which doesn't use a reactive framework, is pretty horrible internally, it's a mess of ECS systems and observers. In bevy_reactor I'm able to replace 100 lines of ECS systems with a 12-line reaction closure.

split harness
cobalt stone
#

hmmm, right right

#

That's probably a good starting point

split harness
rare whale
# topaz ginkgo I would argue that reactivity should not run in the ECS, but rather on the bsn A...

There's a third option I would like to point out, which is for bsn to generate two trees: one containing the dynamic update plumbing, and one which is the actual visible output. That is, instead of embedding the dynamic stuff in hidden nodes in the output tree, instead put it in its own separate tree which points to the output. This means that spawning a scene would have to return some kind of handle which points to the dynamic nodes.

#

This idea has some challenges, but it means that you wouldn't have to resort to trickery to ensure that the dynamic plumbing remains invisible.

#

Popping up a few levels, I want to talk about the current state of things at the ecosystem level. We have 4 or 5 different reactive frameworks that I know of:

  • My stuff (bevy_reactor, quill, and thorium)
  • @cobalt stone 's bevy_dioxus work, plus more recent experiments
  • Avi's jonmo and haalka (There are several Avis here, not sure which one to link to)
  • @queen oak's thing, forget what it's called
  • @timid leaf 's cobweb_ui
    All of these have interesting ideas and none should be dismissed out of hand; unfortunately, no one really has the time to become fluent in each of these frameworks to the degree needed to make a useful comparison, because they are all radically different in approach.
#

However, having looked briefly at all of them, I would say that what they all have in common is that they more or less diverge from the idiomatic "bevy experience" - to what extent that is important depends on how you feel about the parts of bevy that they are diverging from.

#

Also, there has not been much experimentation in compile-time data dependency analysis of the kind seen in frameworks like Svelte.js. Most of what we see is dependency graphs being built at runtime.

queen oak
#

yeah if we're talking about reactivity i have a pretty controversial take on it, based around using async to make it explicit and procedural, you .await button presses, and well anything, and using tools like select!{}, breaks and labels you can build complex machinery in a few lines of code that expresses some pretty complex behavior

#

but it very much doesn't feel like bevy, it feels like a strange cross-breed of bevy and async

#

but it doesn't use the ecs's systems to store ui functionality, ( while it does store part of the state in the ecs itself, the other part just exists in the closures inline, like boolean flags, inline vecs, etc ) , it builds the ui state procedurally, it's like immediate mode in some ways, in terms of it's explicit procedural nature, but because it's using .await it doesn't actually incur the standard costs of immediate mode in terms of cpu consumption

split harness
# split harness

Reusing space in the bump allocator by resetting after each bundle insert does save a bit of time:

#

(baseline is still relative to main)

#

It required writing a new, less safe BundleScratch impl that ignores the lifetime. This is due to how collections with lifetimes work

#

The borrow checker can't know if there are any references currently present in the collection, meaning theres no way to "clear" the idea that the collection is holding borrows. This blocked the ability to reset the bump allocator

#

We either need to move to allocating new bundle scratch space on each insert, or erase the bump allocator lifetime

#

Very hard to ensure this is safe globally. But in this context where the accesses are "local", we can make it pretty safe validate the code easily without much fear of future developers invalidating an assumption

split harness
split harness
#

I've also inverted the BSN spawn order to be "bottom up" instead of "top down". This means that when the root component is added, the entire scene is available.

#

Ex: it makes this possible:

bsn!{
  :"player.gltf#Scene0"
  Player { held_item: #Item }
  Children [
   (#Item Item),
  ]
}

impl Player {
  fn on_add(add: On<Add, Self>, players: Query<&Self>, items: Query<&Item>) {
     let player = players.get(add.entity).unwrap();
     let item = items.get(player.held_item).unwrap();
     // the whole inherited glTF scene is also available here
  }
}
silk lava
#

Does that make the reverse not possible? (child component with parent reference) also, with respect to hooks/observers are the events now in reverse order, if so would there be some way to cache them and emit them in correct order? maybe it doesnt matter though.

split harness
#

The alternative is to add another set of bottom-up SceneReady events, but spawn top-down

#

The downside there is incurring the overhead of triggering a bunch of additional hooks / observers

#

/ adding more complexity to the system

silk lava
split harness
thick slate
silk lava
split harness
#

I think the choice isn't really "should we do this" but rather how:

  1. Leave spawn order (and therefore Add events) as-is: They are still top down. We add another set of Ready events, triggered bottom up.
  • Pros: not a breaking change, people can choose top-down or bottom-up
  • Cons: For a scene with N entities, we trigger an additional N entity events. We have one more event that people need to learn about.
  1. Make spawning bottom-up. This would ultimately be both BSN and bundle-spawning.
  • Pros: Simpler (no new events) and more efficient (we don't trigger more per-entity lifecycle events)
  • Cons: The "component reaches into its parent on Add and reads a parent Component" pattern is no longer supported.
split harness
slender lion
silk lava
#
bsn!{
  :"player.gltf#Scene0"
  #Player
  Player { held_item: #Item }
  Children [
   (#Item Item, EntHolder(#Player)),
  ]
}

#[derive(Component)]
struct Item;
impl Item {
  fn on_add(...) {
    let entity = ...; // Parent entity from ChildOf
    world.component::<Player>(entity); // Check that Player Component exists on the Parent
  }
}

Does this work? (psuedo-code of course)
P.S: As I posted kek, I should just wait

thick slate
#

I would prefer 2. I think it's the better default. You can always play games with deferred setup of children values that need to do that

split harness
silk lava
#

The EntHolder would still work though? I assume all the Entities are pre-alloced or something like that?

split harness
silk lava
#

2 is similiar in logic, I think, to tail recursion an seems to be able(?) to take advantage of the same perf optimization, so that should probably be the path.

split harness
#

That (prior art) combined with the flexibilty to do both, and being non-breaking for the current ecosystem is a pretty compelling argument to do both and just eat the overhead

silk lava
#

You can also do a second event with 2 right? if that is needed.

#

When the root component is added, the scene is ready and you send the ready event down the scene.

slender lion
#

I think the Con of "component reaches into its parent on Add and reads a parent Component" being unsupported has an additional twist in that it "is only supported outside of scene usage", which is something people run into today: They write on_add as if the component was the last component being inserted, which breaks when introducing scene usage

silk lava
#

I think the choice may be whether we want to be able to have access to parent data or child data in the 'default' hook path, where 'default' means supported by both scenes and normal spawning.

split harness
#

Consistency would make sense to me. If we go the Ready route, we'd almost certainly want to fire that as part of normal bundle spawning too

#

Although that does complicate things, as the bundle-spawn Ready would trigger early in the context of BSN, which inserts one entity at at time

#

So maybe it should be a scene-only thing

#

I guess we could have a spawn_no_ready variant

silk lava
#

Hmm, what if you split hooks/observers, no external access in hooks, if you need external access you use observers 😈
Then spawn order doesn't matter for hooks, and then queue/cache the events somehow, so that the entities will be fully spawned by the time observers are run. /j

queen oak
#

what about a different second class of hook intended for relationship based behavior instead of entity, that waits until the whole scene has loaded and then runs the hook on every entity with every component on the scene

#

on_scene_ready

#

this would also work in normal spawning

tight raft
#

where can i find more bsn example? (it's to make bsn example ironically)

thick slate
steel oak
thick slate
pale edge
#

will next gen scenes and new ui system be available for 0.19 ?

#

are .bsn files hot reloadable ?

dapper sky
#

pcwalton already has a PR up for the asset format and everyone's after design tooling based on BSN, so it's not like it's a loose promise.

topaz ginkgo
#

And there’s some other tweaks and improvements I’d like to make for the ergonomics the dynamic scene stuff (ie, exposing an API for jackdaw to use)

dapper sky
#

Yeah you two have been knocking it out of the park

dapper sky
#

aye, though more things may end up being added on (i.e. reactivity in the bsn macro)

south lagoon
thick slate
pale edge
#

so when is bevy visual editor going to be started ?

#

like in 0.21 ? 0.23 ?

lime veldt
#

if nobody can stop jackdaw it started a month-ish ago

#

if the 1st party editor is something else it likely will pick up learnings from that, so it either way is already ongoing

queen oak
#

oof so we can't use the #Name entity within expressions

#

Is this blocked on some issue?

#

I think i see why

#

Hmm, how are we intended to use tuples of values inside our components?

#

Doing MyComponent((#Name1, #Name2)) does not work

split harness
thick slate
split harness
#

Not a particularly hard problem, just work that hasn't been done yet

queen oak
#

I c I c, nice thing is you can work around it because the default init stuff

#

So I have a component thats just 16 entities

#

And the init patching makes it all work out

#

Now, I am getting panics on using names inside entities, is this a ordering issue?

#

The entities name doesn't exist yet because the child needs to be spawned first?

#

It would be nice if we spawned the associated entity on first access of the #Name and then inserted components on it afterwards when we encounter it?

#

Hmm, the example on the original PR actually shows exactly the behavior i'm panicking on rn

#

One second will link

#
#[derive(Component, GetTemplate)]
struct UiRoot(Entity);

#[derive(Component, GetTemplate)]
struct CurrentButton(Entity);

bsn! {
    #Root
    CurrentButton(#MyButton)
    Children [
        (
          #MyButton,
          UiRoot(#Root)
        )
    ]
#

I'm getting a panic in ScopedEntities::get when I try to run something ~ equivalent

#

@split harness I actually think this is a straight up bug?

queen oak
#

This bug didn't exist in the pr after the feathers -> bsn port

#

So its somewhere between then and now that it slipped in

cobalt stone
#

Experimenting with react-in-bevy, I mostly have something working

#

Hard part is I haven't figured out a good equivalent to useState yet

#

There's no good way to say "determine observer system params based on captured closure data"

queen oak
#

I have my async ui stuff working with bsn ( and older version of it to avoid the panic bug with names ) and it is great

#

Bsn having the names allowing me to reference entities across the hierarchy makes the whole thing so much better

steel oak
#

I so want to nuke my project's glXF from orbit

#

Seeing complex Hanabi VFX built in BSN will be lovely

queen oak
queen oak
queen oak
#

we'll see how much success i have

#

then we can resolve the entities ahead of time for each actual template resolution, and then pass them through to each closure during that, and evaluate the closure

split harness
split harness
#

Sadly this does complicate the BSN impl when it comes to dynamic bundle spawning in the context of inheritance / I'll need to do a bit of work.

#

(coupling the lifecycle change to dynamic bundles was no coincidence ... I saw that flipping the order would work nicely with my current approach. I knew I wanted to consider flipping the order anyway, so I took my chance)

split harness
#

Alrighty I've wrapped up moving back to "top down" spawn order in the context of dynamic bundles. Weirdly, that performs worse than "bottom up". Perhaps something to do with needing to keep spawn context from "parent" entities on the stack?

split harness
#

Note that raw_bundle_no_scene also regressed significantly (despite not being touched). Might be weird micro optimization BS that we wouldn't actually see in a larger context?

#

Very consistently different though. Not much "noise" in these benchmarks between runs (both before and after)

#

I'm now adding empty RelationshipTarget components to the dynamic bundle for "parent entities". This is pre-allocated with space to hold each related entity. This also avoids archetype moves in the parent after spawning the first related entity.

Sadly this isn't moving the needle on the benchmark, which is even more surprising than the regression introduced by moving back to top-down-spawning

#

Anyway, I think this is good enough for 0.19 / the big expenses have been mitigated

#

I'll wrap up quality work tomorrow morning / get this out

queen oak
#

Passing the scoped entity to a function is trivial

#

But while we can convert the expressions we care about to FnMut closures, there's no mechanism to modify their scoped entities to real entities before running the closure / passing the real value, i think

split harness
queen oak
#

I want to be able to reference #Name as an entity inside my async closure, cause it would make it so neat

#

I could make an exception inside the bsn macro itself to make it work tho, for that specific case, in theory

#

And implement template manually

#

I think?

split harness
queen oak
#

Would something like a special exception for a piece of syntax that only shows up in a third party crate be supported by the bsn macro? Similar to how we Derive Serialize in 3rd party crates, or is that out of scope

#

I guess I could always fork bsn syntax and make my own thats 1-1 with my exception for #Name in async stuff

#

That's not awful, its a little bad but its whatever

split harness
queen oak
#

Yeah I was thinking like just, direct support, as in the same way you carve out an exception for on

split harness
#

We could definitely try to build a general purpose feature that 3rd party crates can use

queen oak
#

πŸ₯Ί

#

It would be a significant ux improvement

#

Rn I have an entity that is called Entities with 16 fields that are all entity

#

And you have to match up that with the closure which takes in entities as parameters lmao

queen oak
#

@split harness I have figured out a minimal reproduction for the regression!

This didn't used to crash but now it does

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
        ))
        .add_systems(Startup, |world: &mut World| -> Result {       world.spawn_scene(demo_root())?;
            Ok(())
        })
        .run();
}

#[derive(Component, FromTemplate)]
struct MyEntity(Entity);

fn demo_root() -> impl Scene {
    bsn! {
        MyEntity
    }
}
dapper sky
#

Hmm, shouldn't that crash just because it's not been given a reference? Is that the same error as the name error?

queen oak
#

It didn't used to crash!

#

Maybe it is intended to, idk

queen oak
#
fn demo_root() -> impl Scene {
    bsn! {
        Node {
            width: percent(100),
            height: percent(100),
            align_items: AlignItems::Center,
            justify_content: JustifyContent::Center,
        }
        ThemeBackgroundColor(tokens::WINDOW_BG)
        Children[(
            Node {
                align_items: AlignItems::Center,
                justify_content: JustifyContent::Center,
            }
            Children [
                (
                    #Minus button(ButtonProps::default())
                    Children [ (Text::new("-1") ThemedText) ]
                ),
                (
                    #Counter Text::new("0") ThemedText
                    Node {
                        margin: UiRect::horizontal(px(10.0)),
                    }
                ),
                (
                    #Plus button(ButtonProps::default())
                    Children [ (Text::new("+1") ThemedText) ]
                )
            ]
        )]
        async |world: AsyncWorld| {
            let text_system = world.system_state::<Query<&mut Text>>();
            let mut number = 0;
            loop {
                futures::select! {
                    _ = world.on::<Activate, ()>(#Minus).await.fuse() => { number -= 1 }
                    _ = world.on::<Activate, ()>(#Plus).await.fuse() => { number += 1 }
                }
                text_system.bridge(AsyncUi, |mut query| {
                    query.get_mut(#Counter).unwrap().0 = format!("{}", number);
                }).await.unwrap();
            }
        }
    }
}
#

By modifying bsn macro a little I can automatically spawn my async closures, pass AsyncWorld to them, and have #Name work as entity values within it!

#

( this is the feathers button example rewritten )

split harness
#

I think we should add an EntityReference::None variant, which returns an "entity doesn't exist" template error

#

Or alternatively we could squeeze in the Path variant again, but I think the EntityPath impl is controversial enough / important enough to not rush it in

queen oak
#

Yeah, with my forked macro i'm less concerned about having the ability to stuff arbitrary potentially wrong entities into a component

split harness
queen oak
#

Yep

split harness
#

Good to know that works

queen oak
#

Its awesome, and we could even make it generic

#

Here me out

#

What if we allowed people to inline add an async block, and whatever parameters they put in must be Resource + Clone

#

And we spawn the async task and detach it

white lichen
#

manually adding None variants feels like a code smell

#

rust has niching

split harness
# white lichen why not Option

A top-level wrapper likeOption<EntityReference>(ex: FromTemplate<Template = Option<EntityReference>> for Entity) doesn't feel great to me, as it means people assigning an EntityReference to an Entity template need to wrap it in Some. And I personally consider EntityReference::None to be cleaner than EntityReference::ScopedEntityIndex(Option<ScopedEntityIndex>), as None isn't really tied to a particular variant of EntityReference

#

BSN does have implicit into(), but that means we lose out on inner into() for things like ScopedEntityIndex and a future EntityPath

split harness
split harness
# white lichen why not Option

Also aesthetically flatter == better. Some(EntityReference::Path(path)) is less fun to read and type than EntityReference::Path(path)

white lichen
#

okay

pale edge
#

When will there be a vscode extension for bsn ?

rare whale
bitter patrol
#

isn't rustanalyzer already providing error handling and autocomplete?

frosty shell
#

for the .bsn file format probably

split harness
#

We'll want the extension to add bsn formatting capabilities as well

#

(both for .bsn files and for bsn! macros)

buoyant venture
#

I don't think it's the right time to discuss extensions but when we do we should be very careful to not make it vscode specific

bitter patrol
#

I was thinking about the macro, yeah makes sense

split harness
frosty shell
steel oak
#

Though honestly it might be better to rewrite it in Logos for this purpose?

buoyant venture
#

Personally, I use neovim and I'll want treesitter support. My point was mostly that any kind of tooling should be built as editor agnostic as possible. Like, if people want LSP like feature it should be implemented as an LSP and not something bespoke that is vscode only.

rare whale
#

@split harness I'm attempting to revive bevy_reactor with the latest main, and I got all the examples working except for the propedit one, which fails with the following:

thread 'main' (57002876) panicked at /Users/talin/.cargo/git/checkouts/bevy-50d7e162b728c6c6/250a7c0/crates/bevy_scene/src/spawn.rs:467:14:
Requested resource bevy_scene::spawn::QueuedScenes does not exist in the `World`.
                Did you forget to add it using `app.insert_resource` / `app.init_resource`?
                Resources are also implicitly added via `app.add_message`,
                and can be added by plugins.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_scene::spawn::spawn_queued`!
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
thick slate
rare whale
split harness
split harness
rare whale
sharp crystal
#

I got the same error when spawning a scene. I opened an issue for it.

#

Issue #23731

split harness
rare whale
split harness
sharp crystal
split harness
thick slate
sharp crystal
#

This is where the hokey pokey happens

raw mesa
# thick slate Oh I bet this is a hokey-pokey problem

I wonder if we can have an explicit API here like remove_hokey_pokey (bike shed needed) which leaves behind a tombstone that accessing a resource would give us a nice error. I ran into the same issue trying to hokey-pokey the Schedules resource. If the error said "hokey pokey" it would have been clearer what was going on

#

Then again maybe this situation is too niche lol

thick slate
#

I've run into this problem before with one-shot systems πŸ™‚

#

The ideal thing would be if that tombstone isn't just an error tool, but instead creates a duplicate copy that is then reconciled

rare whale
#

The main thing that is different about this particular example (the one where I am getting the panic) is that it's calling queue_spawn_scene_related based on a traversal of reflected types (it's a reflection-based property editor). The other examples work fine.

split harness
#

Really depends on the context. Simple queues might be fine, but even that depends on the context

thick slate
split harness
buoyant venture
#

Am I missing something or is not currently possible to spawn bsn as a child using EntityCommands? As in, doing something like commands.entity(e).with_children(|c| c.spawn(bsn! {})); I'm not sure if it's an oversight or a limitation of bsn.

#

I was expecting to see a spawn_scene in this context but it doesn't exist

rare whale
buoyant venture
#

For context, I was doing something where I'm despawning all the children of a node then I want to spawn a new scene under that node. Before bsn I was spawning things directly and now while trying to migrate to bsn I kept this pattern and that's how I ended up here

rare whale
buoyant venture
#

Oh, I tried searching for it and didn't found it so I assumed you were suggesting a name for the api πŸ™ƒ. I must have made a typo in my search

buoyant venture
thick slate
split harness
thick slate
#

Ohohoho project management toy

queen oak
#

Last I checked spawning happens one by one*

#

Maybe thats going to change?

#

But I think the mutex thing might be best benchmarked first as an easy path before we get assets as entities

split harness
split harness
thick slate
split harness
#

The type state changes to BundleWriter were really nice

split harness
thick slate
split harness
#

@queen oak

queen oak
#

Awesome! Will do so!

thick slate
#

I have left review comments but I fear I'm growing predictable πŸ™‚

I do quite like this: I think this is a Good Enough solution for now.

raw mesa
#

Maybe because this would require a &mut ResolvedScene (or just Scene) too which is also cursed

split harness
#

For awhile it did actually take &mut

#

(Back when there was no such thing as cached inheritance)

#

One restriction I am currently looking into removing is requiring clone everywhere. Ex:

let mesh = meshes.add(SOME_MESH);
bsn! {
  Mesh3d({mesh.clone()})
}

I've managed to (largely) remove the "repeatability" constraint on Scene (ex: change Scene::resolve(&self, ...) to Scene::resolve(self, ...).

I have this compiling:

let mesh = meshes.add(SOME_MESH);
bsn! {
  Mesh3d(mesh)
}

I know a number of people have hit this / like me have been annoyed by it ( @rare whale, @magic belfry )

The only major tradeoff I'm seeing here is a loss of impl Scene for Box<dyn Scene>, which is a pretty massive loss

#

If we can sort out a way to support that, I think we're in the clear

#

The other major tradeoff is that it does mean function-style scenes can only be "applied" once, so you need to call them again if you want to re-apply directly. In practice we're doing this anyway, and we can still cache the ResolvedScene for inherited function scenes.

The repeatability (now that we have inheritance caching implemented) is really just a theoretical performance win that we won't use in practice.

#

I wonder if we can do whatever hacks Rust does to make Box<dyn FnOnce> work

#

As Scene is really behaving like FnOnce now

split harness
#

Eh wait actually this case would perhaps be fine, as we only clone when we write on top, and if we're writing on top, its a different asset πŸ™‚

rare whale
#

@split harness So, I was going to test out the QueuedScenes fix with bevy_reactor, but unfortunately it no longer compiles:

`()` is not a `Component`
the trait `bevy::prelude::Component` is not implemented for `()`
split harness
rare whale
rare whale
topaz ginkgo
#

(Directly related to my proposed asset catalog) πŸ™‚

split harness
# topaz ginkgo How would this work with shared assets? Ie, say i have a Scene 1 that needs asse...

Not in this form. This is just a temporary / easy win. This comment has the longer term plan: https://github.com/bevyengine/bevy/issues/23822#issuecomment-4263077966

GitHub

What problem does this solve or what need does it fill? You will still need to pass a handle from outside or add it manually in template(...). pub fn demo(atlas: Handle<TextureAtlasLayout>) -...

#

Which could ultimately support cross-scene asset sharing

topaz ginkgo
steel oak
split harness
split harness
split harness
#

Came up with some quick "spawn scene" system ergonomics helpers:

#
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, scene.spawn())
        .run();
}

fn scene() -> impl SceneList {
    bsn_list![Camera2d, ui()]
}
#

Given that the pattern is very often just "spawn this scene on startup". This will also play nicely with state transitions I think

#

Although maybe not, given that we often want to despawn on exit

rare whale
#

@split harness I've been wanting to discuss my suggestion about using the markdown page break token (---) as a divider between entities in BSN. However, I have a difficulty in that the advantages of my approach are more obvious in large code examples with lots of nesting levels - too large to be posted in a discord message (with my permission level anyway).

However, you can sort of do this today: within a Children [] block, you don't actually need the parentheses, and you can put the comma on its own line:

Children [
    ComponentA
    ComponentB
    ComponentC
    ,
    ComponentA
    ComponentB
    ComponentC
]

Although this is syntactically valid, this placement of the comma is likely to rub many users the wrong way. So my idea was to pick a different token as a separator, one which wouldn't trigger this kind of punctuational uncanny valley. I don't really care what that separator is, I just picked some pre-existing token that is most often placed on a line by itself, which happened to be the markdown page break.

#

At first glance it seems that you might be trading horizontal space for vertical: that is, you are making the lines shorter, on average, with fewer nesting levels, at the cost of making the code somewhat taller; however when you factor in the extra lines needed for the ending paren (which often occurs on a line by itself), the result is a wash.

silk lava
rare whale
#

I know that you weren't in favor of my original proposal, in fact I think yours was the single negative vote on my post. (I don't mean this as a dismissal).

silk lava
#

I do agree though, that for the easiest view of separation, having a new line with a token, whatever it may be, will provide that the best.

#

It would give you rough ideas about a file at a glance too.
Horizontal space = hierarchy/relation complexity
Vertical space = entity/data complexity

split harness
rare whale
#

Trivia: the same token that markdown uses for page breaks is used in yaml as a document separator

silk lava
#

You could also represent this as app.add_systems(Startup, scene.pipe(scene_spawner)) which reuses piping, instead of adding a bespoke method to any system that happens to return a scene?
Nvm I don't think the piping would support that generic function.

split harness
# rare whale I know that you weren't in favor of my original proposal, in fact I think yours ...

I'm very in favor of the goal to reduce indentation / improve terseness + legibility, and I think the --- proposal is decent at accomplishing this. I'd like to table the syntax convesation until after 0.20 is out, as there are a number of (potentially) related issues to solve:

  1. #Name syntax ... inner (#Name, OTHER_COMPONENTS) vs outer #Name(OTHER_COMPONENTS)
  2. Top level "shared scene entity" syntax
  3. Top level "flattened scene" syntax (which might just be (2))
  4. Inline asset syntax (which might just be (2))
  5. Child scene ergonomics ([], my <> proposal, etc)
#

My only real issue with --- is that it feels like syntax overkill. Feels too much like "drawing a picture in ASCII" to me, which violates my aesthetic sensibilities.

#

But I do think it does a better job than , in this context

silk lava
#

for 2 I do hope whatever it ends up being, doesn't only support Assets. (Even if that is the main use case for the moment πŸ™ )

split harness
thick slate
#

@buoyant venture

buoyant venture
#

For some more context, this is what I'm working on. I was annoyed at the default ui of CKAN which is a mod manager for KSP so I decided it would be a good opportunity to experiment with bsn and feathers to make a wrapper for CKAN

#

Each row is a child of a container inside the table

#

I have my whole hierarchy working with bsn, except the spawning rows part because they are generated at runtime and I can't figure out how to do it inside bsn

#

I will say, other than this minor annoyance it's been pretty nice so far

raw mesa
#

I guess that might not work if your parent entity is not the root of the BSN actually...

buoyant venture
raw mesa
#

Just like manually spawning each individual child

buoyant venture
#

Ah, well, yeah, I can do this without bsn, the issue is doing it with bsn. I had it working kinda like that before trying to use bsn

#

I have essentially this thing:

        let mut ui_root = commands.entity(*ui_root);
        ui_root.despawn_children();

        let rows = result.list.clone();

        ui_root.queue_spawn_related_scenes::<Children>(bsn_list! {(
            Node {
                flex_direction: FlexDirection::Column,
                flex_grow: 1.,
            }
            Children[
                // Header
                (
                    Node {
                        height: Val::Px(LINE_HEIGHT),
                        width: Val::Percent(100.),
                    }
                    :table_header
                ),
                // Row + Scroll
                (
                    Node {
                        flex_grow: 1.,
                        flex_direction: FlexDirection::Row,
                        min_height: Val::Px(0.),
                    }
                    Children[
                        (
                            ContentPane::new(rows.clone())
                            Node {
                                flex_grow: 1.,
                                height: Val::Percent(100.),
                                flex_direction: FlexDirection::Column,
                                overflow: Overflow::scroll_y(),
                            }
                        ),
                        :scrollbar_track
                    ]
                )
            ]
        )});
#

And the only way to make it work is with an On<Add, ContentPane> observer

#

I was expecting to be able to do some kind of SpawnIter(rows). Where rows would be a Vec<Scene>

#

Or something like that

raw mesa
#

So don't have BSN do the spawn. Spawn all your rows, and then just have BSN glue it together

#

That might also be non-trivial though lol

buoyant venture
#

Also, in the process of working on this I found an issue with how tuple struct are handled. I have no idea if it's already a known limitation though.

struct Foo(f32);
let number = 42.0;
// This leads to a compilation error on the .clone() saying it's expecting a parenthesis
bsn!( Foo(number.clone()) )
#

Of course that specific example is pointless, but it's just a minimal example

buoyant venture
# raw mesa That might also be non-trivial though lol

I think my observer trick is simpler because I can use bsn almost everywhere except for the glue part. It's not that hard to setup either. Anyway, the reason I was talking about it isn't because I couldn't find a solution and more because I was surprised there didn't appear to be a way to do it while inside bsn. I think there's probably a way to make something relatively generic that could work here

native mesa
#

I think I had a way to do this with icbinbsn, and the template structures are not too different at this point

#

I do think a way to run arbitrary code that produces a scene list inside of a bsn macro seems quite useful

split harness
split harness
#

Currently for Scene repeatability reasons it requires the collect() at the end. But I believe I'm at the point now where my "run once Scene" impl is viable, which (among other things) should allow iterators directly (provided they are owning iterators)

#

We dont yet have sugar for iterators / conditionals in BSN, but thats pretty high on my list in 0.20

split harness
split harness
#

I'm working on "hoisting out" expressions from the patch closures, which further reduces the constraints (ex: Send + Sync + 'static is no longer required for inputs, ex in the feathers label widget).

/// Workaround 1 (used in Feathers)
fn label(text: impl Into<String>) -> impl Scene {
    let text = Text::new(text.into());
    bsn! {
        template_value(text)
    }
}
/// Workaround 2
fn label(text: impl Into<String> + Send + Sync + 'static) -> impl Scene {
    bsn! {
        Text(text)
    }
}

/// This now works! No workaround necessary!
fn label(text: impl Into<String>) -> impl Scene {
    bsn! {
        Text(text)
    }
}
#

I have an initial working impl. Just need to do some cleanup

#

@rare whale you will probably like this πŸ™‚

split harness
#

In the interim, you can resolve this category of problem using rust-expression syntax: Foo({number.clone()})

split harness
rare whale
buoyant venture
split harness
#

Ex: FnMut vs FnOnce

rare whale
#

@split harness Something else: we've discussed the boilerplate generated by the need to impl both Scene and Template in all of bevy_reactor's primitives. This causes a lot of repetition - the same code, with minor variations, appears about 8 or 9 times. However, all of that boilerplate is inside bevy_reactor's core, and doesn't affect the DX of callers, so it's not a huge deal. Still, it would be nice to have a more concise way of doing those sorts of things.

split harness
queen oak
#

Are we going to try to get the #Name in arbitrary rust expressions working before 0.19, or is that a 0.20 aim

rare whale
#

It might be worth experimenting to see if we can pull them out of the closure

#

It's a trade off: what resources are we willing to spend for branches not taken?

split harness
split harness
#

Ok I've wrapped up hoisting. Once we merge "run once scenes" I'll put it out, as it relies on that to actually be useful

split harness
#

In practice I'm not sure if this is a problem. If it is, we can always just add a variant to ScenePatch

rigid adder
#

With the introduction of BSN, do we still need BundleEffect? It was used to spawn hierarchies as part of a bundle, but that’s handled by scenes now, right?
Or are there any other use cases for BundleEffect?

split harness
# rigid adder With the introduction of BSN, do we still need `BundleEffect`? It was used to sp...

I've gone back and forth on this. I think BSN reduces the need for bundle effect / it does solve the same problem, and it would certainly clean things up a bit to remove it (including making nested dynamic bundles much easier to implement). However I suspect a lot of people would still miss the ability to define children![] in their bundles. I think it really depends on how prolific BSN usage becomes / if anyone still uses children![] in practice

split harness
#

(and the plan is to deprecate observe())

#

In other words: BundleEffectexists solely to enable spawning related hierarchies in Bundle, it was largely created to fill the void of bsn!, I'd love to deprecate it, but I'm worried it would be disruptive to peoples' existing workflows

thick slate
#

It will suck more to do later

#

Probably in 0.20

slender lion
#

is it expected that the majority of spawn/etc usage, even for single-level tuples/small bundles, becomes bsn! in the future?

#

for ex:

commands.spawn((A, B, C))

vs

commands.spawn_scene(bsn! { A B C })
split harness
split harness
slender lion
#

yeah, makes sense. It feels more on the viral side of things at the moment, and will be interesting to see adoption through 0.19's cycle

rigid adder
thick slate
split harness
#

just nice

split harness
#

yup πŸ™‚

#

I think my vote is lets defer the decision until 0.21 when we've improved BSN a bit and people have started to use it in practice

split harness
#

@thick slate I also resolved your comment

#

Ah wait back to draft I forgot about the internals cleanup I wanted to do πŸ™‚

split harness
#

Ok the cleanup is done. Now it is ready for review / merging πŸ™‚

#

@thick slate

#

BSN example ports are starting to look mighty tempting once everything in flight merges πŸ™‚

copper dragon
#

RA seems to have a seizure every startup
however the error clears on a rerun.

split harness
#

Presumably you are consuming Bevy from the bevy crate?

#

And not piecemeal crates?

copper dragon
copper dragon
#

im in a top level bin folder on main for the launcher work

split harness
#

And it is looking for bevy_scene, implying that RA is somehow not finding the bevy crate in the current cargo.toml file when it hits that error

copper dragon
#

Perhaps i need to add required deps to the bin definition?

split harness
split harness
copper dragon
split harness
#

I suspect that this is a product of the[[bin]] pattern / this is somehow throwing off BevyManifest

copper dragon
#

possibly

#

its something that hasnt been used before within the tree

split harness
#

Opening VScode directly on the bevy_launcher folder and running it with cargo run might be enough to test this

copper dragon
split harness
copper dragon
split harness
#

This is a "macro resolution type path hack" issue, not a "rust import issue"

#

More specifically it is likely a "Rust Analyzer macro resolution type path hack issue"

#

When the macro is being expanded by Rust Analyzer, it thinks it is operating in an environment that does not have access to the bevy crate / it assumes it must rely on bevy_scene

#

And actually, I believe this is in-line with how all the bevy examples run. Notice the #[dev-dependencies] imports of crates like bevy_ecs and bevy_scene, despite the fact that the examples all use bevy

#

I believe this was a tradeoff we made to make the BevyManifest logic work as expected everywhere else, with the Bevy project itself eating the "boilerplate" of providing manual bevy_x imports to make the examples work

#

Before you blame us for this nonsense, instead blame Rust's proc_macro implementation for not giving us a $crate equivalent.

copper dragon
split harness
copper dragon
#

ahh ok

split harness
#

I believe that would largely be a no-op from a compilation perspective, and it would probably trick RA

#

Although this feels like an RA bug, provided that cargo run --bin bevy_launcher works as expected

copper dragon
split harness
copper dragon
#

mhm

split harness
#

Then thats unrelated. If the cargo run --bin bevy_launcher builds, then this is an RA issue

#

And if this is reproducible for other people, I'd be cool with adding a bevy_scene import to the launcher cargo.toml to fix mitigate it

#

Just add a comment on it explaining the situation. And ideally file a bug on the RA repo explaining the situation / providing a link to the offending code

#

(and link to the bug in the comment)

copper dragon
split harness
copper dragon
split harness
#

I probably write something like 100 GB / day from bevy builds (maybe more)

copper dragon
#

how would i get the contents of a resource deep into a bsn! ?

#

without passing it through every layer

#

env!("CARGO_PKG_VERSION");
trying to use this to make a text node show the launcher version but ran into issue of not being able to have the macro in it so i tried storing in a resource but now i dont see how to access that resource

#

nvm

copper dragon
tawdry owl
#

Is scene inheritance with generics (:scene::<T>) not supported?

south lagoon
dapper sky
#

Not in 0.19 no

#

but there should be example usage of bsn

split harness
#

This would be very easy to fix yourself if you're feeling inclined. Just a path parsing thing

split harness
tight raft
#

Can i (if yes, how can i) use Sprite in bsn? Can you show me any example?

tawdry owl
tight raft
tawdry owl
tight raft
#

Oh i thought I should assign Some(vec2(100.0, 100.0)) to custom_size for some reason. Thanks a lot!

tawdry owl
split harness
queen oak
# split harness This needs another review / is blocking my "expression hoisting" PR: https://git...

given how close we are to release i actually would rather not merge any functionality changes to bsn πŸ˜“, but this is an ergonomics win so...

but repeatability might come in handy in some ways that we can't quite expect, i think the repeatability of scenes was actually very forward looking and idk how i feel about removing that. Could you elaborate on what you mean by inheritance caching making this a moot point?

thick slate
split harness
#

This should land in 0.19

queen oak
#

πŸ˜” ig so

queen oak
#

@split harness reviewed

split harness
#
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, scene.spawn())
        .run();
}

fn scene() -> impl SceneList {
    bsn_list! {
        Camera2d,
        Sprite {
            image: "branding/bevy_bird_dark.png"
        },
    }
}

Sprite example is so minimal πŸ™‚

elder grove
#

bsn is so cool

#

I cant wait

rare whale
#

There's a saying that "quality beats quantity, but sufficient quantity has a quality all it's own". Similarly, I think that if you remove enough friction, you eventually get a qualitative shift in how patterns get used. An example is the difference between Python and JavaScript: lambda functions in Python have extra syntactical friction, so you don't see a lot of callback-based APIs, whereas in JavaScript they are everywhere.

split harness
split harness
#

@rare whale christmas is coming early for you πŸ™‚

:FeathersButton {
    caption: {bsn_list![
        (Text("Center") ThemedText),
    ]},
    corners: RoundedCorners::None,
}
#

Things to note:

  1. Struct-style scenes
  2. bsn_list! doesn't require an explicit Box anymore
#

I'd also like to get those pesky {} out of the way for bsn_list! but we'll see

#

bsn_list! doesn't require an explicit Box anymore
This isn't any special bsn! magic. Just realized that while we can't impl<L: SceneList> Into<Box<dyn SceneList>> for L due to rust constraints, we can implement impl<L: SceneList> Into<Box<dyn SceneList>> for SceneListScope<L>, which is the type bsn_list! returns πŸ™‚

queen oak
#

any macro -> expression automatically

split harness
#

But the carve-out is certainly easier

queen oak
#

is the latter on the table? owo

split harness
queen oak
#

i c i c....

#

oh, btw, for the "use #Name in expressions" stuff, aren't we going to have to modify the FromTemplate derive to add some sort of datastructure to keep track of the ScopedEntities?

#

we need some place to store them, we can't just put them in a trait πŸ€” but this would break normal FromTemplate on data structures which we can't easily control

#

OH wait, hm, if we took every auto-generated expression, and instead made it not only a closure, but a tuple of closure and our entities we want to resolve later..... ?

queen oak
split harness
#

Anything involving adding "state" to FromTemplate is almost certainly a dead end

queen oak
#

if we want to use #Name

queen oak
#

i can't concieve of how else to store the ScopedEntities for later resolution

#

i cheated and just added a field alongside my closure to allow for it

split harness
#

Also for "patches", although we might actually be able to defer to the Rust expression parser for those, then operate on the outputs retroactively

queen oak
#
pub struct AsyncClosureWrapper2<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + Clone + 'static>(
    pub T,
    pub Vec<usize>,
);
pub struct AsyncClosureWrapper<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + Clone + 'static>(
    pub T,
    pub Vec<ScopedEntityIndex>,
);

impl<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + 'static + core::clone::Clone> Scene
    for AsyncClosureWrapper2<T>
{
    fn resolve(
        &self,
        context: &mut ResolveContext,
        scene: &mut ResolvedScene,
    ) -> bevy_ecs::error::Result<(), ResolveSceneError> {
        let closure_wrapper = AsyncClosureWrapper(
            self.0.clone(),
            self.1
                .clone()
                .into_iter()
                .map(|e| ScopedEntityIndex {
                    scope: context.current_entity_scope(),
                    index: e,
                })
                .collect(),
        );
        scene.push_template(closure_wrapper);
        Ok(())
    }
}

impl<T: AsyncFnMut(Ctx, Vec<Entity>) + Send + Sync + Clone + 'static> Template
    for AsyncClosureWrapper<T>
{
    type Output = AsyncTaskThing<T>;

    fn build_template(
        &self,
        context: &mut TemplateContext,
    ) -> bevy_ecs::error::Result<Self::Output> {
        let mut entities = vec![];
        for scoped_entity_index in &self.1 {
            entities.push(context.get_scoped_entity(*scoped_entity_index));
        }
        let async_ctx = context.resource::<Ctx>().clone();
        let mut closure = self.0.clone();
        let (tx, rx) = async_channel::bounded(1);
        AsyncComputeTaskPool::get()
            .spawn_local(async move {
                let mut future = closure(async_ctx, entities);
                futures::select! {
                    _ = rx.recv().fuse() => {}
                    _ = future.fuse() => {}
                }
            })
            .detach();
        Ok(AsyncTaskThing::<T>(PhantomData, tx))
    }
}
#

this is how i did it to allow for the async |world: Ctx| {} with #Name there πŸ€”

queen oak
#

that makes tentative sense to me

split harness
queen oak
#

How are you planning to get around it πŸ‘€

split harness
#

Which I haven't fully taken to its conclusion yet

queen oak
#

let me know when you've finished cooking

#

we could wrap every FromTemplate a second time, with the entities it needs, and dispose of that during resolution

thick slate
rare whale
#

Also, does this permit struct fields to be initialized to #Name values?

#

That's what is holding back scrollbars, since we need a way to tell the scrollbar which entity it's controlling

tight raft
#

@split harness What about this (to close or to improve)? They should be similar.

split harness
split harness
#

^ needed for things like tool_button

rare whale
#

I can think of 3 different ways to avoid that particular syntactical construction

queen oak
#
Children [
    (#Minus 
        button(ButtonProps::default()) 
        Children[(Text::new("-1") ThemedText)] ),
    (#Counter 
          Text::new("0") 
          ThemedText
          Node { margin: UiRect::horizontal(px(10.0)) } ),
    (#Plus 
        button(ButtonProps::default()) 
        Children[(Text::new("+1") ThemedText)] )
]

I've been messing around with different ways of formatting bsn, and i've quite come to like a lisp-style #Name as identifier, then the rest of the sibling components indented, i'm honestly glad we don't have auto-formatting yet, it's letting these experiments here percolate.

rare whale
#

Here's how it would look with my proposal:

Children [
    #Minus 
    button(ButtonProps::default()) 
    Children [
        Text::new("-1")
        ThemedText
    ]
    ---
    #Counter 
    Text::new("0") 
    ThemedText
    Node { margin: UiRect::horizontal(px(10.0)) }
    ---
    #Plus 
    button(ButtonProps::default()) 
    Children [
        Text::new("+1")
        ThemedText
    ]
]
queen oak
#

I do like the dashes

#
Children [
    #Minus 
      button(ButtonProps::default()) 
      Children [
          Text::new("-1")
          ThemedText
      ]
    ---
    #Counter 
      Text::new("0") 
      ThemedText
      Node { margin: UiRect::horizontal(px(10.0)) }
    ---
    #Plus 
      button(ButtonProps::default()) 
      Children [
          Text::new("+1")
          ThemedText
      ]
]

Here's dashes plus tabbing

#

idk if it does much

#

OH wait

leaden dew
rare whale
#

Which format you prefer may depend on what you are using BSN for - UI tends to have deeper hierarchies, which means that you get a lot of indentation, and things get crowded up against the right margin, whereas for typical 2d and 3d work, you don't generally get that many nested levels.

queen oak
#
Children [
    ---#Minus 
      button(ButtonProps::default()) 
      Children [
          Text::new("-1")
          ThemedText
      ]
    ---#Counter 
      Text::new("0") 
      ThemedText
      Node { margin: UiRect::horizontal(px(10.0)) }
    ---#Plus 
      button(ButtonProps::default()) 
      Children [
          Text::new("+1")
          ThemedText
      ]
]

But idk πŸ€”
i feel like there might be something here

queen oak
#

i like how it's flat, but i also just like it asthetically a lot

rare whale
#

@split harness So there's a bit of friction around asset handles in nested structs. The magical conversion that you get with FromTemplate only extends to properties of that type. For something like TextFont (a common case) the handle is inside the FontSource enum:

pub fn label_small(text: impl Into<String>) -> impl Scene {
    bsn! {
        Text(text)
        template(|ctx| {
            Ok(TextFont {
                font: FontSource::Handle(ctx.resource::<AssetServer>().load(fonts::REGULAR)),
                font_size: size::EXTRA_SMALL_FONT,
                weight: FontWeight::NORMAL,
                ..Default::default()
            })
        })
        PropagateOver<TextFont>
        ThemeTextColor(tokens::TEXT_MAIN)
    }
}
#

Here we have to use template(|ctx|) so that we can acquire the handle

split harness
#

At the very least it’s easy enough to slap FromTemplate on TextFont if it’s missing

rare whale
#

However, the handle is inside of FontSource.

#

Which isn't a component

queen oak
#
Children [
    ---#Minus---
    button(ButtonProps::default()) 
    Children [
        Text::new("-1")
        ThemedText
    ]
    ---#Counter---
    Text::new("0") 
    ThemedText
    Node { margin: UiRect::horizontal(px(10.0)) }
    ---#Plus---
    button(ButtonProps::default()) 
    Children [
        Text::new("+1")
        ThemedText
    ]
]

oooo it would be interesting to have the option to surround it
So you could either surround it with dashes, or just have 3 dashes.

queen oak
rare whale
queen oak
rare whale
# split harness At the very least it’s easy enough to slap FromTemplate on TextFont if it’s miss...

This came up because I'm experimenting with a dynamic style component that contains a vector of ImageNode:

/// A single rule in the list of style rules.
#[derive(Clone, Default)]
pub struct StyleRule {
    pub pattern: StatePattern,
    pub background: Option<ImageNode>, // None = render nothing for this state
}

/// A component which updates the style of an entity based on the widget states (disabled, pressed,
/// hovered, and so on)
#[derive(Component)]
pub struct StatefulStyle {
    pub source: StateSource,
    pub rules: Vec<StyleRule>,
}
#

That is, I can convert a path to a handle when the ImageNode component is used in component position in the BSN syntax. But I'm guessing that won't work if I am using ImageNode as a sub-field of a larger component.

#

(The basic idea here is a component which looks at the widget states, and then updates the background image in response to changes in those states. It's doing what feathers does, but in a much more data-driven fashion rather than using bespoke conditional logic.)

#

This is laying the foundations for my "bring your own artwork" widget collection idea.

dusk aurora
# queen oak ```rust Children [ (#Minus button(ButtonProps::default()) ...

feel free to adapt/experiment, although I wouldn't recommend coming up with a special formatter, Id stick to what works and possibly add rules/config later.

No need to reinvent the wheel imo. Experimentation happens naturally with a usable state and accessible api imo (both consumers and devs), i.e. tooling gets created because people use something and the code is extendable/accessible, not because its hidden and encapsulated away and doesn't have formatting. In my experience it has been the opposite typically.

https://github.com/bevyengine/bevy/pull/23586

GitHub

Objective
Bsn fmt
Solution
Adds a bsn formatter and binary that can be used in terminals, editors or CLI tools.
Testing
There is a small readme with some instructions, you might find more there soo...

split harness
split harness
#

Notably, the only reason the wrapper is necessary is because the Into conversion doesn't exist. I remember hitting this before and not adding the Into for the asset handle template because for non-template FontSource, it has From<&str> for FontSource::Family, not the asset

#

So it would be weird for strings to be "asset paths" for templates and "families" for non-templates

split harness
#

I would like to stress that nesting templates does indeed work as expected

split harness
#

(but im still not ready to embark on that quest)

rare whale
pale edge
#

What will happen to the old bevy ui system /syntax ?

dapper sky
#

.spawn(/* Bundle */).with_children(|parent| { /* procedural spawning logic */ }) is sticking around. It's just very gnarly syntax once things get complex.

dapper sky
#

yeah, it's sticking around.

#

it's still useful for highly procedural circumstances

dapper sky
rare whale
#

@split harness Once again I'm pushing the envelope, I'm trying to figure out the best idioms for embedding a bitfield in a bsn scene. I have something that looks like this:

bitflags! {
    /// Bitflags representing the possible states of a widget
    #[derive(Clone, Copy, Default, PartialEq, FromTemplate)]
    #[repr(transparent)]
    pub struct WidgetState: u32 {
        const DISABLED = 1 << 0;
        const HOVERED  = 1 << 1;
        const PRESSED  = 1 << 2;
        const FOCUSED  = 1 << 3;
        const CHECKED  = 1 << 4;
    }
}
#

The intended use case is something like the following:

bsn! {
    Node {}
    Button
    Hovered
    EntityCursor::System(SystemCursorIcon::Pointer)
    FocusIndicator
    StateSource::SelfState
    States [
        StyleRule {
            pattern: StatePattern {
                mask: WidgetState::DISABLED,
                required: WidgetState::DISABLED,
            },
            background: ImageNode {
            },
        },
        StyleRule {
            pattern: StatePattern {
                mask: WidgetState::PRESSED,
                required: WidgetState::PRESSED,
            },
            background: ImageNode {
            },
        },
    ]
}
#

So essentially the widget has a relation which enumerates the various visual states, and has a bitmask pattern which is matched against the current state of the widget

#

However, I get the trait bound WidgetStateTemplate: std::convert::From<WidgetState>is not satisfied

rare whale
#

@split harness OK so I'm confused about a bunch of things. My main problem is that while I have a bunch of new tools (Template, FromTemplate, etc.) I don't have a good sense of which tool to apply for a given problem.

In addition to the above issue I mentioned (templating for bitflags), I'm also seeing some other things that I don't understand:

  • In the above example, you can see how I assign an ImageNode to the backgroundProperty, however the actual type of background is Option<ImageNode>. I don't know why this compiles; I would think that I would have to wrap it in a Some(). Does BSN automaticaly coerce options?
  • It still thinks that the type is Option<ImageNode>, I can't add any properties to it: ImageNode { color: Color::WHITE } doesn't compile because Option has no Color property.
  • Explicitly wrapping it in Some doesn't compile, the error I get is "try using the variant's enum: core::option::Option, std::option::Option"
  • Changing it to OptionTemplate::Some(ImageNodeTemplate doesn't work, because when I try to add ..default() I get expected identifier:
background: OptionTemplate::Some(ImageNodeTemplate {
    color: Color::WHITE,
    ..default()
}),
#

I'm basically just flailing around without really understanding what I am doing.

thick slate
rare whale
thick slate
#

Definitely recommend the crate docs. Not exhaustive, but I've made it my goal to help provide that sort of map.

rare whale
# thick slate Definitely recommend [the crate docs](https://dev-docs.bevy.org/bevy_scene/index...

One quick question: can a type be its own template? Like if I have a primitive newtype like Velocity(f32), and I don't want to use the auto-derive for some reason, do I need to define a VelocityTemplate?

The reason I ask is because, if I have a bitfield with a bunch of constants, I'd have to duplicate all those constants in the template type.

Related to this: I have no idea whether reflection works with bitfields, I'm guessing it probably doesn't.

thick slate
rare whale
lime veldt
#

aren't bitfields already part of bevy's core dependencies? then it could just do a proper reflection

rare whale
#

Note that I don't have to use a bitfield here - but it's the most efficient representation for what I am trying to do. The question is, what's the best way, in BSN, to represent a closed set of items?

queen oak
#

Enum?

#

Degenerate enum

silk lava
#

An enum?

#

Although I guess an enum doesn't easily support AND semantics like an bitfield does.

thick slate
lime veldt
#

unless there is a noticeable chance bevy gets rid of bitfields some day

split harness
# rare whale <@153249376947535872> OK so I'm confused about a bunch of things. My main proble...

In the above example, you can see how I assign an ImageNode to the backgroundProperty, however the actual type of background is Option<ImageNode>. I don't know why this compiles; I would think that I would have to wrap it in a Some(). Does BSN automaticaly coerce options?

Field values have implicit into(). Same reason "some string" works for String.

It still thinks that the type is Option<ImageNode>, I can't add any properties to it: ImageNode { color: Color::WHITE } doesn't compile because Option has no Color property.
Explicitly wrapping it in Some doesn't compile, the error I get is "try using the variant's enum: core::option::Option, std::option::Option"

This is an unfortunate corner case. ImageNode has custom template logic, meaning you need an OptionTemplate, not the normal Option: https://github.com/bevyengine/bevy/pull/23699

Changing it to OptionTemplate::Some(ImageNodeTemplate doesn't work, because when I try to add ..default() I get expected identifier:

You shouldn't use ..default() in "field value" position in BSN. Although perhaps we should add support for it just to make migration easier.

It still thinks that the type is Option<ImageNode>, I can't add any properties to it: ImageNode { color: Color::WHITE } doesn't compile because Option has no Color property

Hmmm I'll want to investigate this one.

split harness
split harness
#

This works:

#[derive(Component, FromTemplate)]
struct Widget {
    state: WidgetState,
}

bitflags::bitflags! {
    /// Bitflags representing the possible states of a widget
    #[derive(Clone, Copy, Default, PartialEq)]
    #[repr(transparent)]
    pub struct WidgetState: u32 {
        const DISABLED = 1 << 0;
        const HOVERED  = 1 << 1;
        const PRESSED  = 1 << 2;
        const FOCUSED  = 1 << 3;
        const CHECKED  = 1 << 4;
    }
}

fn widget() -> impl Scene {
    bsn! {
       Widget {
           state: WidgetState::DISABLED
       }
    }
}
split harness
split harness
dapper sky
#

was only after the merge I realised the "basic" criteria.

#

"FromTemplate is the magic" is maybe a " #1264881140007702558 lurker" brainworm

lime veldt
#

what if the third party needs custom template logic but the type does not derive FromTemplate? Is that a use case thing?

split harness
lime veldt
#

oh this is just neat

split harness
#

These behave differently / have different requirements when it comes to field access

#

So Some on the right hand side does not work. It requires Option::Some. We could special case built in types based on their name, but that creates nastiness when there are naming conflicts (ex: a user has a custom Some type/enum variant in scope), and doesn't properly prepare people for the non-hacked cases

split harness
# rare whale <@153249376947535872> OK so I'm confused about a bunch of things. My main proble...

The reason you can't skip OptionTemplate::Some is that you don't get implicit into() for anything that is in "patch position", as we aren't assigning a full value. Instead, we are setting a field using the whole type path :

#[derive(Component, FromTemplate)]
struct Widget {
    #[template(built_in)]
    image_node: Option<ImageNode>,
}

fn widget() -> impl Scene {
    bsn! {
        Widget {
            image_node: ImageNode {
                flip_x: true
            }
        }
    }
}

The relevant expanded bsn!:

<Widget as bevy_scene::PatchFromTemplate>::patch(
  move |value, _context| {
    // image_node is an OptionTemplate, which doens't have a flip_x field
    value.image_node.flip_x = true;
  }
));
#

Implicit into() is only for "terminal values", such as literals, rust expressions {}, function results, etc

split harness
split harness
#

However given the already complex interplay of rules, this type of exception to the rules should not exist imo

#

This "consistent with the rules as expressed" impl doesn't work:

background: OptionTemplate::Some(ImageNodeTemplate {
    flip_x: true
})

This is a consequence of a tradeoff being made for enum support, combined some incorrect logic:

  1. "Root level" enums are expected to implement VariantDefaults, and resolve to "enum patches". VariantDefaults defines a default constructor for each variant type, which enables the patching behavior. This allows enums to "layer on top of each other" and skip fields in BSN in the same way that structs do. If OptionTemplate was a "root template" (aka it produced a component), then this would all work as expected.
  2. Anything that isn't a "root level enum" is expected to express every field on the enum / the patching behavior isn't used. The parameter inputs are parsed as a BsnType (aka things like ..default() are not allowed), but the fields are dumped as if they are full expressions of the type. This is flat out wrong. It isn't doing the implicit Default / patch-on-top logic and it isn't letting you express a Rust expression inline (like you might for function parameters)
#

This is the current (broken) output for the bsn! above:

move |value, _context| {
    value.image_node = OptionTemplate::Some(ImageNode { flip_x: true });
}
#

This should be sorted before 0.19 / I'll start coming up with a fix

#

@rare whale thanks for catching this case πŸ™‚

split harness
#

(or just derive Clone, which is usually doable)

#

@thick slate FYI for any future FromTemplate derives: #1264881140007702558 message

thick slate
split harness
split harness
thick slate
split harness
#

However I still think it is probably the correct tradeoff, as many value-position-enums are outside of user control / won't have the ability to derive VariantDefaults

#

I think theres a best of all worlds solution

rare whale
split harness
#

@dapper sky

split harness
rare whale
#

You can see where I am going with this, I hope πŸ™‚

split harness
rare whale
#

The thing with the bitfields is similar to what a lot of physics engines do - they have a pair of masks representing collision groups

split harness
#
GitHub

Objective
EntityReference is a template, but it doesn't follow standard naming conventions. EntityTemplate follows conventions and is much more self-documenting / instructive.
Solution

Ren...

GitHub

Objective
3d_scene doesn't use the new scene.spawn() pattern.
Solution

Use it!

split harness
#

@rare whale I've made it possible to treat bsn! as a SceneList, which means we can clean up some single-entity bsn_list parameters:

// before
:FeathersButton {
  caption: {bsn_list![ (Text("Normal") ThemedText) ]}
}
// after
:FeathersButton {
  caption: {bsn!{ Text("Normal") ThemedText }}
}
#

Hopefully soon to be:

:FeathersButton {
  caption: bsn!{ Text("Normal") ThemedText }
}
rare whale
#

I try never to assume that the "thing that fits here" is a singular entity

lime veldt
#

can you design a macro that has both foo!{ } and foo![ ] syntax?

split harness
#

Which is imo the wrong call

#

I'd like bsn_list![] to only work with []

lime veldt
#

oh I could have done vec!{1, 2, 3} all the time to confuse reviewers you say

split harness
lime veldt
#

yes... fortunately

split harness
#

Ex: if you have an Activator { thing_to_activate: SCENE_TYPE }, where thing_to_activate is expected to have some Activate component

#

In this case, SCENE_TYPE should be Box<dyn Scene>

#

Or if you want more type safety (at the price of giving up arbitrary scene definitions / extensions), then you could accept Activate directly (ex: if it has a scene associated with it)

split harness
split harness
rigid adder
#

This also great for state serialization/deserialization.
In my game I don't serialize every component to save the state. I serialize only necessary and insert the rest via required components or triggers on deserialization.
This should simplify this.

#

Or this won't spawn the scene with the regular component insertion? πŸ€”

split harness
tawdry owl
#

I feel like having "components are scenes, but only sometimes - look at the definition" is a bit confusing. I like having structs-as-scenes since they're basically functions with defaults that we're missing in rust today, but tying them to components with special syntax for prop fields ("@") feels a bit too magical. I personally would've preferred a special SystemParam to query scenes instead.

rigid adder
#

Thanks for the clarification, I just got confused by on_insert.
Makes sense ,actually.

split harness
#

I do admit the @ is a certain kind of "magic", but I also think it does a good job of making "scene constructor parameters" visually distinct from "patched component fields"

tawdry owl
#

Mixing component fields with config props fields together in one block also feels a bit wrong, but that's just my first impression of the proposal

split harness
#

I think the competing proposal is:

#[derive(Component, Default, Clone)]
pub struct MyButton {
    clicks: usize,
}

pub struct MyButtonScene {
    label: Box<dyn SceneList>,
}

impl IntoScene for MyButtonScene {
    fn into_scene(self) -> impl Scene {
        bsn! {
            MyButton {
                clicks: 0
            }
            Children [
                {self.label}
            ]
            
        }
    }
}

Where the tie between the types is implicit. I think this is fundamentally worse, as MyButton (the runtime component) is no longer "logically tied" to MyButtonScene. MyButtonScene is not queryable. MyButtonScene is the "user facing" type that people spawn, but they can not interact with it at runtime / it isn't "real". Theres no enforcement that MyButton has the scene logic, even though it is built to be used in that context.

#

It is also just more boilerplatey / adds more types into the mix

#

It also makes the "smaller" / simpler cases nastier:

#[derive(Component, Default, Clone)]
struct MyButton;

#[derive(Component, SceneConstructor, Default, Clone)]
#[scene(my_button)]
struct MyButtonScene;

fn my_button() -> impl Scene {
  bsn! {
    :"my_button.bsn"
    MyButton
  }
}

Whereas in my proposal you can just do this:

#[derive(Component, SceneConstructor, Default, Clone)]
#[scene("my_button.bsn")]
struct MyButton;

The extra boilerplate is necessary because we want to ensure MyButton exists on the scene.

#

You can add MyButton manually in my_button.bsn, but that isn't necessary in my proposal, and won't work in cases like my_button.gltf

#

Whereas my proposal will support this, and it will behave as expected:

#[derive(Component, SceneConstructor, Default, Clone)]
#[scene("player.gltf")]
struct Player;
tawdry owl
#

This does make sense from the marker component perspective if 1 scene == 1 marker, and that does allow to query for entities containing a scene, I'm just not sure how it fits with all the other systems currently in place - required components and function scenes. Maybe also scenes should have more distinct syntax from components (like prefixing with @ instead)?

#

It just feels like it's becoming a bit too difficult to parse what's going on in bsn! blocks, but maybe I'm just not used to it yet

leaden dew
#

what's the motivation for making scene inheritance do double-duty as also being how you call scene constructors? those feel conceptually unrelated to me but that might just be me not having the right mental model here

#

it's a little scary to me that MyButton { ... } and :MyButton { ... } mean such radically different things despite both saying "i want this to be a MyButton"

copper dragon
#

And having the textual difference be so subtle makes it hard to mentally track.
Tho maybe that could be solved with syntax highlights rather than syntax changes

split harness
# tawdry owl This does make sense from the marker component perspective if 1 scene == 1 marke...

Function scenes could still be used in the same way they were before. I suspect that in practice they would largely be used to apply small pieces of additional reusable patches, rather than to spawn "things", as the "type driven" approach is better for pretty much everything. We have long known that "required components" and BSN intersected with each other from a problem space perspective. It would be interesting to try to unify them, but that would require some fundamental changes to how we spawn things

#

"Scene components" doesn't really fundamentally change the scene vs required components question. It just makes the question clearer.

split harness
split harness
#

They are both "MyButton" component patches. :MyButton just does "scene constructor" work too

#

Additionally, it will have guard rails. If you use MyButton {} in a scene that doesn't do :MyButton earlier, it will print a warning

split harness
#

I believe we could also add some guardrails to prevent spawning scene components via world.spawn() without the full scene. Ex: add a required component that is only set "properly" when spawned with the scene, and warn when it is not

silk lava
split harness
#

I think the most important reason we should do this: it is what pretty much everyone wants in practice, in my experience. It is essentially a Bevy right of passage to define a Player component and then be like "ok how do I get all of the other entities / components / scenes necessary for Player to function to spawn whenever I spawn Player"

silk lava
#

Also, not being able to spawn a component, is a no go.

split harness
silk lava
#

Also 1 nit about the Component -> Scene, if two components produce the same scene, they will still not be in archetype together, because the scene-producing components themselves would make them disjoint. (Although I suppose the assumption is not for pure-factorys but rather component+friends)

#

Dont mean to dogpile, but also I assume required components will stick around as the procedural variant, with bsn components being the declarative variant.

leaden dew
split harness
# silk lava Why does this need to be a separate trait, compared to `Template`, couldn't you ...

I started from this perspective and tried to make it work. The "problems":

  1. Not all Templates are scenes / should produce them
  2. Template can be patched from "anywhere" in the inheritance hierarchy. This makes it impossible to evaluate scenes incrementally and reuse work / cache it, because they could change in arbitrary ways in the future. Scene constructors notably run at a fixed point in the hierarchy.
  3. This also creates a weird "chicken and egg" problem. An inheriter could write "on top" of a Template, but that influences what it is supposed to be writing on top of.
#

I see this as very similar to how inheritance is expressed in languages like C#. CoolCollection: Collection and then in the constructor calls the base constructor.

leaden dew
#

i think the only thing that feels odd to me is marrying it to scene inheritance syntactically and conceptually, when inheritance isn't really something that comes to mind when i think of a spawner function or even constructor

#

a constructor is "this work must happen to produce a valid X" (to me)

split harness
#

We see this exact pattern when porting the tool_button widget in Feathers to SceneConstructor:

 #[derive(Component, SceneConstructor, Default, Clone)]
#[scene(FeathersButtonProps)]
pub struct ToolButton;

impl ToolButton {
    fn scene(props: FeathersButtonProps) -> impl Scene {
        bsn! {
            :FeathersButton {
                @caption: {props.caption},
                @variant: {props.variant},
                @corners: {props.corners}
            }
            Node {
                padding: UiRect::axes(Val::Px(4.0), Val::Px(0.)),
                min_width: size::ROW_HEIGHT,
            }
        }
    }
}
split harness
#

That is the : piece

#

In theory you can also do this and "skip inheritance":

bsn! {
  {FeathersButton::scene(props)}
}
#

I just opted not to provide sugar for it because that would be ambiguous with Component { foo }

silk lava
#

For the syntax, probably not possible, but could we do something like:

:MyButton {
    clicks: 10
}(
    MyButtonProps{label: bsn!{ Text("Hello") }}
)

Or maybe even drop the MyButtonProps and just named arguments πŸ€”
Feel like I want to try to preserve the fact its a function call, and split the state and input more.
The other forms of inheritance normally are function calls, right?

leaden dew
#

what's the intended approach for component constructor functions (like MyComponent::new) where the component derives FromTemplate? i have a component with entity references so Default doesn't make sense, but deriving FromTemplate makes it all weird
(for ex: bsn! { Projectile::new_with_range(...) } where Projectile has derive(FromTemplate))

split harness
#

Manual FromTemplate impls (and therefore the MyType vs MyTypeTemplate split) are for things that really need the template. And at that point, it is generally best to just use struct init syntax

leaden dew
#

the projectile carries an Entity field indicating which entity fired it, hence the FromTemplate

split harness
#

Especially given the automatic into, which is another reason people often reach from constructors

split harness
leaden dew
#

it's new_with_range(from: IVec2, to: IVec2, speed: f32, range: f32, origin: DamageOrigin) where DamageOrigin contains two entity fields (the weapon and the actor firing it) but that's mostly extraneous to the example

split harness
leaden dew
#

i tried moving the constructor to ProjectileTemplate but then it wants DamageOriginTemplate which is....bleh

#
pub struct Projectile {
    pub velocity: Vec2,
    pub range: f32,
    pub origin: DamageOrigin,
}
#

im probably just gonna split out the origin to a new component to sidestep the issue in this case

#

but not having easy constructors is unfortunate

split harness
#

In general embracing field-driven init is the "easy win". And that notably plays the most nicely with Asset-driven scenes (as they will generally be composed in an editor as field patches)

#

But yeah if you are using "template types", the constructors will need to be on those templates

#

Alternatively, if you really need to use the constructor for the "template output" rather than the template, you can always just use template(|_| Ok(Projectile::new_with_range(....))

#

But that would also require sorting out the DamageOrigin entities manually. If you don't want to use templates, you give up the benefits of templates πŸ™‚

leaden dew
#

ah! that does work, cool

#

in this case the origin stuff is always passed from the caller so it doesn't really need any template involvement

split harness
#

Allowing you to not derive FromTemplate on Projectile?

#

Then you could still use direct BSN patch syntax

leaden dew
#

what would that look like?

#

i thought you need either FromTemplate or Default+Clone

#

and default doesn't make sense for DamageOrigin

#

(i could force it of course, with Entity::PLACEHOLDERs)

split harness
#

Yeah it would require placeholders or Options

#

To get the default impl on DamageOrigin

leaden dew
#

just feels bad because it's an impl that ideally is never used (the struct should always be fully specified any time it's used)

#

but it does make for the easiest use in bsn :p

split harness
#

How does from and to factor in to the final Projectile properities?

#

velocity = normalized_direction * speed?

leaden dew
#

yeah lol it's not sophisticated or anything, would be fine to init in-place. it's just that for this game virtually every callsite starts with the origin cell and target cell, hence the helper

split harness
#

If so, perhaps the FromTemplate derive + velocity calculation logic is better?

bsn! {
  Projectile {
    velocity: velocity_between(from, to, speed)
  }
}

I think thats clearer anyway

#

/ resuable across contexts

leaden dew
#

yeah that seems reasonable

split harness
#

In addition to what I consider to be improved clarity, you also get all of the benefits of bsn patch syntax and templated entities.

#

/ no need for the placeholder stuff

#

I think "template contructors" should generally only come into play when theres a lot of internal interrelated logic between inputs and output fields

leaden dew
#

it makes sense as a design direction - i know a lot of bevy components have gone in this direction, i.e. preferring to be constructed directly rather than having any computed fields (and if computed values are needed they're shunted to another component)

#

it is a little opinionated though

#

but nothing wrong with that. anyway i appreciate the attention and guidance, thanks!

topaz ginkgo
#

Sounds to me like it’s separate right, and can still integrate?

split harness
topaz ginkgo
#

awesome!

exotic panther
# split harness <@301060831314182146> <@159873981174906880> <@451476504879169546> Here is my St...

my one concern is having to use the deferred spawning method in order to use scene constructors. It makes keeping track of spawned entities much harder, requiring either monolithic spawning functions or keeping track of spawned scenes.

to give a somewhat contrived example, this wouldn't work as there's no way to guarantee that all the items will be spawned by the time spawn_player is ran. While it's not something we're likely to run into a lot those times that we do are going to be very frustrating for end users.

fn main() -> AppExit {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, (spawn_items, spawn_player).chain())
        .run()
}

fn spawn_items(mut commands: Commands) {
    commands.queue_spawn_scene(bsn! {
        #Items
        Children [
            :Sword { @quality: Quality::Legendary, @damage: 10 }
            ...
        ]
    });
}

fn spawn_player(mut commands: Commands) {
    commands.queue_spawn_scene(bsn! {
        Player
        Weapon("Items/Sword")
    });
}
pale edge
#

Will feathers be ported to bsn in 0.19 ?

thick slate
worldly ingot
#

Why require Default for SceneConstructor::Props? Required properties seem like a common use case. Could even allow SceneConstructor on a non-Default Component.

rare whale
split harness
exotic panther
split harness
exotic panther
#

ohh

#

that makes a lot more sense

split harness
tight raft
split harness
#

@rare whale I've wrapped up a port of Feathers to SceneConstructor. Still some cleanup + quality to do, but PR should be out soon.

silk lava
rare whale
split harness
silk lava
split harness
silk lava
#

Ah okay, so 'resolve once' or 'build hierachy cache by value', but running that cache 'builds'/runs the Templates everytime/repeatably.

thick slate
#

@split harness I've finally had a chance to properly digest Scene Components.

It also ensures that the scene can be queried for
This is the big win IMO.

I like this pattern, but I think we're going to need to start thinking hard about how to present the complexity of the scene system to users, and where we can efficiently reign it in

#

Relatedly, I think we should start talking about renaming Bundle to ComponentBundle now that it's much less prominent

split harness
#

I agree being able to query for scenes is also a big win πŸ™‚

#

/interelated

thick slate
#

Yeah, that's fair from a user perspective πŸ™‚

#

I'm biased towards library authors these days

split harness
rare whale
split harness
#

Worth discussing

rare whale
#

I think that we won't know for sure until we see how things get used "in the wild"

split harness
thick slate
split harness
thick slate
rare whale
split harness
#

Components are the "thing" that drive / opt-in to behaviors in Bevy. Scenes are deeply interrelated with behaviors / cannot be extracted from them

split harness
# thick slate <@153249376947535872> I've finally had a chance to properly digest [Scene Compon...

Something I'm considering in the vein of "reigning in user facing complexity" is moving the "scene constructor" derive into the Component derive:

#[derive(Component)]
#[component(scene, on_add)]
struct Player;

impl Player {
  fn scene() -> impl Scene {
    bsn! {}
  }
  fn on_add(world: DeferredWorld, context: HookContext) {
  }
}
  • Lets the user think fully in terms of "what is this component's scene" rather than "what the heck is a SceneConstructor"
  • Parity / siblingship with hook config
  • More strongly enforces the SceneConstructor / Component tie
  • Enables us to add component hooks to enforce Player always being spawned with its scene
  • One less trait to manually derive