#Next Generation Scenes

1 messages · Page 4 of 1

ebon bolt
#

Yeah, I just noticed the proposal mentioning importing .bsn assets using the @ prefix, as Villor said. Nice! If there could be support for importing these assets like we would import Components, that would be nice. Something like use "./other_scene.bsn" as other_scene.

#

Is the most up to date part of the proposal what is on GitHub, or have there been advancements somewhere else in addition?

split harness
#

I do think that preserving entities when they don't need to change is the better fit for a general purpose reactive system in Bevy. If it is "airtight" then you can build game logic around it.

#

(sorry I'm not yet familiar enough with your approach to know if it is "airtight")

#

They will look the same. With the exception of imports (which will be slightly different in assets), bsn macros will be a superset of bsn asset functionality (as you can define inline rust in BSN macros)

#

is there some intention to replicate the rust as close as possible?
BSN is intentionally designed to feel "at home" in a Rust context, but it is not actually valid Rust code (intentionally). Ex: it removes the need for ..default(), it adds the @ operator, etc

#

However the EntityPatch that the bsn! macro generates is "just Rust", and you can compose it manually

timid leaf
split harness
#

Older iterations did not require commas. I'm still a bit divided on that one

split harness
timid leaf
timid leaf
thick slate
#

Very bikesheddy but I really don't want alternate syntax if possible

#

It screws up diffs and makes writing tooling harder

timid leaf
thick slate
#

Yeah, and I think the whitespace haters care more

split harness
timid leaf
split harness
#

We'd either need the macro to be more dynamic / runtime-reflection-ey, or we'd need to break compatibility between the asset format and the macro

#

Using reflection to resolve it would also break autocomplete

#

(and turn compile time errors into runtime errors)

timid leaf
#

Sure, good reasons not to do it. Just pointing out there is a syntax gap between rust and a minimalistic syntax.

split harness
rare whale
#

So the issue of "minimal syntax" is a long-standing issue of mine. I think that a judicious amount of punctuation increases, rather than decreases, readability.

#

Two historical examples are LISP and JavaScript. In the case of LISP, many language advocates (read: fanatics) claim that LISP is superior because it only has one delimiter, the parens (), the argument being that novices only need to learn one syntactical pattern. However, most people find LISP hard to read: the lack of syntax means you have to spend more effort visually parsing the code, understanding the meaning of the individual symbols.

#

In my mind, an ideal language is one where you can replace all the alphabetic letters and numbers with asterisks, and you can still get a rough sense of the structure of the code even though you can't read the names. This means that as you become fluent in the language, your brain can flash-recognize patterns of syntax faster than you can comprehend the meaning.

#

Now, the other example is JavaScript. As you probably know, in JavaScript semicolons are optional - the compiler "knows" unambiguously when you get to the end of a statement, because the parser runs to the end of the grammar production. Within the JS community, there are two bitterly-opposed camps: the "semicolons should be mandatory" camp, which follows the rule of EIBTI (Explicit Is Better Than Implicit, from the Zen of Python); and the "semicolons should be forbidden" camp, which says that a good coder should be familiar enough with the behavior of the parser that they shouldn't need the extra redundancy. I call this second group the "JS cowboy" camp (you can tell which side I'm on).

#

However, there is a meta-issue which informs both of these choices, which is that the purpose of a programming language is not merely to facilitate communication from humans to computers, but to also facilitate communication from humans to other humans. The goal should not be to reduce the amount of typing to the smallest degree, but rather to maximize readability and comprehension.

ebon bolt
#

If not already decided upon, I'd love to see BSN assets parsable during compilation.

native mesa
ebon bolt
# native mesa That’s why there’s a macro, yep.

Yeah, so we would parse them during compile time like so?

bsn! {
    @"./my_scene.bsn"
}

I've only seen examples in the proposal inheriting from assets, not rendering assets by themselves.

bsn! {
    (MyMarker, :@"scene.bsn")
}
native mesa
#

I believe I floated this idea to cart before and he was receptive

ebon bolt
#

Neat! Thank you! 🙂

rigid adder
#

Am I understanding correctly that entities inside a scene will be identified via EntityPath?

With the current approach, we just use Entity inside a scene to identify scene-local entities. If a component references an entity inside a scene, it gets remapped to/from the scene during serialization/deserialization. How will this work for the new scenes with EntityPath?

hybrid spoke
#

I also have this problem. I make a game with big map, I want entity to load/unload by the distance to player. but entity changes when serialization/deserialization, this cause unstable. current temp solution, insert another Component as stable ID to all Entity

split harness
# rigid adder Am I understanding correctly that entities inside a scene will be identified via...

Currently the plan / implementation is EntityPath. I think that should work for pretty much all cases in the Scene -> World case (if you can see holes in that let me know). I haven't implemented (or fully spec-ed out) the World -> Scene case yet (and that is not currently a top priority for me), but its worth discussing. In World->Scene, all serialized entity references should be self-contained in the scene, meaning EntityPath should still work, we'd just need to compute it at serialization time

rigid adder
split harness
fallow cloak
#

at some point me and @native mesa were wondering about using custom syntax for this, and some kind of label that gets erased at load time: [#parent: (...), #child: (ChildOf(parent))]

#

rather than using Name (or maybe that's already not the case, idk much about EntityPath)

native mesa
#

this was speculation about how to encode relations directly into a bsn document. it seems like it might be nice to have a globally unique sort of identifier, like a css id or an html anchor.

cobalt stone
#

I like Name tbh. It's like html IDs

native mesa
#

i'm fine with it being name, but i was thinking there might be situations where the editor will need to auto-generate an anchor

#

and it seems like we should reserve Name for human-readable stuff.

fallow cloak
native mesa
#

and also, i do think there is value in requiring anchors to be globally unique. the convention around Name in animation currently is that it should be unique among siblings.

#

as @rigid adder points out, that makes it less than ideal for a stable id between revisions.

fallow cloak
native mesa
#

yeah, i was about to add that :p

#

the final drawback is, if we go down the entity path thing, moving an entity will mean rewriting all the paths in any relations pointing to it (and that applies whether we have first-class ecs relations or manual relation-like-things). that could lead to significant churn in the files, and hence large annoying diffs. I've heard godot's scene format has problems of this sort.

fallow cloak
#

if these labels get erased on load it might be nice to be able to get a SceneLayout or something when a scene is spawned, to be able to look things up after the fact

#

also perhaps for linking editor entities back to bsn?

native mesa
#

if we did decide to have file-unique stable identifiers, preserving them as components seems like it would help with matching up and reloading documents

fallow cloak
#

I was thinking to include it with asset metadata, since each id would be specific to a scene

#

actually components would be useful as well

native mesa
#

i suspect i can see why cart might be opposed to unique ids, they are not always fun to author by hand.

fallow cloak
#

yeah, though I guess the editor could probably generate them from the Name

#

actually there might be an issue with unique ids and scene inheritance

native mesa
#

for the editor it would be fine, but if we wanted to use them for making correspondences during reloads then we'd really have to require them, and that would probably be annoying to write in the source file macro version.

#

the issue touches a lot on what our reactivity/reloading approach will be.

fallow cloak
#

yeah...

native mesa
#

entity paths and index disambiguation do seem like they will complicate reloading. too much is implicit in the structure, and if the structure changes the correspondences break.

fallow cloak
#

hmmmmm I wonder if they could be generated on-the-fly during scene load whenever they're not explicitly used for relations

#

so they don't appear in the doc at all

native mesa
#

for the editor it's not an issue, for the bsn source macro, it might be possible to have the macro generate semi-stable ids behind the scenes

#

idk

#

again, depends on if we are expecting people to make ui out of composable fn(props) -> bsn functions

#

no word on that yet, as far as i know

fallow cloak
#

I'd expect so, which is why I was thinking about how this would relate to inheritance

fallow cloak
#

😅

native mesa
#

yeah they'd still have to be in the doc for reload. i said you could maybe hide ids from the macro because technically in a fn(props) -> bsn the macro isn't the bsn doc, it generates the bsn doc, so you'd just have to generate them in a stable way.

#

idk, probably not possible.

fallow cloak
#

but yeah, if we only need labels for relations in the doc or for editor reloading, I guess they probably wouldn't be required for every entity of the macro

#

so maybe just generating them from Name in the editor would be enough

native mesa
#

we'll see. the editor will do what's required to get reloading happening, maybe something of general utility will develop out of it. i suspect experimentation will be likely (and that there will be a ton of iteration on bsn once it's out).

compact stag
#

@rusticorn @fallow cloak

rigid adder
#

In Godot they require unique paths and if you trying to create a node with duplicate name under a parent, it will add number to it. Blender does the same.
Maybe worth considring this approach as well.

#

My use case for unique identification is networking.
When loading a scene-based level on both client and server, I need to map server entities to client entities based on scene for replication.

But I think it's not specific to networking. It's also needed for mapping entities inside scene components.
Any of the described approaches - disambiguation syntax, unique IDs, or unique paths - would work for this.

split harness
split harness
split harness
split harness
native mesa
timid leaf
keen patio
#

I’m a bit curious about other devs ideal/planned usage for the macro (bsn!) vs asset format (.bsn).

Personally, I don’t really see myself using the asset format for anything other than composing scenes in the editor (think game levels, creatures, etc), meaning it does not necessarily need to be very human writable. Just vcs-friendly. For example, I wouldn’t mind if the editor slapped a uuid on every entity in the .bsn-file to identify it in hot reload scenarios.

For any hand-coded scenes (ui widgets, ui styles, reusable entity schematics, etc), I’d much rather write them using the macro, snugly colocated with their logic in the rust codebase, with the added support for expressions and the like (and hopefully, reactiveness at some point).

I sometimes get the feeling that I may be in a minority here, that many others plan to use .bsn for coding scenes. Would there be any advantage of hand-coding .bsn-files rather than using macros that I’m not aware of? I realise that hot-reloading is one point, but I believe it should be possible to support the exact same level of hot-reloads for macro invocations, at least for the expression-less parts of the patches (same as .bsn files would by default).

crisp garden
#

There are also just many cases where you might not want to set things up in an editor (like scenes that have no visual components and don't play nicely with the UI) but you don't want to hook up a bunch of functions to register them for. Referencing scenes from other asset files is a lot easier if the scenes are also assets for example

#

Not to mention that afaik the idea is still that the editor will be optional, which means writing scene assets by hand would be very much a valid usecase

keen patio
# crisp garden There are also just many cases where you might not want to set things up in an e...

I guess I just don't see how referencing those scenes as assets would be better than putting them in .rs files and referencing them statically, allowing static EntityPatch-patching where possible.

Referencing scenes from other asset files is a lot easier if the scenes are also assets for example
This seems like a very valid reason though! As long as there is no convenient way to magically "link" statically compiled scenes to asset-bsn

#

I guess in theory, many .bsn-files could be turned into static EntityPatch representations by some build.rs (or proc_macro even) magic when compiling for production

crisp garden
#

The biggest benefit would be that at runtime .rs files can't be added, but .bsn files could be added and loaded from elsewhere or trough a loaded folder

crisp garden
keen patio
# crisp garden The biggest benefit would be that at runtime `.rs` files can't be added, but `.b...

That's a good point!

But what if the hot-reload mechanism watches for added .rs-files and any bsn! macro invocations in those, like:

// new_module.rs
#[scene]
fn cool_button() -> impl Scene {
  bsn! { ... }
}

The hot reload system could parse the BSN and set up a dynamic scene for it, and scenes in other files updated to reference it would be hot-patched using that dynamic scene.
The #[scene] attribute is what would ensure that the scene definition conforms to whatever is necessary for it to be hot-loadable.

All very theoretical though... and I'm sure there will be a bunch of weird edge cases 🤔 I'm starting to see some benefits of using .bsn files over macro in these cases.
Just finding it hard to accept separating things like this because of technical limitations, rather than code organisation concerns.

fallow sinew
#

I thnk at that point, you're not really adding a new Rust module, but an asset with the .rs extension. The #[scene] wouldn't work because you'd need to actually compile your project with that file for it to expand, so you'd have to manually parse and process the file

#

And even then it becomes tricky if you start trying to import code or write anything outside of the bsn! macro (or anything in it that isn't compatible with .bsn assets)

#

Unless we manage to get hot-reloading for Rust working, which has been experimented with in the past

keen patio
# fallow sinew I thnk at that point, you're not really adding a new Rust module, but an asset w...

Yeah.. might be going a bit too far on the .rs hot reload to also detect new files. Just to clarify, I didn't mean that the #[scene] would get expanded during hot reload, but more as a way to mark it as hot reloadable. The IDE would expand it and yield any errors if it does not conform to the "rules".

However I still see value in being able to hot reload macro invocations that existed during compilation. Either by some magic invocation tracking, or by doing some kind of reverse-embedding them as bsn assets when a feature flag is on.

Maybe the latter actually. Would be cool to have something like this available in .rs-files:

bsn_asset!("my-scene-identifier", {
    // .bsn-compliant scene goes here
});

That way it could ensure it doesn't use any imports or things that are not allowed in .bsn files, while also watching the .rs-file for changes and hot-reloading it as a bsn asset on change.

#

Then that could be used from both bsn! and .bsn as @"my-scene-identifier"

#

While hot-reloading actual Rust code is a big challenge, I don't see any reason we can't watch for certain constrained patterns in .rs-file to allow hot reloading parts of our code dynamically.

crisp garden
#

I think hotreloading the code in macros is actually possible ... But that still doesn't cover all the cases where hotreloading might be relevant

#

For example there might be games or apps that ship with hot reloading because users modifying assets is a normal usecase within the app, at which point you can't really rely on them having the code

thick slate
#

There's some chatter with Cart in the thread for this too, talking about his current plans 🙂

thick slate
#

My thinking is that Name (or whatever it turns into) is likely to be an integral part of BSN

#

And that we're going to want BSN in bevy_ecs or a minimal-dependency crate on top of it

split harness
split harness
# thick slate <@153249376947535872> is https://github.com/bevyengine/bevy/pull/16894 compatibl...

Over the past couple of days I've been zeroing in on some changes:

  1. Make normal spawning much nicer / natural / data-first / ergonomic (ex: spawning children is no longer a builder method ... this builds on (2)).
  2. A generalized "non fragmenting relations" implementation that feeds into (1) cleanly, potentially removes the need for GhostNodes (by allowing reactions to be their own separate relations), and could (potentially) evolve into opt-in fragmenting relations. It removes the "archetype move" associated with adding children / observers / reactions / arbitrary other relations. So we can do a full spawn of a tree without moving archetypes once.
#

Hoping to get that implemented / reviewable soon. But I'd like to wait to merge until I'm convinced it will enable the reactivity scenarios we need

#

Everything compiles but it isn't fully functional yet and there are some unsolved problems 🙂

karmic rock
#

In the case of fragmenting relationships you'd get a move on reparenting, but on spawn you can make sure to create the child in the correct table right away

#

(which is what I do when instantiating prefab hierarchies)

#

Also curious what the non-fragmenting relationships design looks like 🙂 I'm currently working on what sounds like a very similar thing (hierarchy optimized for asset trees, also non-fragmenting), curious how similar our approaches are

split harness
#

A suitably advanced command merger could solve that problem too

split harness
# karmic rock Also curious what the non-fragmenting relationships design looks like 🙂 I'm cur...

My "non-fragmenting relations" approach essentially connects two components: the "relationship" component (ex: ChildOf implements the Relationship<SourceCollection = ChildOf> trait), and the "source collection" component (name TBD) (ex: Children implements the SourceCollection<Relationship = ChildOf>).

It uses pre-defined hooks to keep them in sync. The traits are trivially derivable, but they can also be implemented manually if you want to control the storage / correlation logic. Most relations will probably just be Vec wrappers, but they could be hashsets, btrees, etc.

The spawn logic "primes the pump" on the SourceCollection by counting the number of items spawned (statically in the rust type system) and pre-allocating space for them.

The idea for transitioning this to "opt-in fragmenting relations" is to add the concept of a "value component", which is stored on the archetype and indexed. Components would be statically marked as a "value component" when implementing the Component trait. You could then mark, say, the ChildOf component as a "value component".

"Value components" would work for arbitrary component types (not just relations). ZSTs would be a natural fit, but it could be anything that is comparable and/or hashable.

#

Introducing the "value component" concept would also resolve the "multiple relations with different targets" problem

split harness
karmic rock
#

Ahh gotcha, so it's more of a generalization of the current system + hooks 🙂

split harness
frosty shell
#

let me push what I have in a bit, just haven't finished writing the hooks yet

split harness
#

Curious to see how much "convergent evolution" we have 🙂

karmic rock
#

There are definitely a bunch of similarities! I'll share some details in a bit, dinner brb

rare whale
#

@split harness A couple of comments. First, I think you might still need ghost nodes even with relations, because we still want to support "child-like" reactions. You can get rid of ghost nodes if you have external scaffolding, that is, you have some parallel data structure that knows how to order the children when they get regenerated.

#

Second, I've been doing some experiments today:

split harness
#

Correlating to the appropriate position in the parent's children is the problem I haven't tackled yet. But that feels solvable

rare whale
#

So, I changed thorium to add an .attach() trait method to EntityCommands. .attach() accepts a tuple of Attachment. Attachment is a throwaway/consumable thing that can represent an effect, a child, or an observer. It looks like this:

pub trait Attachment: Sync + Send {
    /// Method to construct the attachment on the target entity.
    fn apply(self, commands: &mut EntityCommands);
}

With this, you can do things like:

commands.spawn(Node::default())
    .attach((
        InsertWhen::new(
            move |world: DeferredWorld| disabled.get(&world),
            || InteractionDisabled,
        ),
        WithChild::new(Checkbox::new("I have read the terms and conditions")),
        WithChildren::new((
            Button::with_label("OK"),
            Button::with_label("Cancel"),            
        )),
    ));

However, there's a hitch, which runs into the limitations of my Rust knowledge.

#

Actually there's a couple of hitches.

split harness
#

Presumably WithChildren doesn't support arbitrary bundles in that form

#

(ex: only a list of Buttons)

#

vs ((Button, Foo), (Button, Bar))

rare whale
#

Yes exactly

WithChildren::new((
    (Button::with_label("OK"), Name::new("fred")),
    (Button::with_label("Cancel") TabIndex(0)),            
)),
#

(all_tuples_enumerated to the rescue!) 🙂

split harness
rare whale
#

OK so the next wrinkle is we want to be able to define attachments on the children declaratively. This runs up against the limits of my Rust knowledge:

#
WithChildren::new((
    (Button::with_label("OK"), Name::new("fred"))
        .attach(WithChild(...etc...),
    (Button::with_label("Cancel") TabIndex(0)),            
)),
split harness
rare whale
#

The next wrinkle here is the issue of archetype moves. Now, it really depends on what we want here. For example, if we wanted to know in advance all of the component ids, we could define a method on Attachment that populates a set or something.

split harness
#

Filling in that implementation is the last piece of work required to get this operational

rare whale
#

So for example if you have (Attachment, Attachment, etc...) you could collect from it all of the known component ids, and the attachments that didn't create components simply wouldn't add to the set.

split harness
#

You could theoretically hack in "non-component attachments" into the infrastructure I've built / I think it is roughly compatible with your approach in thorium. But I'm going to recommend against it (as I'm still on team component-only construction)

rare whale
split harness
#

Observers will be represented as Relationships

#

Same with children and reactions

rare whale
#

OK that sounds interesting

split harness
#

However fitting Observers into my current "non fragmenting relations" mold will be interesting

rare whale
#

Now, in my framework there are two other kinds of "owned" thingies that could also benefit from relations, which are callbacks and mutables. The only thing these need is lifecycle management - despawn when their owners do.

#

Callbacks are just one-shot systems that are scoped to an entity

native mesa
#

are they properly many-to-many?

rare whale
split harness
split harness
native mesa
#

ah, so you are building some sort of generalized set of reverse indices to match an immutable "relates to" component?

split harness
#

We could probably in theory do "non-fragmenting value components", but from my perspective that should be a last step done when we actually encounter a usecase

native mesa
#

that will be a regression (i think they are currently many to many) but tbh most people either use 1-to-1 observers or global observers and won't notice

split harness
native mesa
#

are we getting generalized propagation/traversal with this too?

split harness
native mesa
#

if you are doing what i think you are doing you can toposort the index

split harness
#

Haven't implemented it yet, but I don't see any blockers

frosty shell
#

Is it implemented plainly on top of bevy_ecs or does it require modifications to ecs?

split harness
#

Think about the Children / Parent relationship as it exists today, port it to hooks, and generalize it using traits and thats roughly what I've built

split harness
#

Largely entity/component init

#

Very superficial / maybe 10-20 lines total

frosty shell
#

Ah ok

cobalt stone
#

@split harness do you think there's any risk of scope creep for your work here? I think we all agree that we want to avoid what happened with assets v2 right? So I'm asking this early in hopes that we can avoid that.

split harness
cobalt stone
#

Sounds good then

native mesa
#

this is work that needed to get done anyway (and this is a better approach to it). i'm very pleased to see it.

split harness
#

This was motivated by getting reactions out asap, and from my current perspective sorting out non-fragmenting relationships was a sticking point

cobalt stone
#

I'm not too worried about the overall amount of changes, more amount of changes at once

#

If we introduce 1 subpart and then give people time to play around with it for a bit I'm fine

split harness
cobalt stone
#

(for more isolated changes, like with what often happens with rendering, I prefer doing the opposite and getting it all out of the way at once, so my concern is specifically for ECS/fundemental stuff here)

cobalt stone
#

Or a comment link I can read

rare whale
split harness
#

@rare whale had a cool idea to use a global monotonically increasing spawn order index for the sort, which removes the need to topo-sort

native mesa
split harness
rare whale
split harness
#

However that approach has inherent limitations and I'm not yet convinced it is a good general solve

cobalt stone
rare whale
karmic rock
# karmic rock There are definitely a bunch of similarities! I'll share some details in a bit, ...

The problem I'm trying to solve is that fragmenting relationships are actually not that great for (deep) asset hierarchies:

  • each parent fragments children
  • each asset has a relatively small number of (nested) children
  • there can be many asset instances
  • instantiating assets causes archetype creation causes query matching

I've spent 2 years optimizing fragmenting relationships and it's reasonably fast now, but if you compare it to the theoretical maximum it's still just shy of an order of magnitude slower when instantiating/iterating.

What fragmenting relationships are good at though is that you can do a DFS/BFS and iterate child components in contiguous arrays: you never have to do random access. But because of the fragmentation, you end up iterating a lot more tables than you need to. The other thing that's nice with fragmenting relationships is that once tables are created, most operations are O(1) (like removing/adding children).

The new implementation combines the best of both worlds:

  • Possible to iterate contiguous arrays when doing BFS
  • O(1) child creation/deletion/reparenting
  • No unbounded fragmentation

The TL;DR of the approach is:

  • There is a Children<Relationship> component with a vector of children (very similar)
  • There is a Parent<Relationship> component which points to the parent (very similar)
  • Children are fragmented on asset child "kind", so that each table contains at most one child per asset parent

So if I have a Turret asset with Cannon and Base children, I get two tables (one for Cannon children, one for Base children). Because these children are all at the same level in the hierarchy (most of the time) that still lets me do BFS over contiguous component arrays, which means things like transform systems work faster with this kind of storage than when fragmented (and faster than when iterating a Children vector and random-access get'ing).

(there are a bunch of other details why this design works really well with existing flecs features like query traversal, but that's getting in the weeds)

</blurb>

split harness
cobalt stone
split harness
rare whale
#

Right, you need multiple reactions per entity, which means either a collection component (and the difficulty of spawning) or trait queries.

native mesa
split harness
rare whale
#

Every time you hear the word "reaction" replace it with "observer" and then think the logic through.

cobalt stone
karmic rock
cobalt stone
#

And I never got around to it, but eventually I/O would be some sort of systemparam/bundle or general expression trait, rather than only a single component

split harness
wheat remnant
karmic rock
wheat remnant
karmic rock
split harness
wheat remnant
#

Yeah, was a bit slow but I got there 🙂

karmic rock
#

In the new design both the instance root* and instance children have an IsA relationship to their asset

wheat remnant
#

Definitely neater that way

karmic rock
#

Yeah, the reason I didn't do that before is because that would have ensured (ChildOf, instance) x (IsA, asset_child) fragmentation, which in practice means every child ends up its own table. But in the new design having the pair becomes an advantage

#

It doesn't have to be an IsA pair though, any relationship would do as long as it fragments the child tables. Once this lands for prefabs I'm planning to extend it to templates, so that each entity in a template can act as the child "kind". That'd also significantly speed up template instantiation

#

Right now the biggest thing that's weighing the performance of template instantiation down is table creation

#

Tl;dr any scenario where you have a tree with a well defined structure, the new storage is better. For scenarios where the tree structure is not well defined/has an unbounded number of children, fragmenting relationships is better.

wheat remnant
#

What happens if I try and break the unique parent guarantee, i.e. reparent one of the template children to another instance?

karmic rock
#

That'll be disallowed. It cannot be represented in the new data structures

#

You can add & delete children, but you cannot have multiple children of the same kind for the same parent

wheat remnant
#

Yeah it makes sense to me that it breaks things, just wasn't sure how you would catch it, but I guess the map will have a duplicate table_id

karmic rock
#

So instead of having to do a bunch of lookups and iterate the Parent component of that table, you can just look it up in the map

wheat remnant
#

Since this only works for children that can be uniquely tied to a child in the template do you fallback to normal fragmentation for "dynamic" children?

#

Based on the branch you linked flattening is per table which makes sense

karmic rock
#

So those entities will still use regular fragmenting relationships

#

That's one difference with the old v3 flattening feature, where I flattened everything. The difference is that the old feature relied on a post-processing step that flattened a fragmented tree into a flattened tree, which actually made template instantiation time worse (but iteration much faster)

#

The old code was also pretty complex because it grouped children for the same parent together in a table- which meant that you couldn't easily add/remove children

wheat remnant
#

I remember reading up on it at the time and thinking that it seemed pretty difficult to do anything with the hierarchy after flattening 👍

karmic rock
#

Yeah, the feature was pretty unportable to the new query implementation as a result

#

But the speed gains were real, so that bothered me :p

#

Took a while before I landed on the new design

thick slate
split harness
#

Discuss on Github please!

frosty shell
split harness
hybrid bloom
ebon bolt
#

On the topic of observers, how will triggering observers change in this case? Has it been discussed yet? Didn't post this as a question on GitHub as I feel it's not entirely related to the spawning ergonomics.

crisp garden
#

Afaict it's just about spawning them, so triggering them should probably remain unchanged

ebon bolt
#

Out of curiosity, what other benefits do we gain from moving observers from the "app" level to the "entity" level, other than a cleaner flow when spawning?

Is the plan to make systems for other components, which can query observers on the same entity, and trigger them automatically? Like, a Button component on an entity with an Observer component will allow the Button to trigger the Observer?

(Edit: Dumb question, they haven't been moved from the app level)

crisp garden
#

Afaict this wouldn't be a change to observers, only how they are spawned for entity-specific observers, which is currently done trough commands.spawn((A, B)).observe(some_observer)

ebon bolt
#

I understand! I suppose my question spans outside of the context of this discussion

rare whale
#

@native mesa A while back you asked about the difference in user experience between fine-grained and coarse-grained reactivity. @keen patio you may also find this interesting.

The primary distinction, from a user standpoint, is the boundary where lazy evaluation turns into regular data variables. That sounds rather abstract so let me explain.

#

In a reactive system, you have dependencies on data sources (signals, computations, whatever), which are lazily evaluated. In other words, you don't pass around a bool or i32, but rather some object which yields a bool or i32 on demand; and further more, this dereference is tracked in some way.

#

In a coarse-grained system, this dereference happens at the template level: the parameters that get passed into the template are lazy, but once inside they are normal variables. So for example in React:

useEffect(() => {
  // do something
}, [selected]);

The selected in this case is just a normal boolean. If the data source changes, then the template is re-executed, and this code runs again, possibly with a different value of selected.

#

In a fine-grained system, OTOH, the lazy dereference happens at the level of individual expressions. This means that when you work with values within your template, what you are passing around is not normal variables but lazy objects. So for example in Solid:

createEffect(() => {
    // `selected` is a signal
    let s = selected.get();
    // Do something with s
});
#

It can be difficult for users to transition from one model to another. For example, if you are used to working with a coarse-grained framework and you switch to fine-grained, your habits will tend to want to dereference variables too early, whereas instead you need to dereference them at the last moment.

keen patio
#

Hmm interesting, I find the coarse/fine grained terms pretty confusing.

I always thought fine vs coarse-grained was more about rendering/updating the DOM, e.g:

  • Coarse grained: VDOM + diffing
  • Fine grained: Use either runtime or compile-time analysis to make reactions update the DOM directly.

For example, Vue (my personal favourite DX-wise) has a reactivity-system very similar to signals, but still uses a VDOM (with some compile-time fine-grained optimizations). Is it fine-grained or coarse grained? 🤔

rare whale
#

Speaking of diffing, there's another design variable, which is "output-diffing" vs "state-diffing" (my terms). Output diffing is like a traditional VDOM where you are comparing the product of the template. State diffing is where you are comparing the parameters that generate the DOM, and then deciding to tear down and rebuild pieces based on that.

#

Most of the experiments I have seen in Rust/Bevy (including mind) are state-diffing, because it's easier to compare parameters than to try and compare components.

#

So for example, in a dynamic text component, when the input text changes, we patch the entity's TextSpan. We don't try to compare TextSpans.

keen patio
rare whale
#

You are correct in that diffing is an optimization - mostly. There are other reasons, besides performance, to preserve the DOM.

#

The DOM has hidden properties such as focus, selection, input methods and so on which are not represented as DOM attributes.

#

If we destroyed and re-created the element every frame, then external systems would get confused.

#

Also, I've said that fine-grained doesn't need "diffing", but this is an oversimplification. Signal implementation often memoize their outputs, which is a form of diffing that the fine-grained template relies upon. And foreach loops compare the input array with the previous input array.

keen patio
#

Oh, so coarse grained in this case means destructive essentially? There is no VDOM, but the actual DOM is treated as the VDOM. And the state-diffing is used to decide when its necessary to do said destruction/rebuild.

But if you were to use state diffing for tracking when to run certain effects, but the effects themselves do fine-grained updates on the DOM. They will only ever overwrite things that are "bound" to the tracked dependencies. For example if your BSN-template doesn't concern itself with the focus state, it won't ever be overwritten, regardless of whether the reactivity system uses signals or another paradigm to decide when to run those effects.

Would that be considered fine or coarse grained? 🤔 Or would it be a hybrid, in which the reactivity system (state, memos/computeds, effects) is coarse grained, but the rendering is fine grained?

I'd love to read up on these concepts, but I can't really find a good source explaining the differences....

rare whale
#

That being said, I don't think that series talks about the DOM much.

keen patio
#

Thanks, will check that out! After a quick skim-through, like you're onto, it seems like his focus is on the reactivity system, rather than rendering. For example he puts Vue in there as fine-grained, even though it is VDOM+diff-rendered at its core.

karmic rock
#

I think for ECS we'll want the same end result (either coarse or fine grained updates*) but I think the most efficient path to get there will look very different

rare whale
rare whale
#

In Bevy, we want BSN scenes to be assets, but we don't want to implement a fully-generalized scripting language to embed in templates. We do want some form of expression evaluation in templates, but with a carefully curated feature set.

#

The use case I like to use is the list of saved games. This is a template which takes a list of saved game structs which might look something like this:

struct SavedGame {
    pub save_name: String,
    pub hours_played: f32,
    pub character: String,
}

The parameter which gets passed in to the template might be a Vec or slice, or it might be some special collection type specifically made for templates. The main thing, however, is that the template needs a way to iterate over these structs, and access individual fields.

karmic rock
#

but with a carefully curated feature set
I agree it has to be carefully curated, but I think that feature set will become quite large over time

rare whale
# karmic rock > but with a carefully curated feature set I agree it has to be carefully curate...

This is why Vue.js is a model worth looking at: a Vue template is divided into two parts, one which has the logic and the other which has the presentation - they are not mixed together like in React. The presentation section does have some support for expressions but from what I can tell it's purely functional / expressions not a turing-complete language.

Now, when I say "look at Vue" I'm not suggesting that we ape the syntax, but rather look at the popular usage patterns: are users satisfied with this kind of structure, where you can't put arbitrary logic inside of a DOM attribute? It seems like they have many happy customers.

karmic rock
#

I'm a happy Vue user, but a large part of that comes from being able to still define the JS logic very close to the template logic. It's super easy to add a method to a component that you then call in your template

#

I don't see that really working out in a context where each time you want to do something non-trivial you have to go through a (slow) compilation step

#

E.g. if I want to display a sorted leaderboard but I have to write a Rust/C++ function first to do the sorting, that's really meh

keen patio
# rare whale This is why Vue.js is a model worth looking at: a Vue template is divided into t...

This is why Vue.js is a model worth looking at
Yeah. Having control over the template language (and being able to constrain it) opens up a lot of possibilities both for reactiveness implementations, dynamic/script-based templates, and hot-reloading.

It seems like they have many happy customers.
As someone who uses both Vue and React professionally, I don't feel limited by the lack of full language support in the templates at all. Any complex logic would usually go in the setup fn, or even better, inside a reusable composable (hook).

My main gripe with Vue (SFC) is that I can't put multiple components in a single file, which is a pattern I use a lot in React (locally scoped) for code organisation.

keen patio
karmic rock
#

(which runs entirely in the browser)

#

If someone really likes rust I can see how they'd look past those limitations, but other than that I think that being able to build UIs/scenes/assets without requiring a compilation step is a big UX advantage.

#

Not just for iteration speed, but also for modding-like use cases where devs might not have a full development setup

rare whale
karmic rock
#

That's also complex though, since you need to serialize from template -> native -> script runtime and back

keen patio
karmic rock
rare whale
#

As mentioned in the other thread, it's not terribly difficult to embed a wasmtime VM in a Rust resource, and have assets that are handles to executable script functions. The hardest part is dealing with the limitations of bevy asset loaders: without the ability to import types and functions from other script assets, a script can't do very much, it can only call built-in functions that are known to the compiler. However, in Bevy, while it's possible for an asset to depend on other assets, all you get is a handle, you can't peek at the actual data in the loader itself, or await for the dependent asset to be loaded.

karmic rock
#

vs. a "native" scripting language that vertically integrates with the ECS type system

rare whale
keen patio
karmic rock
#

And yeah, I really need to add triangle/mesh support, add more primitive shapes

#

One thing that'd also be nice is to just create wall geometry with holes cutout, so doors and windows don't have to portrude

karmic rock
karmic rock
# rare whale I use earcut

Do you know a good native CSG library for C++? Really hard to find one that also has a permissive license

keen patio
rare whale
#

You could port the JS one I suppose: https://www.npmjs.com/package/earcut

karmic rock
#

Full CSG would be nice but I don't know enough math to implement it

rare whale
karmic rock
#

Ah true, I'll take a look

rare whale
#

It's not a full CSG, but it does triangulate polygons with holes.

rare whale
# karmic rock Agreed, I think what I want from a scripting language is (in addition to basic e...

In my JS engine all actors and interactive objects had scriptable behaviors categorized like this:

  • interactions() - returns a struct which describes what happens when the object is clicked - this determines cursor shape, tooltip, and an action handler
  • goals() - returns a tree of prioritized goals
  • sense() - for entities which are sensors (pressure plates, proximity sensors)
  • present() - reactive function which returns true if the object should be present in the scene - for example, some objects are only present during certain quest stages.
#
interface IBasicInteraction {
  id: string;
  caption: string;
  cursor?: string; // For cursor hovering
  icon?: string; // For radial menu
  distance?: number; // Maximum interaction distance
  focus?: boolean; // Whether clicking causes focus
  combat?: boolean; // Is usable in combat
  useFrom?: string; // Name of marker to stand on when interacting
}
karmic rock
#

Would you expose that as functions to the scripting language?

rare whale
#

Yes

#

Those functions are exported from the scripting language

karmic rock
#

From or to? 🤔 or both

#

I guess you'd want both

rare whale
#

So interactions() is a script function which gets called when the mouse hovers over an object, or when the character gets close enough to use a keyboard shortcut.

#

(When you are playing via the keyboard, there's an implicit ray that projects forward from your character, if that ray hits an interactable object then a keyboard shortcut prompt is displayed at the bottom of the screen)

karmic rock
#

Makes sense. Are you thinking about exposing a similar set of well-defined functions to bind scripting logic with native code?

#

This is very gameplay oriented, how does that work for UI

#

Oh wait- you're not building this for UI

rare whale
#

I have a long diatribe about reactivity and game logic, I think I posted it in ui-dev a while back. The tl;dr is that most game scripting systems are spaghetti events; however the way we inutitively think of most game logic is not about events but states, such as "Am I hungry", "How much gold do I have" or "Does this faction hate me?". Turns out that Solid.js-style reactivity is a very natural fit for this kind of logic.

#

Random off-topic question: Will there be a .bsb? That is, a binary-encoded form of BSN? Like if I want to have a scene that includes a height field, the text format could get unweildy.

rough cave
magic belfry
karmic rock
#

Oh nice, will check it out

magic belfry
#

(I have not personally used it but have seen people recommend it for CSG a lot 😅)

rough cave
#

(E.g. you typically configure Git to not put the YAML-backed assets in LFS, but if you accidentally end up with several MB of probuilder mesh data stored as text-encoded vertex positions, etc., then suddenly you want to put it in LFS, but LFS doesn't support placing files in LFS based on size, only by whether the file name/path matches patterns - so now you have to ensure that there is a .gitattributes entry that maps that specifies that specific filepath as being stored in LFS, which has its own curses)

keen patio
# rare whale I have a long diatribe about reactivity and game logic, I think I posted it in u...

This is aligns very well with my goals as well. I do not see a reactive scene system as something solely for (or even primarily for) UI. It is something I see myself using everywhere in a game that doesn't need bare-metal ECS perf.
For example if I want my player entity to switch texture on lower health, the ergonomic way to do that would be to simply conditionally apply that texture in the template, rather than writing (and naming + registering) a system or manually setting up an observer, just for updating the texture.

It would be very useful in procedural generation scenarios as well. As made very clear by Sander's example above!

karmic rock
#

Something I haven't really figured out yet but I'd really like to be able to do is to make objects responsive to what's around them

#

Tiny glade style, I'd like to just be able to drop objects in a scene and have them configure themselves based on where they are

#

It seems to go a bit against how templates/reactivity traditionally work where you have to pass in everything as inputs vs. having an object "discover" things

rough cave
#

Is that not achievable with an "outer" component whose behaviour is to capture their surroundings and pass it to their inner template which treats the captured information as inputs?

karmic rock
#

Yeah that's definitely one possibility, but I'm not sure how ergonomic that ends up being. You'd basically have to prop drill every possible variable into your templates

rare whale
karmic rock
#

Probably the first option, but not sure

keen patio
#

Being able to "hook" into the ECS is essential imo, just like we can hook into contexts or global stores in JS frontend land.

karmic rock
#

I can't put my finger on why, but I feel like there should be a more structured way to achieve that

#

Should give talk another watch https://www.youtube.com/watch?v=WumyfLEa6bU

In this 2017 GDC session, Tracery developer Kate Compton explains the many surprisingly simple algorithms of procedural content generation and how to use simple data structures to control complex content-generators that are scalable, flexible, and surprisingly powerful.

GDC talks cover a range of developmental topics including game design, prog...

▶ Play video
karmic rock
queen oak
karmic rock
#

Though even assuming that this was somehow possible, having to need a Rust development environment ready is also a big hurdle

#

Imagine you'd have to install a Chrome binary compatible compiler first if you wanted to do any JS development

crisp garden
#

Writing a scene by hand and having it hot reload is definitely not the only usecase for scene files and hot reloading

queen oak
# karmic rock That'd be nice, but this hasn't really happened for C++ compilers which are _rou...

This is true, but the C++ model is very different from rust, from my understanding, once you add in things like imports and file level declarations like other constants and function headers, each function could be split into it's own translation unit, so if that function is not inlined anywhere it would only require recompilation of the function you are touching and no other part of the file. This is not the case for C++, even though each language has a similar level of complexity, the way the complexity is designed in rust, from my understanding, would allow for extremely fast hot recompilation.

karmic rock
queen oak
#

I agree it's not good to make architectural changes based on what might happen, I do think it is achievable though

rare whale
#

@split harness I have been pondering about BSN and custom materials. It seems natural to want to be able to specify a custom material in a BSN template. However, ideally we would want to avoid creating a new instance of the material each time the BSN template was instantiated. I'm wondering how this might be achieved.

In three.js, materials are automatically de-duped: two meshes passed identical material properties will both get a handle to the same shader instance.

keen patio
#

Seems like this is the kind of thing Construct aims to solve.
Maybe the props for constructing a Handle<T> can somehow (enum?) implement both From<&str>/From<AssetPath> for loading assets, but also other variants like <M: Material> From<M> which will add or reuse instances with same properties

#

Which would essentially allow this in BSN:

MeshMaterial3d(@MyCustomMaterial {
    color: BLUE,
    alpha: 0.6,
}),
crisp garden
split harness
# rare whale <@153249376947535872> I have been pondering about BSN and custom materials. It s...

It hasn't been fully specified, but if you search for "inline assets" in my Next Generation Scene/UI discussion, I do touch on this: https://github.com/bevyengine/bevy/discussions/14437

GitHub

Preamble My vision for Bevy UI is largely a vision for better Bevy Scenes and an improved Bevy data model. The general angle is: Embrace the "Bevy data model" for both Scenes and UI. Fill...

#

The general idea being that it would treat them as "sub assets" loaded as part of the initial scene asset load (and therefore only loaded once when the scene is first loaded). How this plays into the macro and the details of defining them and referencing them in BSN is still unspecified, but I do think inline assets is important to support eventually

cobalt stone
#

Who was it that worked on the template macro for not-bsn again?

#

What are the limitations? Does it support nested children, dynamic children, passing in data from other sources, etc?

crisp garden
#

I think that was @native mesa 🤔

native mesa
# cobalt stone What are the limitations? Does it support nested children, dynamic children, pas...

it's major limitations (as I see them) are:

  • it makes very heavy use of rust code-blocks to return bundles. this makes conditional logic hard and annoying, since rust needs a specific return type from each block.
  • it's not been cleverly constructed to give valid output; compiler errors and rust-aware editors are going to be confused. rust-fmt does work for me though.
  • the current insertion api is a very sloppy & stupid api, and there's some stuff I just forgot to make public since i rushed it out
#

it does support nesting children, dynamic children, passing data in from other sources.

cobalt stone
#

I don't particularly care about errors rn, I'm experienced enough to manage

native mesa
#

it expands to direct variable assignment, and variables can't be boxed.

#

Template is basically just a clever type-erased wrapper around Bundle

cobalt stone
#

can you give an example for the type issue? Not sure I understand what the issue is

#

Because I meant from functions, you can return impl Bundle

#

I'm wondering if the template stuff is usable enough for me to go back to editor stuff

native mesa
#

give me an example and I will try to write it in the template macro for you to look at

#

let me just port that demo for you, so i can see

#

it will be rough around the edges currently but i'd be very excited to have you try it.

cobalt stone
#

Like what if I do:

let foo = <query over list of entities in the scene>;
bsn! {
  for entity in foo { 
     <widget showing data on foo>
  }
}

And have it automatically update the widgets incrementally? So that stuff like observers and focus state is persisted between updates.

cobalt stone
#

This is as much as I was able to get working pre-templates with not-bsn https://github.com/bevyengine/bevy_editor_prototypes/blob/main/bevy_editor_panes/bevy_scene_tree/src/lib.rs. Trying to add more dynamic styling around hover and click state was not doable, and I didn't dare attempt a manually-incremental entity inspector 😅

GitHub

Low-friction experiments for the bevy_editor. Contribute to bevyengine/bevy_editor_prototypes development by creating an account on GitHub.

native mesa
#

it's not... pretty.

#

but if you then attached something to item 5, and the query order changed so it became item 3, the entity for item 5 would be moved to 3 in the list of children and the thing you attached would be moved with it. (getting this property requires naming things explicitly, which is what that {i}: is doing.

#

the for-loop case is interesting, I think I can make this much more ergonomic.

#

under the hood, template is defined like this

pub type Template = Vec<Box<dyn Prototype + Send + Sync + 'static>>;
#

where Prototype is defined like this

pub trait Prototype {
    // Required methods
    fn name(&self) -> Option<String>;
    fn build(self: Box<Self>, world: &mut World, receipt: &mut Receipt);
}

So it's basically a type-erased thing which can be updated within the world (receipt tracks what is inserted and where). This is implemented for Fragment

pub struct Fragment<B: Bundle> {
    pub anchor: Option<String>,
    pub bundle: B,
    pub children: Template,
}

which is basically a bundle with template for children.

#

the splice operator @{ ... } just consumes an iterator of Box<dyn Prototype>. It's extremely simple.

#

anyway, don't want to cloud this wg up too much with unrelated stuff so if anyone is interested we should probably move to #1318653986815086683

cobalt stone
native mesa
#

i'll ping you when i think it's worth looking at.

native mesa
# cobalt stone This is as much as I was able to get working pre-templates with not-bsn https://...

there's several ways to implement this logic, but I would expect it to look something like this

// Build the scene tree row template.
fn build_scene_tree(
    mut commands: Commands,
    scene_tree: Single<Entity, With<SceneTreeRoot>>,
    query: Query<(Entity, &Name, Option<&Hover>)>,
    selection: Res<Selection>,
) {
    let template: Template = query
        .iter()
        .map(|(entity, name, hover)| scene_tree_row(entity, name, hover, &selection))
        .flatten()
        .collect();

    commands.entity(*scene_tree).build_children(template);
}

// Generate scene tree template.
fn scene_tree_row(
    target_entity: Entity,
    target_name: &str,
    hover: Option<&Hover>,
    selection: &Selection,
) -> Template {
    let is_hovered = hover.map(|h| h.count).unwrap_or(0) > 0;
    let is_selected = selection.entity == Some(target_entity);

    template! {
        {target_name}: (
            SceneTreeRow(target_entity),
            Node {
                padding: UiRect::all(Val::Px(4.0)),
                align_items: AlignItems::Center,
                ..default()
            },
            BorderRadius::all(Val::Px(4.0)),
            BackgroundColor(
                if is_selected { SELECTED_BACKGROUND.into() } else if is_hovered { HOVERED_BACKGROUND.into() } else { Color::NONE }
            )
        ) => [
            (Text::new(target_name), TextFont { font_size: 11.0, ..default() }, PickingBehavior::IGNORE);
        ];
    }
}
#

the next version should be up tomorrow, with the full example.

rare whale
#

@native mesa @thick slate @split harness My current plan is to port the thorium reactive components to cart's relation-spawning UI once a prototype becomes available. These components are template-agnostic, so should be able to work with ICBINBSN or with regular commands.spawn().

I think that the mechanics of how thorium implements reactivity is solid and doesn't require much in the way of further refinement; what is contentious is the current API for constructing the reactive hierarchy, which uses fluent methods. With the relation-spawning approach, we could instead do something like:

commands.spawn((
    Node::default(),
    Children((
        Spawn((Node::default())),
        Cond::new(
            |counter: Res<Counter>| counter.0 & 1 != 0,
            || Spawn((Text::new("Even")),
            || Spawn((Text::new("Odd"))
    )))));

(Syntax subject to bikeshedding).

These reaction components + a template syntax gives us a lot of what BSN will offer, the main thing it doesn't provide is UI templates defined as assets. However, for a lot of use cases (like editor prototypes) I think that will be sufficient. And it can serve as a basis for further experimentation.

thick slate
#

Excellent

#

Yeah, UI-as-assets is an extension to me: I mostly want a good code-first "multiple entities" abstraction

rare whale
#

Another advantage of this approach is that the templates will (hopefully) be semantically similar enough to BSN that migrating to "real BSN" isn't difficult. For example, observers, event handlers, callbacks, and so on should not have to change much. The only thing that would have to change is the actual calls to spawn(), and these changes can be mechanical.

thick slate
#

Yeah, exactly

rare whale
#

Besides reactions and observers, there's another kind of "effect" that is very useful, which is static composable styles. One of the most common complaints you hear about vanilla Bevy is the amount of boilerplate involved in styling ui nodes. In many cases, however, those styles ought to be re-usable - the same button style can be used for many buttons. In other cases, you have styles that are slight variations of each other: this button is like that other button except left-justified, or with a different color scheme.

The two most common solutions for this are either inventing a custom CSS-like stylesheet language, or writing a function / template like "create_button".

Instead, what we can do is say that a "style" is simply a tuple of mutator functions, and wrap those up into a component-like object that operates similarly to ICBINBSN "Children":

commands.spawn((
    Node::default(),
    Style::new((typography::default, standard_button, primary_button))
));

Where standard_button is a function that sets up all the button style properties (padding, standard colors, border radius, flex alignment and so on), and which can be re-used over and over again. So you only have to code the style for a button once. The function takes an EntityCommands as input, and can pretty much do whatever it wants to the entity.

#

BSN has a different way of handling this, which is template patching - the ability to merge in additional templates. But that will likely have to wait until we get "real BSN".

copper dragon
rare whale
rare whale
#

@thick slate @keen patio @split harness Good morning. I have a new prototype this morning, here's a sample:

commands.spawn((
    Node::default(),
    BorderColor(css::ALICE_BLUE.into()),
    DynChildren::spawn((
        Invoke(Hello),
        Invoke(Button::new().labeled("Click me")),
        Spawn((
            Node {
                border: ui::UiRect::all(ui::Val::Px(3.)),
                ..default()
            },
            BorderColor(css::LIME.into()),
        )),
    )),
));

Hello and Button are template objects. The Invoke wrapper implements SpawnableList<DynChildOf>, and calls the template with a builder. The template can make the choice of how many root-level entities it wants to spawn. Using Invoke, rather than having each template implement SpawnableList directly looks a bit nicer, and provides parity with Spawn.

#

That is, the difference between Spawn and Invoke is that Spawn always creates a primitive entity, whereas Invoke excutes some higher-level construct that may contain multiple Spawns within it.

thick slate
#

And it looks like Invoke was constructable in user space?

rare whale
#

There's one problem with this approach, however. Invoke is implemented for both SpawnableList<ChildOf> and SpawnableList<DynChildOf>, so it can work with either. However, the individual templates currently have the relationship "baked in" to the build API:

struct Hello;

impl BundleTemplate for Hello {
    fn build(&self, builder: &mut DynChildSpawner) {
        builder.spawn(Text::new("Hello, "));
    }
}

This means that the Hello template can only work with DynChildren, not regular children.

rare whale
#

What I want here is a RelatedSpawner that is able to type-erase the relationship type, however this is apparently not possible due to the fact that spawn() is not a dyn-safe method.

#

Some context for those who haven't been following along: DynChildren is a concept for replacing ghost nodes - it's a special list of children that understands how to transform a mix of regular entities and fragment entities into a flat list of regular entities:

  • (A, B, C) -> (A, B, C)
  • (A, B, Fragment()) -> (A, B)
  • (A, B, Fragment(C, D, E)) -> (A, B, C, D, E)
#

An ECS system in PostUpdate looks for DynChildren that have changed, and uses them to produce a regular list of Children, where each fragment is replaced by its children, recursively.

#

Control flow nodes such as Cond and For::each() are represented as fragments.

#

The problem is that once you write a template that works with Children, it won't work with DynChildren and vice versa. This isn't a problem if you decide that all templates should always use DynChildren, but that's controversial to say the least.

rare whale
#

Also, I'm not fond of the name DynChildren. Some alternatives I have considered are:

  • Contents
  • Elements
  • Offspring
  • Progeny
  • Members
  • Nested
formal vine
#

Is this a good start to do PR's? If so, what would be some simple tasks to work on?

thick slate
rare whale
#

@split harness I've been thinking a lot about what I want to do with BSN. As you know, my game is a top-down-view rpg with lots of modular scenery. Each "tile" or wall section has a model, physics, and behaviors which can be represented as an entity graph. Dynamic walls such as traps or pressure plates can also export or import signals, such that complex behaviors can be defined by wiring pieces together. The worlds are very large, and new scenery chunks are "paged in" as the player moves about the world.

What I've been thinking about is whether it would make sense if each individual wall tile was its own BSN template. Now, there are hundreds of individual wall tile types, we don't want to have hundreds of .bsn assets, so these would be aggregated by theme - all of the "dungeon wall" tiles would be combined in a single asset for example, but would be individually addressable by asset label.

The actual terrain chunks which reference these templates would probably not be BSN, for a number of reasons - first because they are mostly just arrays of structs which is not particularly efficient in BSN syntax. Also, my game editor relies heavily on a "tile-like" construction paradigm, in which tile placement is quantized on a grid (although not as strict as in a true isometric game, you can adjust the grid size). This, and other engine features like color remapping, enables a single artist (me) to rapidly create large amounts of scenery without having to do a lot of fiddling with placement and transforms. This means that I need my own bespoke editor regardless of what editing facilities BSN has.

split harness
# rare whale <@153249376947535872> I've been thinking a lot about what I want to do with BSN....

This sound a bit like the "BSN Sets" concept I pitched in my Next Generation Scene / UI system doc: https://github.com/bevyengine/bevy/discussions/14437

Ex: define a "base" wall tile scene and then build on top of that (ex: BaseWallTile, BlueWallTile, RedWallTile, BigWallTile, etc). Each of these would be individually addressable by their name as sub-assets in the same BSN file

GitHub

Preamble My vision for Bevy UI is largely a vision for better Bevy Scenes and an improved Bevy data model. The general angle is: Embrace the "Bevy data model" for both Scenes and UI. Fill...

copper dragon
#

Will bsn files have a dedicated bsn folder instead of "anywhere within assets folder"

vast relic
#

dedicated folder wouldn't make sense in my opinion

crisp garden
#

Definitely agree there, grouping assets by file type is probably the worst way to structure assets, and definitely not something we should enforce, especially not as a BSN-specific thing

quartz sleet
copper dragon
quartz sleet
#

Fair enough!

copper dragon
#

Just trying to think of quality of life features for the editor since I feel like they could be the bread and butter of the editor, "Make the hard/tedious stuff easy"

#

Bevy cli/lint takes care of a lot of the code side stuff

wind dock
#

I’m a simple dev. I prefer systems that have minimal extra logic, especially logic that assumes I’m working a certain way. If something like that was implemented I’d prefer if it was a button that I could ignore

copper dragon
dapper sky
#

are we getting "construct" in 0.16 or is this kicked down the road?

copper dragon
native mesa
#

We got relations instead.

river geode
#

We got relations instead.
we need a #no-context-quotes channel 🤣

snow wedge
native mesa
#

in fact, maybe @keen patio it would be good to publish a version of that on crates.io

keen patio
#

If we make a tag to freeze the editor prototype repo on 0.16 (and every release onwards), then people could just use that as a git dep 🤔

native mesa
#

and, personally, I don't want to wait to get user evaluations of the general design.

#

some of what they come back with will be aplicable when cart's offical version drops.

keen patio
#

are there any advantages to a crates.io-published crate compared to a git dep, other than the visibility, and ability to have bugfixes (minor releases) pushed out?

crisp garden
#

Having versions not start conflicting if multiple deps use it

keen patio
#

Right, but if the versions (git tags) follow Bevy releases, with bug fixes exclusive to main, that probably would not matter much

native mesa
#

but having a "if you really want to try it, add this line" would be nice.

#

git dep can probably do that.

keen patio
#
# main
bevy_proto_bsn = { git = "https://github.com/bevyengine/bevy_editor_prototypes.git", branch = "main" }
# 0.16 (does not exist yet)
bevy_proto_bsn = { git = "https://github.com/bevyengine/bevy_editor_prototypes.git", tag = "bevy-0.16.0" }
#

Something like that should work

keen patio
#

Currently working on improving the BSN syntax (it's very hacky rn, missing some vital things like nested patching) and macro hot reloading

#

Also been thinking about how to add relations into the mix. Maybe I'll think up some syntax similar to the related!-macro as this is not covered in cart's original proposal

snow wedge
river geode
native mesa
snow wedge
#

It's nice that ui and scene stuff will be unified, but as someone who wants it just for scene stuff right now it's unfortunate that we're blocked on ui experimentation

river geode
native mesa
keen patio
native mesa
#

you need to use anchors/names imo

river geode
native mesa
#

it does not

keen patio
#

yeah my initial focus would just be on parity with related!

#

for other kind of relationships one should be able to simply add relationship components constructed using an entity path:
ChildOf(@"OtherNamedEntitySomewhereElse")

river geode
#

I'm sure m2m relationship will also cause more complexity as well

#

I can definitely see wanting to define a scene where I define an entity/bundle once and link it in a couple m2m places

keen patio
keen patio
keen patio
#

Or to keep things consistent with the new spawning APIs, maybe the children block itself should go in the "bundle"

#
(
    Node,
    Observers[
        // ...
    ],
    [
        Text("Child")
    ]
)
river geode
river geode
#

(my example was incomplete and bad, edited above to be more clear)

keen patio
#

Hehe now it makes more sense!

#

Here is what I tried to express in my first example, using your explicit Children:

(Node, Children [
    (
        Text("Hello BSN!"),
        SomeRelationship [Foo, Bar],
        Children [Text("Regular child")],
    )
])

And if we make [] default to Children (I'm not sure I'd want to be forced to type out Children every time):

(Node, [
    (
        Text("Hello BSN!"),
        SomeRelationship [Foo, Bar],
        [Text("Regular child")],
    )
])

Still a bit of a downgrade from pre-relationship bsn though 🤔 I really like the clean syntax with opening bracket on the same line as a single component/small bundle, keeping indentation down

Node [
    Text("Hello BSN!) [
        Text("Regular child"),
    ]
]
river geode
#

(also as someone who uses xml constantly at their day job and is dealing with that now, STILL BETTER THAN XML)

split harness
dapper sky
snow wedge
dapper sky
#

I don't have the time for that kind of thing, alas. I may just wait out the time it takes for design tooling to pop up, or at least wait out enough time that I no longer feel burnt out and epistemically overwhelmed by how many things I need to track via their position in a text editor and can take some swings.

#

I'm scratching together a bit of a "bevy in burnout" blog post atm, but what I don't mean to do is undermine.

snow wedge
#

Not everyone wants to build tooling before they can get to the making the thing part

keen patio
#

I've encountered a small annoyance in implementing nested patch support. Because structs and enums are ambiguous, while the macro needs to keep them apart, I decided to impose the following rule:

Struct paths may not contain more than a single segment (aside from QSelf and generic params). Any paths with ::-delimited idents are treated as enums.

No biggie, just means you'll have to use the structs beforehand, which makes the templates cleaner anyway.

We also have functions/methods. So I added another rule:

Struct types and enum types/variants have to be capitalized. A path ending with a lowercase ident followed by parentheses, e.g Val::px(..), will be treated as a function/method.

This should also be fine, as long as codebases follow the naming conventions, which I have yet to see a rust project that doesn't...

But! There are also constants in rust, which might also be ambigous. And they do usually start with a capital. I initially figured I could set them apart by checking for all caps, but I don't think that's a good idea as pascal case names (structs and enums) might be all caps too.
What this means syntax wise is that nested patch constants that are not a single imported ident, e.g. BorderRadius::MAX, need to be surrounded by braces for BSN to treat them as expressions:

TextColor({css::RED})

Not super pretty, but maybe workable.

For some types like colors it works to just import the single idents instead, which would not require braces. Though for certain things (especially components like BorderRadius) it looks a bit ugly.
Curious what others think, or if someone has any better ideas.

If this becomes an issue in the real BSN implementation, maybe we should re-think the way constants are defined for bevy types.
Maybe they should be functions instead (BorderRadius::max()). This would also ensure they work with function reflection, without needing a separate data/const registry. 🤔

somber rivet
#

Hey @keen patio , proto_bsn is very cool. I wanted to ask if bsn-templates-as-fields was supported. For example, say I want to store a bsn template on a component to be spawned later, like so:

SpawnEntity((
    // components
) [
    (
        // a child
    ),
]),

... and later I could just do commands.spawn(spawn_entity.0) for instance

#

Or if its not supported is it part of the bsn spec?

keen patio
#

Hmm I'm not sure I follow completely.. Do you want to take one child from a template and spawn just that child?

somber rivet
#

I want to use bsn to spawn an entity with a component. That component would store a template to build an entity. Or a handle or whatever is necessary. The point being I could define the template in place in the bsn. Right now I'm theorycrafting something like this:

(
    AffixMetaData {
        affix_type: "Test",
        tags: [],
        weight: 0.0,
    },
    MeditateLevels(1.0),
    GrantsStats({
        "IncreasedLife": 25.0,
    }),
    Requirements([
        "root.Level >= 1"
    ]),
) [
    (
        InvokedCue,
        TargetNearby {
            radius: 10.0,
        },
        Repeat {
            repetitions: 5.0,
            delay: 0.1,
        },
        Range {
            max: 20.0,
            min: 4.0,
        },
        SpawnEntity(( // <- holds the definition of a projectile entity. Later this will be accessed to spawn an instance of this projectile.
            Origin::InvokerHead,
            ProjectileEffect,
            Damage {
                min: ..,
                max: ..,
            },
        ) [
            (
                OnCollideCue,
                AreaEffect::Sphere {
                    radius: 5.0,
                },
                Damage {
                    min: ..,
                    max: ..,
                },
                StatEffect({
                    "parent.ProjectileLife": -1.0,
                }),
            ),
            (
                Requirements([
                    "parent.ProjectileLife <= 0"
                ]),
                CueDespawn::Parent,
            ),
        ]),
        Requirements([
            "self.Charges > 0",
        ]),
        Stats,
    ),
]
#

Or would it be SpawnEntity("projectile_definition.bsn") and then I just handle that stuff myself with the projectile definition in a different file?

keen patio
#

Right! There’s no way to inline BSN like that currently, unless this is a macro in which case it may be possible somehow by calling the macro again in a braced expression and storing the result somehow.

But it looks like we’re talking assets here. I think you’ll have to keep them as separate files for now.

#

Have you seen the Prefab component? Might be useful

somber rivet
#

Okay good to know. Still looks pretty good. Slightly disappointing and I'd love to see that capability eventually. No I haven't seen Prefab. I'm trying to search for it and I'm not finding anything. Or more accurately its all commented out lol

keen patio
#

I’m not all caught up on the new entity cloning stuff, but that might be another option. If you can keep the template-projectile as a child and just clone that 🤔

#

Looks like you’re in the wrong repo 😅

somber rivet
#

Oh my bad

#

Yeah this seems similar to what I'm looking for

lime veldt
#

Can someone link me what this related! macro does?

magic belfry
lime veldt
#

Thanks, did not know it is on main :)

magic belfry
#

yeah it was added with the improved spawning PR

tame salmon
#

with the new Construct stuff, will you be able to require components using a function which constructs them like

#[require(Mesh(my_mesh))]
struct MyComp;

fn my_mesh(mut commands: Commands) -> Mesh {
  Mesh(<create mesh using commands.construct>) // this requires access to ResMut<Assets<Mesh>> currently
}

?

tame salmon
#

is there a place where i can see when different features in the proposal might be added?

magic belfry
#

With the bsn! macro, we'll still be able to use constructor methods, right?

bsn! {
    (
        Player,
        Transform::from_rotation(Quat::from_rotation_z(PI)),
    ) [
        (Camera3d, Hdr)
    ]
}

asking because all the proposals I've seen just manually specify fields everywhere, like this

bsn! {
    (
        Player,
        Transform {
            translation: Vec3 { y: 10.0 },
        },
    )
}

but for hand-written BSN that would be basically unusable for sooo many types that rely on constructor methods. I don't imagine anyone wants to write out xyzw manually for quaternions, or define matrices by hand

#

I believe embedding Rust code like {a + b} works in some places at least, but having to do that everywhere would still be ugly for this

#

also does method chaining / builder-style component construction work? e.g. LockedAxes::ROTATION_LOCKED.unlock_rotation_y()

#

of course not in the .bsn asset, but in the macro

quartz meteor
#

I'm also curious about this. If we have access to the expression we could parse Block as a special case:

// construct magic 🪄
translation: Vec3 { y: 10.0 },
// accepted verbatim
translation: { Vec3::new() }

Something like this can also be used to determine what can and cant be hot reloaded without recompilation.

split harness
#

My bsn! impl has supported block expressions for years now, but not the component position (only in field positions).

Supporting constructors like that in bsn! is interesting though because it is ambiguous with some::mod::TupleStruct(10). So we need to be opinionated about it by relying on rust naming conventions to disambiguate

#

Which imo is perfectly fine

#

/ we already need to do that for enum identification

#

Or alternatively, we could use component-position block expressions. We should probably support both actually

wind dock
#

Something I’ve wondered about is allowing users to register helper functions with the BSN system and using them in BSN files.

Adding full expression support seems like a rabbit hole and a non-starter since that’s basically a mini bevy scripting language.

But just helper functions would be useful and potentially allow a lot of functionality. Like letting people create their own expression language and passing it to the registered helper function.

#

Perhaps even BSN macros. Bsn would just recognize the macro and pass the contents to the helper function

split harness
#

One of the major motivators for the function reflection feature in Bevy Reflect

dapper sky
wind dock
wind dock
copper dragon
#

so there was talk few days ago in ecs dev, the editor needs compile time component registration to ensure we can ensure loading bsn files properly, or at least force users to register everything in their various plugins and wondering if you already have this impled for your bsn stuff so far?
It was said it was wanted just not sure if youve got a impl in the works already

#

Or do you have an alternative solution to that?

copper dragon
#

@split harness

split harness
copper dragon
split harness
#

Note that types have recursive registration, so you only need to register top-level types

copper dragon
split harness
#

Querying World information should only be used for runtime-inspectors. Not static editors

#

The TypeRegistry is the canonical place for getting information to be used for things like the editor + scenes

copper dragon
split harness
#

Manual registration is of course suboptimal user experience. Take it up with the Rust folks for not supporting better reflection, "life before main", and/or linking across all platforms

#

That being said, @tawdry owl's solution in the PR linked above may be good enough for us, despite the tradeoffs

tawdry owl
# split harness That being said, <@295556276346290177>'s solution in the PR linked above may be ...

Automatic type registration in 15030 should work for the editor design proposed by @copper dragon just fine since all types will be registered regardless of the plugins used. Well, top-level types with generics will still be missing but I hope this won't be too much of a problem in practice. While we're at this topic, it would be nice to know if my proposed fallback solution is good enough sooner rather than later if possible. I'd like to avoid having this feature miss another release cycle because I didn't find a viable solution in time. Not to put too much pressure, I understand this feature has a large impact on the ecosystem and will be painful to roll back if something doesn't work out!

split harness
rare whale
#

@split harness @thick slate My recent work on bevy_feathers has really crystallized my needs for the next stage of BSN. What I am about to say is stuff that you already know, but for purposes of clarity I'll risk being redundant. Working with the existing bundle effects API, I find myself struggling with four specific pain points:

  • How to pass child entities as a parameter to a template function (like the label or icon for a button)
  • How the template can access the theme (a resource) without requiring the user to pass it in explicitly
  • How the template can access assets (like fonts) without requiring the user to pass in a reference to the asset server
  • How to build a template which is opinionated about most styles, but which allows the caller to customize some style properties. An example is a button with a fixed height, but which allows the user to specify flex width
nocturne lotus
#

fwiw on the first point, i've been doing fn container(children: impl Bundle) -> impl Bundle { (A, B, C, children) } and then calling it with container(children![...])

#

it lacks static guarantees, but it works for now

quartz meteor
#

i went with astro-style slots in beet

#[template]
fn App() -> impl Bundle {
  let title = ReactiveApp::resource::<AppTitle>();
  rsx!{
      <Container>
        <h1 slot="header">{title}</h1>
        <p>children go in default slot if unnamed</p>
      </Container>
  }
}

#[template]
fn Container() -> impl Bundle {
  rsx!{ 
    <header>
      <slot name="header"/>
    </header>
    <slot/>
    <footer>
      <slot name="footer"/>
    </footer>
  }
}
thick slate
native mesa
#

very exciting!

rare whale
#

@thick slate @split harness @karmic rock wanted to follow up on my comments on the reactive systems params PR. Seems like there's a couple of different design patterns we're discussing: in my previous experiments, it was "micro-systems", swarms of tiny system-like functions that handled reactions for a single entity, whereas what cart was describing was more like a classic ECS system which operates on large numbers of entities in a tight loop. While I don't really understand flecs monitors, Alice was explaining them to me in a way that makes me think they might be relevant.

#

For something like the widgets, where I am willing to give up colocation and instead code a global system, what I basically want to know is, for the set of entities that meet some criterial (e.g. buttons or sliders), did any of them mutate in a way that requires me to rebuild the visuals, with the understanding that a given visual aspect may be derived from mutations on multiple components?

#

And in particular, if a bunch of components changed all at the same time I don't want to rebuild the visuals multiple times.

#

More concretely: for a button, the background color property is a function of (disabled, pressed, hover, and color variant). Change any of those and the background color needs to be recomputed.

karmic rock
rare whale
karmic rock
#

Yup I agree, monitors don't sound like a great tool for that use case

rare whale
#

The approach I used the last go-round was to say, forget about change detection entirely, just poll all three and then memoize the result of the computation. (And do change detection on the memo)

#

Which is kind of brute force

copper dragon
#

Do yall think itd be possible for bsn to be used to build the system graph?
Im imagineing a system's node graph tool for the editor that lets you visually order your systems.
Rn current capabilities only would allow you to view a graph of the systems.

raw mesa
#

With systems as entities, I don't see why not. Users would still need to add their systems anyway though, so I'm not sure how much value that would have IMO

copper dragon
raw mesa
#

I totally agree with a visual tool for showing the graph, but I still think we should be using code to add the ordering requirements. Like spotting issues we absolutely need better tools, but I'd rather not have two separate flows for adding system ordering (code vs bsn)

thick slate
#

Yeah, I would want it to be possible for weird modding use cases

#

But I would prefer a single clear way of doing it

raw mesa
#

Agreed, and that's why I'm in favor of systems as entities, since it should allow us to change these orderings at runtime. So you could build a visual tool to do this, I'm just not convinced it should be part of the official editor.

#

Actually I suspect bsn wouldn't be helpful here anyway since you need to look up the system entity anyway, so you're not really spawning anything. I might be wrong though

copper dragon
raw mesa
copper dragon
thick slate
copper dragon
raw mesa
dapper sky
#

I think the editor should really have an LSIF consumer & graphical viewer, which also isn't that hard to do what with the quality of what rust analyzer outputs in that format

copper dragon
white lichen
#

what if the editor edited the code

raw mesa
raw mesa
# white lichen what if the editor edited the code

That's very hard to do well IMO. You might just end up with "system_ordering.generated.rs" with a random collection of system ordering rules. I don't think anyone will want to look at that file, so I don't think it solves the problem

copper dragon
thick slate
lime veldt
#

we already track locations of spawns (if you activate the expensice feature). Can do the same for added systems and configs. Likely is way less expensive as it only affects building.

karmic rock
# thick slate

shareable code by end of week: core scene system, BSN macro, templates
did this already happen somewhere? 👀

thick slate
latent crystal
#

Hello -

I learned that there is a BSN prototype for Bevy 0.16.1. I'm mostly interested in the BSN asset file format for modding purposes. Is there a place I can read about the current support for this file format? I'm deserializing a lot of JSON files in my game, and preferably I would like to make the transition to the BSN file format as soon as it is feasible.

Personally, I think I would consider it completely usable in my case if I could register my own types with BSN. For example, if I could textually describe a Units {...} resource in a .bsn asset and have it immediately inserted into my world at boot. That would be fantastic.

copper dragon
#

random thought

Bsn violates Rusts pub bounds?
Bsn can evaluate all components, meaning even private components not intended for direct use can be accessed via manually written bsn files.

#

is this desired or should it be prevented?

lime veldt
#

eh this is also true for types that impl Reflect or serde

copper dragon
lime veldt
#

as long you don't blindly expose everything to scripting of the players I think it is okay and responsible for the programmer to know which values to leave alone

#

not sure how cheat tools play into this though

rare whale
#

The answer I would give is, "there's more to a field than just it's type". To edit a field in a general way, you often need additional metadata - hints to the editor. This may include information that tells the editor this field should not be edited directly. Or it may need to be edited in a restricted way. For example, suppose we have a data structure that represents a finite state machine, where the state ids are strings. Ideally, users won't be able to type in just any text, but only valid state ids.

#

In my JavaScript editor, I gave it the ability to register additional "field type handlers". So for example, if the field being displayed is type String, there was a default generic string editor - but a different handler could look at the metadata and go "Oooh! Oooh! I know how to edit this one!" and build a custom editor that's different from the default string editor.

thick slate
queen oak
#

and serde!

thick slate
cursive trellis
#

what is the future of updating UI ? my current goto way is to create a marker component for each entity that must be updated, and then write a system that query for it. I wish I could skip the marker component and directly inline the system inside the bundle

thick slate
#

Exact designs are still be worked out

#

But there's consensus that we'd like this

queen oak
#

Hehe _ _

#

hehe _ _

#

blepmlemhehe _ _

quartz meteor
# cursive trellis what is the future of updating UI ? my current goto way is to create a marker co...

In the meantime using an Observer + BundleEffect can at least give you that inline style:

fn Counter(initial: u32) -> impl Bundle {
  (
    Value(initial),
    Text(format!("the value is {initial}")),
    EntityObserver::new(|
      ev: Trigger<OnClick>, mut query: Query<&mut Value, &mut Text>
    |{
      let mut (value, text) = query.get_mut(ev.target()).unwrap();
      *value = value + 1;
      *text = format!("the value is {value}");
    })
  )
}
cursive trellis
latent crystal
cursive trellis
latent crystal
#

Got it, ty

keen patio
# latent crystal Just going to bump this, in case anyone knows of anything. I was told elsewhere ...

There is an unofficial (and unsupported) prototype over in bevy_editor_prototypes: https://github.com/bevyengine/bevy_editor_prototypes/tree/main/crates/bevy_proto_bsn

It does implement Construct/Patch and has partial support for BSN assets. Currently it only supports entities though, so no resources. But if you're ok moving the "Unit" to a singleton entity you would be able to store it as .bsn with hot reloading

latent crystal
#

@keen patio Thanks 🙂 I did take a gander at the prototype.
Although, I think I also heard "entities-as-resources" might become a thing in the near future? I may just wait for that to fully commit

#

"resources-as-entities"*

keen patio
#

Yeah I'm pretty sure that is the plan at some point!

quartz meteor
cursive trellis
split harness
#

Hey y'all! Quick BSN status update. Still putting the finishing touches on my initial bsn! code drop so others can start establishing opinions / we can iterate together. Was hoping to have it ready by the end of last week, but there was a bit more last-mile work to make it actually usable in real world scenarios. Should be on the order of days, not weeks at this point.

thick slate
split harness
#
  • Templates (an inversion of Construct that is more flexible)
    • Template trait
    • Template derive
  • Scene system (this does not include the BSN asset format)
    • Scene traits
    • DynScene
    • Scene patching
    • Scene assets (currently in-memory scenes, not loaded from disk)
    • Scene inheritance
      • Assets
      • Functions that return impl Scene
  • bsn! macro
    • Define scene patches for structs and enums
    • Entities can inherit from scene assets and scene functions
    • Constructor syntax in template position
    • Inline template syntax (supports constructors too)
    • Embedded rust expression syntax
    • Optional top level flatness (no () wrapper needed for root entities)
    • Relation spawn syntax (ex: Children [ A ], Observers [ A ], etc)
    • Children syntax sugar ([] can be used instead of Children [])
  • Template field syntax and inner representation, as defined in my last proposal (currently considering cutting this for reasons I'll discuss in a writeup, but it will be included in my code for consideration)
white lichen
#

holds breath

split harness
#

It could be split in roughly that way, but I'm not going to start with PRs right out of the gate. I'd like holistic feedback first as people try to use it in actual scenarios

#

I don't want to ship something until something like @rare whale's Bevy Feathers work can be ported over. We'll likely need to iterate for a bit.

thick slate
#

As long as we're not pressing me on 30k lines at once I'm happy with that

native mesa
karmic rock
split harness
#

Unlike Construct, they could likely host reactive implementations like @rare whale's fine-grained signal-based reactivity.

The outputs also have "diff potential" for that style of reactivity.

But yeah thats explicitly not covered by the initial changeset

keen patio
native mesa
karmic rock
#

Curious to see how the input -> >>transformation<< -> output is expressed / if it's in any way similar with what I came up with

rare whale
split harness
#

It (currently) relies on ensuring the injected asset has an asset path. Obviously assets loaded from disk will have this. But it can also be done for in memory assets via my newly added asset_server.add_with_path(). Not yet convinced thats actually the right API, but it does work

thick slate
split harness
rare whale
# split harness Yup!

My next question is: what are you standing around talking to us for? cracks whip...

split harness
#

In practice that will cause problems for more complicated scenarios

copper dragon
split harness
copper dragon
thick slate
split harness
# thick slate Ooh virtual file system nonsense?

Yeah ultimately I think registering a specific new virtual filesystem source (ex: virtual://) is the move (which we notably already have all of the pieces for), rather than letting things pretend they have a path when they dont. add_with_path is really just a quick hack for prototyping

thick slate
#

Good hack for prototyping though!

split harness
thick slate
split harness
split harness
raw mesa
#

I might not have the full context here, but it sounds like we're saying that if the asset source string is virtual, don't bother doing any loading and just use the "preregistered" asset? If that's the case, I'd much prefer making this asset path thingy for bsn be an enum - we already kinda want this for Uuid assets

#

For prototyping its fine either way

#

But my ideal is that we have more ways of defining asset handles than trying to hack in a special case

split harness
raw mesa
#

Oh I see

split harness
#

Similar in function to the embedded:// asset source

raw mesa
#

Yeah that's what I was picturing haha

#

I'm a little skeptical that users will want to store PNG bytes in the virtual filesystem?

split harness
#

I don't think virtual is the right name for that source fwiw

raw mesa
#

When I heard "in memory assets" I pictured an asset that was Assets::add-ed

#

Which is why I was picturing avoiding the asset loader entirely

#

and why I didn't think storing PNG bytes made sense

split harness
#

We want to store the Image directly

raw mesa
#

Yup

#

I'd ideally like whatever we store in BSN to look like:

enum HandleDef {
    Path(String),
    Uuid(Uuid),
    Name(String), /* arbitrary string */
}
#

Though maybe Uuid is redundant in that scheme

split harness
#

Yup that could work. Although we could also go the route of making virtual:// (or whatever it ends up being called) resolve differently

#

As from a caller perspective its AssetPath -> Image

raw mesa
#

That was the hack I wasn't excited about. It's doable, but I think it would require the asset server having a magic string saying "if the source is virtual" abort early

#

Though maybe I'm thinking too small lol

split harness
#

The "problem" right now is that we assume that AssetPath -> Image involves doing the AssetReader -> AssetLoader -> Image internally

#

which is "too opinionated" to enable this scenario

#

So perhaps we just need to make it less opinionated

#

Open question. Short term add_with_path works fine, and HandleTemplate::Name(String) is certainly a fallback option if nothing else works

#

But yeah thanks for calling that out. A MemoryReader asset source wouldn't be enough in this case 🙂

raw mesa
#

Hmmm ok. I see what you mean. I'd need to think about this situation more though

split harness
#

Yeah I haven't given it much thought yet either (clearly)

#

I know enough to know its a solvable problem without too many unknowns

rare whale
raw mesa
#

Maybe just runtime though that could be confusing haha

keen patio
cobalt stone
#

Also makes hot reloading trivial

native mesa
#

Same. This sounds like basically everything on my wish-list.

karmic rock
cobalt stone
#

Output

rare whale
#

Those aren't the only two options

#

React's VDOM is an intermediate tree, between input and output. It's there specifically because diffing real DOM nodes is both hard and slow.

silver bramble
dapper sky
cursive trellis
#

dioxus uses intermediate representation as well for diffing, with the added benefit of automatically grouping « static » sub trees so they are never being checked for change. but tbh just diffing the bundle should work perfectly fine

keen patio
#

It would be pretty neat to have an entity.update(bundle) for this purpose. Storing the bundle id of the previous bundle in a “receipt” component to diff the component sets for removal, followed by simply inserting the whole bundle.

The spawn bundle effects could be extended to support keyed related entities to map to during updates

Very similar to ICBINBSN but integrated into the bundle/spawn system

cursive trellis
#

that’s the design I came up with in my previous experiment with diffing

wind dock
#

The challenge with using intermediate representations and diff trees is that, while sufficient for plain UI like react and dioxus, bevy and flecs are trying to work towards a general-purpose solution that’s usable for more than just UI.

It’s a different ball game when you build a solution just for UI vs cases like 100,000 entities.

rare whale
#

"Diffing the output" has other problems. Output nodes have transient properties that shouldn't be diffed. You wouldn't want to diff the scroll position, or the text cursor position, yet these are properties of the node.

keen patio
cursive trellis
keen patio
rare whale
keen patio
#

The update/reconciliation process shouldn’t touch components that it didn’t add

rare whale
#

All the reactive frameworks I have looked at do some kind of "diffing". Think of it as a spectrum where the extreme left end is "diffing the input" and the extreme right end is "diffing the output". No framework that I know of lives on the extreme right end. React's VDOM is somewhere to the right of the middle, Solid's signals and memos are somewhere to the left of the middle. (Obviously there are many other design differences, but this is trying to look at things through the lens of just diffing.)

#

(Admittely I am using a very broad definition of "diffing" that includes things like memoization.)

keen patio
#

If I know React properly there are two kind of diffs right?

  1. The “reactivity” system diffing effect deps to see if they should run, powering memoization etc
  2. The reconciliation - diffing the resulting VDOM of a render with the actual DOM to produce minimal DOM updates

I see this as a variant of the latter, but with less aggressive diffing (comparing component sets rather than values).

A low level building block that may be used for higher level reactive trees, but also for other purposes like hot reloads or replication

cursive trellis
rare whale
#

The VDOM only contains attributes that are in your react template.

#

It does not have all of the baggage that comes with real DOM nodes.

cursive trellis
#

uh yeah you’re right I got confused

#

then maybe we need separate representation for writing and rendering UI? react has logical « components » which are separate from real dom « elements ». we do not have such split. for example, the existing UI components would be the real DOM nodes, and we could create a virtual representation of Node, Text, etc. which does not include the transient properties, and can be diffed

rare whale
# cursive trellis then maybe we need separate representation for writing and rendering UI? react h...

That is what Dioxus does from what I understand. As does Xilem. I went down this path with Quill. The question then becomes, what does this data structure look like? Is it made out of entities and relations, or something else? For Quill I followed Xilem's example and made the intermediate data structure out of nested tuples: very efficient (only one memory allocation per template), but hard as hell to look at in the debugger.

cobalt stone
crisp garden
#

Definitely agree that when done well this workflow is great though

#

When done poorly it becomes a nightmare however ferris_spooky

wind dock
#

Ultimately reactivity/scenes are about making bulk creating/updating/deleting entities and components easier, cleaner, less boilerplate-y, less error prone, etc. Better to work with overall.

karmic rock
wind dock
karmic rock
wind dock
karmic rock
karmic rock
wind dock
karmic rock
#

That's fair, but also that'll be its state perpetually :p

wind dock
#

I hope after a bit it’s no longer first steps but i get what you mean

rare whale
#

Once we get Template, I'm thinking I might adapt my thorium_ui reactive framework to it. Whether that will become an actual crate is TBD. Thorium is unlike my other experiments (quill and bevy_reactor) in that it doesn't track mutations - it uses a kind of brute force approach where it polls some variables every frame, and then calls an effect function whenever those variables change. The benefit of this approach is that it's completely agnostic to whatever templating system you want to use. Using bundle effects and relations, we can attach a calculations![] to any entity, which contains your reactive effects.

#

I don't think it's the ideal reactive solution - I think more efficient solutions are possible - but it's simple and works pretty well for a large number of use cases.

cursive trellis
wind dock
#

As someone who’s been around here for awhile (specifically trying to avoid speak for maintainers), any solution that special cases UI components is likely a non-starter.

lusty epoch
#

I am unsure about using the same "reactive computation" primitives for both UI and games in general.

The reactive UI frameworks are almost "incremental computation" (the result of the computation on data change should match as if you are computing from scratch).

Games in general are not incremental computations. (They are simulations, and there are sometimes Events)

(I am only familiar with React, I am not sure if Vue etc. can be viewed as "incremental computation", but I very much prefer frameworks which can)

rare whale
#

First of all, reactivity != incrementalization

#

A framework can be reactive and not incremental, or incremental and not reactive. React, Soid, Vue, Svelte and so on are both.

#

When we talk about "diffing" we are talking about incrementalization, not reactivity.

#

You could have an incremental framework with a VDOM that was not reactive: it patches the hierarchy whenever you manually feed it new parameter values.

#

You could have a reactive framework which is not incremental: every time it reacts it rebuilds the hierarchy from scratch.

golden notch
rare whale
lusty epoch
#

What I am saying is just incremental computation are very strict. If you can formulate your problem as some computation on some input data, and then the incremental version of the computation matches as if you are computing from scratch, then it's "incremental computation". A "incremental computation framework" should enforce this property.

for example Observers are not a "incremental computation framework", because they don't enforce the rules.

And also games are usually not formulated as computation on input data, so they are not viewed as "incremental computation".

UI (at least in React) are mostly incremental computation

cerulean mango
#

I’ve been dabbling around with bevy_proto_bsn and I think it’s fantastic to get automatically hot-reloaded incrementally patched data on your entity hierarchy, no matter if it’s UI or physics objects or a material.

keen patio
cerulean mango
#

And just to make clear, I also have a lot of stuff reacting to these patched changes 😜 would be super nice to have ergonomic syntax

magic robin
#

Has there been any consideration for macros/placeholders/variables in BSN? I think it would be very useful to be able to preprocess a BSN to substitute placeholders with concrete values, and perhaps remove definitions behind an #if statement if the respective flag is false. similar to shader defs

#

then, one could create BSN files representing scenes based on game config, e.g. by supplying a runtime-chosen radius for an Explodes component, or by only spawning certain entities (e.g. enemies) when difficulty is hard, etc

#

to be specific, I am talking about .bsn files, not the bsn! macro which seems to have support for expressions considered...

dapper sky
#

I'm wondering if that would get too much in the way of it being a predictable and stable scene format?

#

i.e. if I'm editing a level in The Bevy BSN Editor(tm) am I editing the Hard level or the Easy level

#

if both are the same file but with preprocessor declarations in it

#

or if it's perfectly in line with things like "this observer name is registered with the bevy runtime so don't worry about it" that's already planned I believe?

magic robin
dapper sky
#

It might be that for specifically the difficulty thing that this kind of switch in behaviour is done with i.e. giving different entities different tags that may or may not overlap.

magic robin
#

I'm coming from a place of thinking:

  • game configuration is just data without logic
  • ...so it can be expressed by resources and components in the game world
  • we can spawn into the world using BSN - so BSN would be the ideal Bevy-native config format
dapper sky
#

it may very well be that the components are the preprocessor

#

though I can see people wanting and making .bsn.preprocess_me

magic robin
#

yep, I'm tempted to now...

dapper sky
#

I very much come from this at the angle of ".bsn is a format a designer (the person or persons) can consume and produce through tooling"

dapper sky
magic robin
#

With your idea of BSN, I'd be curious to get your opinion on whether it could be the right tool for what I'm trying to achieve.

I am making a modding system similar to Factorio's, where mods initially populate a config world in 3 passes, manipulating tables via Lua code. I want the same, but was thinking about instead of storing everything in Lua tables, to store it in a config world via components and resources, with mods defining BSN patches that are applied on top of each other.

Each patch must be able to:

  • insert new entities with components
  • update component values on any existing entity
  • remove components and entire entities
  • have basic conditionals to react to user settings (difficulty, mod-specific feature toggles, etc)

Advantages I see over Lua:

  • bevy-native format
  • components are type-checked
  • don't need to duplicate types in Lua and Rust

Disadvantages:

  • BSN might not be what I'm asking it to be
  • not as flexible logic-wise
dapper sky
magic robin
#

I am unsure if I'm just seeing nails everywhere after reading about BSN - maybe it would just be easier to have a Lua-based table population system. Maybe that even makes more sense than having configuration live in a world.

magic robin
dapper sky
#

I don't have enough context on what people can achieve with the factorio modding system.

#

however if you wanted to get modding tools "for free" by bundling in BSN tooling that's aware of your types I can see it.

lusty epoch
# lusty epoch What I am saying is just incremental computation are very strict. If you can for...

more on this:

different incremental computation domains has very different data model / implementation details.

  1. libs for general "application state management" library, like mobx-state-tree
  2. libs for UI, like React
  3. libs for "incremental compiler", like Salsa

I would say there isn't a system that covers all domains, and as efficient as the specialized libs. I feel the reason is the data model and computation they tend to perform are different compared to each other.

And also I am not sure "reactivity" is, for me it means it's event based, and has no strong guarantees compared to a "incremental framework". I think they have there intended usages, but I much prefer a system has strong guarantees for the domains that fits IC.

split harness
#

Hey yall another quick update. We're getting very close now. Today I:

  1. Improved autocomplete coverage by writing some custom expression block and closure parsing logic. Autocomplete now works in those places! Not supporting autocomplete in those cases would have been a non-starter, so I'm glad I finally banged this one out.
  2. Made enum patching a bit more nuanced. The big "problem" with enum patching is that enums (even those deriving Default) don't have a built-in way to produce a default value for arbitrary variants. To solve this, the template derive produces these values, enabling patching of arbitrary template enums (ex: if the enum matches, patch on top. If it doesn't, create a new default instance of that variant and then patch on top). However for non-derived template enums (ex: the blanket impl<T> Template for T where T: Default + Clone ), defining patches still assumed the presence of these variant constructors. This meant that every enum value used in a patch had to either derive template, manually derive VariantDefaults, or use an expression wrapper (ex: field: {Val::Px(10.)}). In practice this was gnarly, as the majority of the Rust ecosystem does not derive VariantDefaults. So to solve this I made the slightly opinionated choice that top-level (aka "component-position") templates are "patchable enums" that assume the presence of the default variant constructors. But any enum value nested inside is assumed to be a full expression of the fields (by default). In the future we can devise a manual syntax for nested enum patching if we so choose (or go back to the "patch everywhere" approach by default and require {} wrappers for enum types that don't implement variant constructors).
#

I know I said we're on the order of weeks days and not days weeks, and tomorrow it will be a week since I said that. I miiiight be able to get this out tomorrow. I have a lot of writing / explaining to do, merging with latest main, and some minor polishing to do, so it might drag on past that. But I swear I'm wrapping this up!

rare whale
# split harness Hey yall another quick update. We're getting very close now. Today I: 1. Improv...

It would be useful for me to know the rough shape of the Template trait. Some things I would like to know are:

  • How does it handle root trees vs. trees that are rooted at a pre-existing entity. In my version of Template I didn't support the former, you had to have a pre-existing parent - kind of like spawn_with.
  • Whether there's a context parameter of some kind.
  • Whether I can do trait extension on the context parameter to do RAII-style things.
#

I'm wondering whether it's possible to hook in to the template lifecycle to track things other than entities and components - like being able to register a one-shot system that gets unregistered when the template instance despawns. This is relatively easy to do if you have a parent entity, but less clear if the thing holding on to your template instance is not an entity.

#

Normally I would use a pattern like observers or relations, where each of the entities created by the template is a potential owner of additional resources. However, that gets tricky when the resources are referenced by multiple nodes in the tree, reflecting cross-cutting wiring.

keen patio
# split harness Hey yall another quick update. We're getting very close now. Today I: 1. Improv...

Enums are tricky indeed. The variant constructors sounds like a nice solution where it can be derived/implemented!

I've done some thinking on this subject while experimenting in proto_bsn. One idea is to rely on Default for the field types rather than the variants. It might be useful for cases where we don't have the variant constructors, like non top-level patches. Something like this:

  • All fields in a patched enum variant has to be listed (too bad we can't query type info in macros 🥲 )
  • The values may be "omitted" by using a special token (for example _),resulting in Default::default() during expansion.
  • A "fallback" value may be specified (e.g. _ | <fallback>) for fields whose type does not impl Default or where one wants a different fallback.
bsn! {
    MyEnum::A {
        x: 5.0,
        y: _,
        z: _ | 0.0,
    }
}

// Patch expansion:
if let MyEnum::A { x, .. } = &mut __props {
    *x = (5.0).into();
} else {
    *__props = MyEnum::A {
        x: (5.0).into(),
        y: Default::default(), // default fallback
        z: (0.0).into(), // custom fallback
    };
}

Not the prettiest, a bit inconvenient that one has to list all the fields, regardless if they are patched or not. But at least it allows patching of any enum, regardless of whether it has the variant default constructors.

A side note: The "placeholder" (_) syntax could potentially be utilized in other places, for example when patching a single value in a tuple struct: MyTupleStruct(_, _, 5.0)

keen patio
#

While on the subject of syntax, I believe a clear distinction of top-level (components, type unknown) and nested (type known) patches is important and useful, as it allows some pretty nice DX when we can apply slightly different syntax rules for them.

For example, in nested patch contexts, we could allow omitting the type path of a named field struct, resulting in neater templates:

bsn! {
    Transform {
        translation: { x: 5.9 },
    }
}
split harness
#

How does it handle root trees vs. trees that are rooted at a pre-existing entity.

Templates are always applied to a specific entity. They have no clue about the wider context. A Scene is a collection of things (usually patches) that produce Templates to be applied to a specific entity (merging the traits is possible and I've done that in the past, but I think the split is useful functionally and conceptually ... we can talk more about this once the code is out and you've read the associated writeup).

If you want to define logic for a group of scenes, work with the SceneList trait (and matching bsn_list! macro) instead.

Note that bsn_list! can be used inside bsn!:

let children = bsn_list![
  Node { width: Val::Px(1.),
  Node { width: Val::Px(2.),
];
bsn! {
  Node { width: Val::Px(10.) } [
    Node,
    {children},
    Node,
  ]
}

Whether there's a context parameter of some kind.

There is a context parameter. Currently it is just EntityWorldMut, but I suspect once we start playing more with entity identifiers we will want the ability to store intermediate state, meaning we'll want a wrapper.

Whether I can do trait extension on the context parameter to do RAII-style things.

Yup this is doable, although it really depends on what you're trying to do here.

split harness
# rare whale I'm wondering whether it's possible to hook in to the template lifecycle to trac...

I'm wondering whether it's possible to hook in to the template lifecycle to track things other than entities and components - like being able to register a one-shot system that gets unregistered when the template instance despawns.

Template (currently) doesn't have a full CRUD lifecycle (it is just the C ... however to support hot-patching scenarios we will need to at least add the U). That being said, it can be used to do things like register one shot systems. Templates instantiate the runtime data model (aka Entities and Components), so if you want to unregister a Template-registered oneshot system when an entity despawns, one approach would be to do RAII at the component level.

Note that Templates can be used anywhere, but from a scene system perspective they live inside the shared scene itself (which is generally represented as an asset). We don't really want to be duplicating and persisting entire hierarchies of non-runtime init state for every instance of a scene we spawn (which for complicated scenes could be massive). As an aside: keeping the cost low for cases like "spawning a single specific customized/patched entity scene" is something we'll want to investigate. Currently the only option is to "bake" a full new scene by applying the patch on top of the inherited scene. But for the one-off cases we'll want a lazier "only create new template instances for things that have changed in the patch". I think theres also a way to just make this the default behavior (ex: by Arc-ing baked templates and sharing them across inherited scenes). Very doable, it just hasn't been done yet 🙂

#

I'd probably handle the shared-one-shot-system-self-cleanup case in the following way

  1. The Template accepts an IntoSystem impl
  2. When spawned the first time, the Template accesses the World from the Template context, instantiates the one-shot system, caches an Arc<StrongSystemId> on itself, and returns a clone of that in resulting component. The StrongSystemId, much like the StrongHandle inside Handle<T>, can notify a cleanup system when it is dropped.
  3. The next time the Template is used to spawn an instance, it will see it has a cached id and return that. In this way, Template and each instance holds an instance of the Arc<StrongSystemId>
  4. When the Template and all instances are cleared up, the last Arc<StrongSystemId> will be cleaned up and the one-shot-system will be freed. (ex: in the case of a scene asset, when all instances of the scene have been despawned, dropping the last Handle<ScenePatch> and removing the scene with the templates inside).
split harness
# keen patio Enums are tricky indeed. The variant constructors sounds like a nice solution w...

One idea is to rely on Default for the field types rather than the variants. It might be useful for cases where we don't have the variant constructors

This is an interesting thought! Definitely worth considering.

While on the subject of syntax, I believe a clear distinction of top-level (components, type unknown) and nested (type known) patches is important and useful

I've given a fair amount of thought to the "optional nested type name" case. There are some problems:

  1. Unnamed field structs are ambiguous with tuple values
foo: Foo(1, 2)
foo: (1, 2)

Potentially resolvable by treating tuple values as patches too, as the field vs tuple accessors will be the same.

  1. Named field structs without fields specified are ambiguous with expression blocks
foo: Foo {}
foo: {}

We could resolve this by just picking a winner (ex: empty expression blocks are actually empty struct patches, or vice versa). However that will make error cases (which will happen) potentially confusing for developers.

  1. Named field structs with fields require reaching further into the struct to disambiguate between expressions and struct patches.
//   V V at both of the points marked with 'V' we don't know if this is an expr or a struct patch
foo: { x: 1 }

This is only a minor complexity issue from a parsing perspective as we can obviously look forward. However it has implications for autocomplete because when typing x we're still in a quantum superposition of "expression" or "field patch". We need to pick a winner, but a percentage of the time we will be wrong. In those cases, autocomplete will yield the wrong results, which will be painful and confusing for people.

rare whale
# split harness I'd probably handle the shared-one-shot-system-self-cleanup case in the followin...

Have a look at my "owned callbacks" PR: https://github.com/bevyengine/bevy/pull/20041 - this already supports EntityWorldMut, it creates a wrapper around the callback which lives in a satellite entity that is tied to the main entity via a linked_spawn relation. So this could integrate with Template out of the box.

GitHub

Defines a mechanism for registering callbacks which automatically unregisters the system when the parent entity is despawned.

#

That being said, we still need a way to permit inline callbacks, without the preregistration requirement.

#

But that's a more complex problem, which we've already discussed in depth.

keen patio
split harness
silk lava
split harness
# silk lava Is `bsn_list!` just a List of BSN's? Is it not possible to just support like a `...

Almost. Scene is implemented for tuples of Scene (same pattern as Bundle). This means that a single Scene with nested tuples would be ambiguous with a list of Scenes. Therefore SceneList is implemented for tuples of SceneList, as well as EntityScene<S: Scene>(S) wrappers.

That being said, I think having separate traits here makes sense, even if this was solvable (ex: I think in the future variadics could kind of solve this problem if we're willing to give up nesting behavior). There is value in being able to differentiate between "a single root Scene that an entity can directly inherit from" and "a list of scenes, which is not inheritable"

rare whale
silk lava
split harness
# silk lava Dont quite know what inherit means in this context, but given there is a functio...

See the "inheritance" section here for an overview. That bit is largely unchanged since the last proposal: https://github.com/bevyengine/bevy/discussions/14437

GitHub

Preamble My vision for Bevy UI is largely a vision for better Bevy Scenes and an improved Bevy data model. The general angle is: Embrace the "Bevy data model" for both Scenes and UI. Fill...

silk lava
keen patio
#

Hmm I would assume you’d either wrap the whole thing (including path/variant) or each member in double braces. Allowing expressions in that position seems a bit odd

split harness
#

Just wrapped up a merge with main, filled the remaining gaps required to port bevy_feathers to bsn!, and ported bevy_feathers to bsn!. Porting was pretty straightforward. Just had to define a new CallbackTemplate. @rare whale

rare whale
# split harness Just wrapped up a merge with main, filled the remaining gaps required to port `b...

BTW I have a PR which changes the callback protocol a bit: https://github.com/bevyengine/bevy/pull/20086 - I'll need to rebase that after your merge

GitHub

Notifications now include the source entity. This is useful for callbacks that are responsible for more than one widget.
Part of #19236
This is an incremental change only: I have not altered the fu...

#

This is to support the case where a single callback is shared between multiple (similar) widgets. For example, in a color picker, you might not need a separate callback for every color slider.

split harness
#

This is what spawning a bevy_feathers button looks like at the moment:

bsn! {
    :button(ButtonProps {
        on_click: callback(|| {
            info!("Normal button clicked!");
        }),
        ..default()
    }) [(
        Text::new("Normal")
        ThemedText
    )]
}

Some things to note:

  1. The lack of implied default, as currently function arguments are considered to be arbitrary rust expressions. Not yet sure if we should be taking liberties here yet.
  2. The callback wrapper. Afaik a constructor of some kind is necessary, as IntoSystem does not work with into().

I'd like to explore supporting "typed scene inheritance", so we can do this instead:

bsn! {
  :Button {
    on_click: callback(|| {
      info!("Normal button clicked!");
    })
  } [(
    Text::new("Normal")
    ThemedText
  )]
}
rare whale
#

Would it be possible for button to be a trait impl, so we could get rid of the stutter? e.g.

bsn! {
    :Button {
        on_click: callback(|| {
            info!("Normal button clicked!");
        }),
        ..default()
    } [(
        Text::new("Normal")
        ThemedText
    )]
}
#

Ooops I just saw the second part of your message

#

Shooting from the hip again, sigh...

split harness
#

Haha I was trying to find a diff between the two and struggling

#

That being said, I still might push for a move to observers like this:

bsn! {
  :button
  on(|event: On<ButtonClick>| {})
  [(
    Text::new("Normal")
    ThemedText
  )]
}
rare whale
#

Great minds think a lot

rare whale
split harness
#

on works for observers atm, in such a way that could support shared observers across scenes, if observers supported adding additional entity targets at runtime

rare whale
#

Other than that, looks pretty good

#

What does the button() function look like now?

copper dragon
#

Ngl I wish there was a better term the "Props" in the ui world
It infuriated me when learning cause no one would fucking say what they actually were and "props" got reused for 50 billion different things

rare whale
split harness
#
pub fn button(props: ButtonProps) -> impl Scene {
    bsn! {
        Node {
            height: size::ROW_HEIGHT,
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Center,
            padding: UiRect::axes(Val::Px(8.0), Val::Px(0.)),
            flex_grow: 1.0,
        }
        CoreButton {
            on_activate: {props.on_click.clone()},
        }
        template_value(props.variant)
        template_value(props.corners.to_border_radius(4.0))
        Hovered
        // TODO: port CursorIcon to GetTemplate
        // CursorIcon::System(bevy_window::SystemCursorIcon::Pointer)
        TabIndex(0)
        ThemeBackgroundColor(tokens::BUTTON_BG)
        ThemeFontColor(tokens::BUTTON_TEXT)
        InheritableFont {
            font: fonts::REGULAR,
            font_size: 14.0,
        }
    }
}

Note that template_value(x) may ultimate just become x

rare whale
#

Hmmmm

split harness
#

Currently functions in that position are assumed to return arbitrary scenes, not "passed in values that serve as a "whole value patch"

copper dragon
split harness
#

So template_value is returning the impl Scene equivalent of "passed in value that serve as a "whole value patch"

#

But I think that scenario is less common than just passing in your desired component value, so we might want to give that the nicer syntax

rare whale
split harness
#

Node, CoreButton, and everything else in that position are Templates, which have arbitrary world access

#

But notably a Scene is not a function with world access, it is something you pass a world into to produce real outputs

rare whale
split harness
#

Or rather, a Scene is resolved to a hierarchical set of Templates, which you pass a World into to spawn the entities

rare whale
# split harness Makes sense!

The simplest composite widget would be something like "Card" or perhaps "Toolbar" - it's presentational only, no interaction logic, it just imposes a style and holds children.

split harness
#

To help illustrate how you might access World from bsn!, consider this:

pub fn button(props: ButtonProps) -> impl Scene {
    bsn! {
        Node {
            height: size::ROW_HEIGHT,
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Center,
            padding: UiRect::axes(Val::Px(8.0), Val::Px(0.)),
            flex_grow: 1.0,
        }
        // This adds a "function style" template to the scene, which
        // is low-boilerplate alternative to defining a new Template type
        template(|entity: &mut EntityWorldMut| {
          Ok(MyComponent { })
        })
    }
}
split harness
#

Just edited in MyComponent to illustrate a template that produces a component

rare whale
#

Next question: widgets that take other widgets as parameters:

  • Menu takes a list of menu items.
  • Dialog takes a list of buttons to display at the bottom
  • Toolbar takes a list of tool buttons
split harness
#

But in general that type of manual "template function" is unnecessary. Imo we should generally be focusing on the normal "component template" pattern (ex: Node in the example above)

rare whale
#

Although the way I normally do dialogs is that they just take "children", and then have separate templates for dialog header, dialog content, and dialog footer.

split harness
rare whale
#

Anxious to get started 🙂

split harness
# rare whale All right, good

I'm also toying with the idea of "inherited scenes intercepting inheritor children":

pub fn menu() -> impl Scene {
  bsn! {
    Node [
      Text::new("Menu"),
      #intercepted_children,
    ]
}

fn my_menu() -> impl Scene {
  bsn! {
    :menu
    MyMenu
    [
      Name::new("I will be intercepted")
    ]
  }
}
#

Higher weirdness factor, but I think its a more legible / natural user facing API than:

pub fn menu(menu_list: impl SceneList) -> impl Scene {
  bsn! {
    Node [
      Text::new("Menu"),
      {menu_list},
    ]
}

fn my_menu() -> impl Scene {
  bsn! {
    :menu(bsn_list![
      Name::new("foo")
    ])
    MyMenu
  }
}
#

Biggest weirdness is multi-scene inheritance

#

Having multiple interceptions will be hard to reason about, regardless of the approach taken.

split harness
#

I'll prioritize getting this out now that I know its actually usable

rare whale
#

Let me see if I can find a reference.

split harness
cursive trellis
rare whale
#

That is, I want the simplest possible experience for the people snapping LEGO bricks together; I'm not so worried if it takes skill to actually mold the bricks.

split harness
#

In general I'm on team "lets make this as rusty as possible" / commas are used in every other position

rare whale
#

Languages which go overboard on minimalist syntax are IMHO harder to read, as are languages with too much punctuation. The key is to strike a balance.

rare whale
split harness
# rare whale Is there some way we could encode the intent of hijacking the children list into...

One of the things I'd like to consider for "struct-style" scene inheritance is something like:

#[derive(Component, GetTemplate)]
struct Button {
  image: Handle<Image>,
}

impl GetScene for Button {
   fn scene() -> impl PropScene<Self> {
     pbsn! {
        // first parameter must match Self
        Button {
        } [
          Text::new("hi")
        ]
     }
   }
}

This would allow callers to accept impl PropScene<Button>. And we could have an equivalent PropSceneList<Button> trait

#

However I'm not yet convinced of the utility of the impl PropScene<Button> trait over just impl Scene

#

As scenes are still just "patches" , so its not like you could directly read the passed in Button properties

#

So it would largely just be about type safety

split harness
# split harness I'm planning on doing a writeup on why not when I put this out, but in short "I ...

My main points are:

  1. Flat entities (no wrapper tuple) are allowed at the root. In Rust, x, y, z is not a valid expression
  2. Relationships are now expressed as peers of the components (old proposal: (COMPONENTS) [CHILDREN], new proposal (COMPONENTS [CHILDREN]). I think in all cases, they are more legible without commas, especially in the very simple / very common case of SomeComponent [ CHILDREN ]. Comma version of this looks weird, especially without the tuple wrapper SomeComponent, [].
  3. Removing the commas helps visually group entities when they would otherwise be lost in a sea of punctuation. I think that in practice this is extremely noticeable.
#

Also on the topic of (1), I'm considering allowing this for non-root entities as well, at least in the single component with children case, as it saves a lot of space and improves legibility

#

Omitting () in nested cases with more than two entries is very bad from a legibility perspective though

#

This is all very negotiable / we can tweak to our hearts' content

#

Also the relationship syntax has a nasty ambiguity that commas would resolve, so this might be a moot point:

These are the same grammatically:

bsn! {
  Node
  Observers [
    SOME_OBSERVER
  ]
}

bsn! {
  Node
  SomeMarkerComponent
  [
    
  ]
}
#

But there are other ways to resolve that

#

One option (which I don't like) is to remove the [] syntax sugar for Children []

#

Another option (which isn't actually an option, but its how I work around it right now) is to disambiguate in those cases via:

Node
SomeMarkerComponent::default()
[
]
#

Sadly hit this issue late in the game, as I implemented [] sugar late in the game

fallow cloak
#

seems like it would disambiguate but maybe it’s too inconsistent

split harness
fallow cloak
#

makes sense! glad to add something to the bikeshedding pile at least :)

rare whale
#

If we're bikeshedding, here's another idea to throw on the pile: give Children a short name:

SomeMarkerComponent
Observers [ ... ]
@ [ ... ]
#

(For some ASCII character that you think is sufficiently pretty.)

#
SomeMarkerComponent
Observers [ ... ]
🧒 [ ... ]

Where 🧒 is the unicode "child" emoticon 🙂

thick slate
split harness
# thick slate We already support adding additional entity targets at runtime FYI. Just not rem...

What does adding additional entity targets at runtime look like? From my perspective theres the internal observer API (which is not public and only consists of register and unregister methods) and the component-driven API, which only has OnAdd hooks (and does initial setup and bookkeeping for every entity). I'm unaware of any world.add_observer_targets(observer_entity, target_entities)-style APIs (or ways to do that from the component api)

#

Notably, thats why ObserverDescriptor has no add_entity(&mut self, entity: Entity) method (only consuming self methods). We need a way to update "both ends" in a clean way. And to my knowledge that path hasn't been built yet

thick slate
# split harness What does adding additional entity targets at runtime look like? From my perspec...

The internal observer API was semi-public in 0.16, and had public types with no accessors. See https://docs.rs/bevy/latest/bevy/ecs/observer/struct.Observers.html / https://docs.rs/bevy/latest/bevy/ecs/observer/struct.CachedObservers.html for example.

In https://github.com/bevyengine/bevy/pull/19608, I opted to make these read-only accessible, consistent with the other ECS internals, because I presumed this inconsistency was an oversight. Looking at it again, I'm not sure we have a public path to watch entities at runtime 🤔

#

We need a way to update "both ends" in a clean way. And to my knowledge that path hasn't been built yet
Fully agree with this though; I spent a while poking at this a couple weeks back in my relationship-ification investigation. The core data model / source of truth is not particularly clear under the current design, and making incremental changes or even understanding what was going on was challenging.

#

Now that I have a better understanding of the internals, I think that I'm likely to start those changes again from scratch, working more incrementally.

split harness
#

Yeah exposing the internals as read only makes sense. Exposing them as mutable would require reconsideration of the API surface 🙂

#

One path forward is removing the internal "acceleration structure" entirely in favor of components

#

One potential path forward. Idk how that shakes out in practice

thick slate
rare whale
#

@split harness Here is an example of what I'd like to build:

bsn! {
  Dialog {
    on_close: callback(...),
    with_close: true,
  } [
    DialogHeader [
      Text::new("You have unsaved changes.")
    ]
    DialogContent [
      Text::new("Are you sure you want to quit?")
    ]
    DialogFooter [
      Button { on_activate: callback(...) } [ Text::new("Cancel") ]
      Button { on_activate: callback(...) } [ Text::new("Quit") ]
    ]
  ]
}
#

(I might not have the syntax right)

#

DialogHeader, DialogContent and DialogFooter are bsn templates that create flexboxes. For example, the footer creates a flexbox with justify-content set to FlexEnd, and a gap between the buttons.

rare whale
#

@split harness Another question: Let's say I want to invoke the button template, but I also want to patch in some additional components such as Autofocus and AccessibleName - how does that work? Note that these could be overrides / replacements for what button provides.

#

Also, instead of template_value(props.variant) how about double-braces / handlebars style? {{props.variant}}.

cursive trellis
#

is there any progress on reactivity ? is there anything I can do to help ? is there existing PRs or experiments ?

dapper sky
#

initial bsn landing => feedback => working on reactivity, is I believe we're it's going.

rare whale
#

This would be based on my thorium_ui experiments, but stripping out all of the stuff that's no longer needed with BSN. What's left is the computations! macro, Calc component and so on.

  • The upside of this approach is that it works with almost any dynamic data in World: components, resources, queries, etc.
  • The downside is that the approach is brute-force. It uses polling and memoization rather than change detection or signals.
#

The basic idea is that you have swarms of tiny one-shot systems that run every frame, each of which produce a value. That value is memoized, so you only take action when the output changes.

wooden vine
#

Btw what is the "user" release of BSN gonna be like?

#

Afaik we are not aiming for 0.17 as to not delay it

#

Is bsn gonna be available as an external crate during this cycle?

cursive trellis
wooden vine
#

And then by 0.18 it will be a proper part of bevy, I imagine?

rare whale
cursive trellis
silk lava
rare whale
#

One of the biggest causes of design bloat is underspecified or unconstrained requirements, and one flavor of this is hypothetical or speculative use cases which don't actually appear in practice.

#

That is, you end up designing a complex solution to a problem that no-one actually needs solved.

#

It's much better to start by looking at what people are actually doing and then design around that.

silk lava
#

I agree, but is this in response to the idea of using Reactive Systems/Params?

rare whale
#

Like (for example) will we see the evolution of something like Redux?

#

Will we see formalized patterns for Model/View/Controller?

#

All that being said, I just now read that they are coming out with a new version of the Commodore-64, so we can all retire now I guess, nothing more to do.

silk lava
#

Ideally, there would be a low-level abstraction that supports the most mid/high-level abstractions, such that you could build a Model/View/Controller abstraction ergonomically on-top of.
For low-level abstractions, the workflow should still be very ECS'ish, and only gain new semantics as you go further up the chain of abstraction. Hence my bringing up Reactive Systems/Params which I believe to be the low-level impl. (ala flecs Monitors)

Although I do know, that for dev workflows in can sometimes be better to work on specialized/opinionated designs first, and then figure out what/should be abstracted into lower level workflows after the fact.

silk lava
thick slate
split harness
#

(oops hit enter too early)

#
:button(ButtonProps)
Autofocus
AccessibleName("My Button")

If you define fields "normally" they are patches on top of existing values. If you define a constructor or a template_value it will fully overwrite it.

#

So AccessibleName::new("My Button") would be a "full value overwrite", whereas AccessibleName("My Button") is a "tuple struct field index 0 patch"

split harness
split harness
# wooden vine Afaik we are not aiming for 0.17 as to not delay it

I suspect it will need more review and bake time than we can give it before 0.17. I think its better to land it early in the 0.17 cycle so it can land in a solid way in 0.18.

The external crate path is an option, but I think aspects of it benefit from living in bevy_ecs (specifically Template), and I'd like to evolve bevy_feathers alongside it, so I kind of want to just commit to working on main in the 0.18 cycle.

wooden vine
#

I definitely agree with the evaluation wrt 0.17 release. I just thought that an external release might be good to gather user feedback + get stuff to interested users (like me 😄) sooner.

#

But I understand that if there's tight coupling to say bevy_ecs it makes more sense to develop fully in-repo.

split harness
#

I think its worth considering doing a sort of "soft launch" midway through the cycle if we reach a point where things have solidified and wider feedback is critical. Truly motivated folks can always play around on main.

#

I suspect we'll have more feedback from that group of people than we'll be able to act on quickly, especially in the beginning when gaps are identified.

dapper sky
#

While not as soon as I'd personally like to see it, I've grown quite comfortable with vendoring my dependencies for jumping on a major QOL upgrade :')

#

excited to see it when we see it

rare whale
rare whale
split harness
#

“Inherit from this”

Which as of my recent scene/template refactor isn’t strictly necessary. One of the “open questions” I’ll cover in my write up

rare whale
#

We could also steal the synax from bash, e.g. $props.variant or ${props.variant}

#

It depends on how spendy you want to be with unused delimiters 🙂

split harness
#

It’s semi-necessary in that if we drop “:” the struct-style scenes we talked about become ambiguous with template patches

rare whale
split harness
#

Those would each be enclosed in parentheses and comma separated in the list

rare whale
#

Ah ok

split harness
#

Although supporting omitted parentheses in that context is on the table

rare whale
#

Parens is not too bad

copper dragon
split harness
#

I think omitting it in that case harms legibility though

rare whale
#

So

(
    :button(ButtonProps)
    Autofocus
    AccessibleName("My Button")
),
(
    :button(ButtonProps)
    AccessibleName("My Other Button")
)

?

#

That's better

copper dragon
#

@split harness how does bsn look before applied to a world?
When last talked bout editor stuff mentioned that wed edit some bsn intermediary prior to becoming entities.
Is this in a resource? Or does it exists entirely outside the bevy app context?

split harness
#

For one-off on-demand spawns I'm planning on building a faster version that doesn't create a new baked version of the scene, and just allocates what has changed

#

And ideally this format will be able to behave like a dynamic bundle ultimately, rather than inserting each thing manually

cursive trellis
#

will it be reasonable to diff templates ?

cursive trellis
split harness
cursive trellis
# split harness Yup! Ex: via Reflect

great. is it possible to know which component fields were explicitly set vs which fields were omitted ? I think it is required to implement proper diffing

split harness
# cursive trellis great. is it possible to know which component fields were explicitly set vs whic...

This will be trivial for BSN assets, which will have a runtime per-field representation. bsn! currently produces closures that directly set the fields in normal Rust. This is faster at runtime, but it means the per-field information is not dynamically available. If we decide that information is necessary we have options: we could generate both the dynamic representation and the patch closure, at the cost of higher compile times and binary sizes, we could just generate the dynamic representation and eat the runtime cost, or we could have two flavors of the bsn! macro (the "fast" one and the "dynamic" one)

#

I'm most interested in trying a per-field patch representation that does both

#

(if we decide that is necessary)

cursive trellis
#

I understand. Then even with BSN the only way to do diffing will be to diff every single property of every single component through Reflect, which is not reasonable as some properties should not be diffed. A possible solution would be to only diff properties that were explicitly set. I hope I'm wrong, but I can't think of any other.

split harness
rigid adder
#

If my component stores a Vec with a dyn trait inside, will it be possible to instantiate it using bsn? 🤔

rare whale
#

@split harness Wanted to talk about some of the use cases for intercepted children. An example is dialog boxes. The structure of a dialog box looks like this:

  • DialogContainer
    • Dialog
      • DialogHeader
      • DialogContent
      • DialogFooter
        Where DialogContainer is an absolutely-positioned element that covers the window. It serves several functions: (1) To help position the dialog in the center of the window, (2) to intercept click events and prevent clicking on the background, and (3) to dim the background behind the dialog (this is optional depending on style).
#

However, we would rather than the user didn't have to explicitly specify DialogContainer.

#

Instead, we would like the structure of the template to look like:

:Dialog {
...options
} [
  DialogHeader
  DialogContent
  DialogFooter
]
#

In other words, the DialogContainer is implicit, which means that the children being passed in are actually situated as the grandchildren of the template's root element.

#

Menus have a similar issue, where the visible menu box is actually the child of the elements used for positioning the popover. We can get around this by having the user explicitly create a MenuContainer entity, but it would be nice to be implicit.

#

Implementation wise, the question is how to inform the calling template that children aren't being handled in the normal way. For struct templates, there are a wealth of options, including associated types and trait derivations. For function templates, the options are less clear.

#

Now, I'm guessing that for consistency reasons you'd want function templates and struct templates to have feature parity; but from my standpoint I prefer struct templates anyway (to avoid the stutter with params), and I don't mind making all my complex templates structs.

cursive trellis
white lichen
#

proposal: server logo stays pride themed until bsn branch drops

rigid adder
native mesa
native mesa
rigid adder
#

Makes sense, thank you!

copper dragon
native mesa
silk lava
#

Maybe not dyn Trait, but maybe an ReflectTrait version.

split harness
#

Today I did another merge with main, removed a bunch of cruft / incomplete ideas from the branch, and generally prepared it for public consumption. I cut things like my reactivity experiments which are incomplete and would distract from the more pressing issues. I also made good progress on my writeup, which should hopefully be done tomorrow. The end is nigh!

#

@rare whale the Bevy Feathers conflicts were straightforward to resolve / everything is still operational on that front

rare whale
#

This presumes that a reactive system can be implemented without adding features to Bevy, which is true or not depending on what kind of reactivity you are going for. More specifically, the more you lean into efficient fine-grained change detection, the more integration you are going to need. Brute-force solutions like thorium don't require any support beyond what Bevy already provides.

#

Also, today I thought of a metaphor which might help clarify some of the discussions around diffing template hierarchies.

#

Imagine you are managing a large railway network that spans between several major cities. As trains are routed to different places, we need to change the state of the network (which track switches are set to which position, which signal lights are activated, and so on). To make this efficient, we only want to change things that need to be changed - only flip switches that are in the wrong position. Thus, we need some form of "diffing".

#

However, the kind of diffing we need to do depends on what kind of changes we want to make.

#

For day-to-day operation of the network, we're not changing routes or laying down new track. We are only change the positions of the switches, while the actual track configurations are static. Thus, our "diff" doesn't need to include all of the rails, ties, light poles, and so on - it only needs to include the state of the dynamic elements.

#

This day-to-day operation is analogous to the changes made to a template of entities in response to reactions.

#

However, if instead we were replacing the entire network, tearing up track in some places and laying down track in others, we would need a much more extensive "diff" that would have to include a lot more state.

#

In this metaphor, it corresponds to a hot reload scenario, where literally anything about the template can change. Trying to do this sort of thing incrementally is a much harder problem with a much costlier solution.

#

For this reason, I have pursued a fairly limited scope of ambition: hot reload scenarios wouldn't try to diff, they just destroy and re-create the relevant subsections of the hierarchy. True "diffing" is only used for incremental updates, which change a small number of dynamic switch points within a hierarchy that is mostly static.

#

This is what React.js does: hot reloading doesn't do a fine-grained diff, if you touch a .tsx file all the components defined in that file, along with their descendants, are re-created, but their parents are preserved.

karmic rock
keen patio
# rare whale <@153249376947535872> Wanted to talk about some of the use cases for intercepted...

Depending on how Template works, maybe one potential solution is to use a relationship for the slots. So instead of placing them as Children ([]), you'd use something like DialogContents/DialogContentOf (DialogContents[ ... ]). And then enforce a constraint somehow (using hooks or other lifecycle techniques) so that the DialogContents entities are automatically parented to the inner leaf. Not sure if it would be possible to enforce this constraint early so that all relationships are set up in the same bundle effect, minimizing archetype moves, though...

I also think it's very useful to be able to pass parameters to the slots. In React slots are usually just callbacks which takes care of that. More template-based libraries like Vue has their own slot syntax with ways to pass through parameters. But maybe this is something to save for the reactivity stuff... 🤔

karmic rock
# keen patio Depending on how `Template` works, maybe one potential solution is to use a rela...

The challenge is that you can't create a relationship towards an entity that doesn't exist yet. A slot entity is a child of the template instance, which can't exist yet before you instantiate the template entity, so I think it makes more sense to have some kind of inversion of control here where the template creates the slot entity, then passes this to the code that creates it (like a callback)

split harness
rare whale
#

Achievement Unlocked

slate pewter
#

yay!!

quartz meteor
#

right well there goes anything i was planning for the next week 🥳

cobalt stone
#

Mostly my thoughts are that there's too many user-level traits. I was expecting basically just a single Scene/Template type, as all users would have to think about.

native mesa
#

My general reaction is: Perfect, no notes. Every decision here works for me. GetTemplate makes a lot of sense, lets us have both the power of arbitrary output-focused templates and sensible opt-in defaults.

It is a lot of traits, but I don't think it's possible to remove any. The way they are set up is very clever.

cobalt stone
#

GetTemplate makes sense to me, just not having to derive it for every component/asset thing

#

SceneList/Scene I feel like we can unify, and same for Template/Scene

native mesa
#

I personally am not sure SceneList should be unified. With a scene you can spawn it directly on an entity, with a list you can only spawn it as an entity's children. Switching between those automatically could be very confusing.

rigid adder
#

This is unlikely to land in the upcoming Bevy 0.17
😢

But understandable, it's a huge pile of work.

dapper sky
#

extremely exciting stuff

#

I like the reasoning for comma-separation & it's absence from some areas from a formal language design POV but I do think it'll be a difficult sell to some