#Next Generation Scenes
1 messages · Page 4 of 1
Is the most up to date part of the proposal what is on GitHub, or have there been advancements somewhere else in addition?
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
I see, thanks. I asked because existing serialization formats (json, ron, toml) have syntax simplifications like eliding Some, eliding struct names, eliding newtypes. And cobweb ui takes it further with no commas, newtype collapsing, whitespace scene hierarchy instead of containers. These are productivity wins, and the resulting syntax still feels rust-like.
Yeah BSN has the same priorities, but it biases slightly more toward "Rusty". Ex: whitespace is not semantically relevant, it still uses commas, etc
Older iterations did not require commas. I'm still a bit divided on that one
Can you elaborate on "newtype collapsing"
For example, A::B(C{ ... }) -> B{ ... }. The lets you do Color::Hsla(Hsla{ ... }) -> Hsla{ ... }. Or A(vec![ ... ]) -> A[ ... ] (for loadables where you need the struct name).
I just made commas equivalent to whitespace so people can use them or not as they like.
Very bikesheddy but I really don't want alternate syntax if possible
It screws up diffs and makes writing tooling harder
In that case commas would be required, because people write commas out of habit so a ban would be a big UX mistake.
Yeah, and I think the whitespace haters care more
A::B -> B requires Reflection to resolve and only works in field assignment position right?
Right. In loadable position it would be A::B{ ... }
This is something we shouldn't do in BSN, as that won't be viable in the bsn! macro if we want it to resolve directly to Rust values
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)
Sure, good reasons not to do it. Just pointing out there is a syntax gap between rust and a minimalistic syntax.
For sure. If I was making an asset-only format I'd definitely consider doing "newtype collapsing"
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.
If not already decided upon, I'd love to see BSN assets parsable during compilation.
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")
}
I believe I floated this idea to cart before and he was receptive
Neat! Thank you! 🙂
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?
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
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
If we replace entities inside components with paths, how to determine which entity to use if there are multiple entities with the same name?
For example:
- scene root
-- NameA
-- NameA
We could add disambiguation syntax (ex: paths by default link to the first, but if you include "index disambiguation syntax", it would search for the nth instance)
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)
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.
I like Name tbh. It's like html IDs
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.
^^^ this. Maybe I've just been using it wrong but that's how I've understood it
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.
also gets around this issue
or unique among that scene at least
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.
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?
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
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
i suspect i can see why cart might be opposed to unique ids, they are not always fun to author by hand.
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
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.
yeah...
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.
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
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
I'd expect so, which is why I was thinking about how this would relate to inheritance
nvm, was a silly idea. If the ids don't appear in the doc they couldn't be used for reloading anyway
😅
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.
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
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).
@rusticorn @fallow cloak
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.
I'm not opposed to unique IDs and they're on the agenda for the asset system (in an opt-in way). I see the value in making things stable across refactors. But they do have significant downsides (effectively require automation, not human-readable so looking at the file directly gives you nothing, etc).
Agreed, although the "reloading" problem goes well beyond entity path here, if the goal is to fully retain identity. Giving every entity a generated unique id is one option, but that affects legibility and requires automation to compose. We could (and maybe should) support both approaches
I'm largely in favor of forcing unique names. Im pretty sure we've identified cases where exported names aren't unique (ex: bones), but we could assign numbers in those cases.
(unique within the context of siblings)
yeah, my concern would be that if we don't provide a machine-facing stable id format people will write tools that pollute/abuse the human-facing one. but tbh this seems like something you can address post-launch.
In cobweb ui I enforce unique ids in each scene layer (but not the entire scene). Users can also make 'anonymous' nodes that assign an id automatically as an index into the list of anonymous nodes in that layer.
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).
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
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
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
With asset processing we could already turn .bsn files into a faster to load format for releases at least. Not shoving all the data into binaries might also play nicer with updates trough platforms like steam
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.
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
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.
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
There's some chatter with Cart in the thread for this too, talking about his current plans 🙂
@split harness is https://github.com/bevyengine/bevy/pull/16894 compatible with your plans here?
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
Yup I support this move
Yeah I'm thinking along similar lines. Things like Construct / Patch / etc would live in bevy_ecs as the "core framework" and then BSN would be a separate syntax / asset crate on top
Over the past couple of days I've been zeroing in on some changes:
- Make normal spawning much nicer / natural / data-first / ergonomic (ex: spawning children is no longer a builder method ... this builds on (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 🙂
It removes the "archetype move" associated with adding children
Why would/does a spawn of a child cause an archetype move?
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
The current approach (with_children builder) adds a command that produces a Children component on the parent, in addition to spawning each child in a command.
A suitably advanced command merger could solve that problem too
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
Before trying to make this the "general purpose relations system", we'd want to make sure it covers the scenarios we care about. I'm curious if you see any holes in this approach.
Ahh gotcha, so it's more of a generalization of the current system + hooks 🙂
I've always suspected that relations could be implemented in userspace, given a sufficiently advanced ECS
I think we might be building something pretty similar
let me push what I have in a bit, just haven't finished writing the hooks yet
The "non fragmenting" relations stuff is already implemented. I'll try to post it tomorrow too
Curious to see how much "convergent evolution" we have 🙂
There are definitely a bunch of similarities! I'll share some details in a bit, dinner brb
@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:
Yeah the idea is to store that on the Reaction component (which would be expressed as a non-fragmenting relation)
Correlating to the appropriate position in the parent's children is the problem I haven't tackled yet. But that feels solvable
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.
Presumably WithChildren doesn't support arbitrary bundles in that form
(ex: only a list of Buttons)
vs ((Button, Foo), (Button, Bar))
Yes exactly
WithChildren::new((
(Button::with_label("OK"), Name::new("fred")),
(Button::with_label("Cancel") TabIndex(0)),
)),
(all_tuples_enumerated to the rescue!) 🙂
This is an intractable problem from my perspective. Only way around it is with a wrapper component type
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)),
)),
Oops explicitly not a "wrapper component". I meant to say a wrapper type 🙂
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.
I believe I've sorted this out with some internal bundle changes.
Filling in that implementation is the last piece of work required to get this operational
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.
I think my advice is "just wait a day or two" as I think I'll have these problems solved for you
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)
What's the plan for observers then?
OK that sounds interesting
However fitting Observers into my current "non fragmenting relations" mold will be interesting
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
i've been waiting for this, that's excellent!
are they properly many-to-many?
This will also make the entity inspector a lot less cluttered. Right now you have to filter out all the little observer entities
Doing this is one of the other "unsolved problems". Currently the system I've built relies on the "relation component" (ex: ChildOf or Observer) being constructed on behalf of a given "related entity", rather than being provided directly by the bundle.
My current plan is to build a secondary init path that takes a user-provided per-entity Relation instance and targets it to the parent
"non fragmenting" is inherently many-to-one. "Fragmenting" in theory provides many-to-many, but that plan is very rough at this point
ah, so you are building some sort of generalized set of reverse indices to match an immutable "relates to" component?
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
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
Yeah I dont see this as a major loss
are we getting generalized propagation/traversal with this too?
Yeah it lends itself to that
if you are doing what i think you are doing you can toposort the index
Haven't implemented it yet, but I don't see any blockers
Is it implemented plainly on top of bevy_ecs or does it require modifications to ecs?
The "reverse index" is the "source collection" mentioned above
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
Minor modifications in a few places
Largely entity/component init
Very superficial / maybe 10-20 lines total
Ah ok
@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.
This is the kind of thing that is:
- Not a huge amount of work to implement
- Could be parallelized if necessary
- Can land first and quickly
I'm not too worried. But definitely help keep me on track!
Sounds good then
this is work that needed to get done anyway (and this is a better approach to it). i'm very pleased to see it.
This was motivated by getting reactions out asap, and from my current perspective sorting out non-fragmenting relationships was a sticking point
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
Yeah. I dont want to commit to a path before the picture is in view, given that the pieces build on each other
(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)
I haven't been following this thread, do you have a tldr of how reactions are useful for reactivity?
Or a comment link I can read
I operate on what I call an "anxiety minimization framework" - which ever parts give me the feeling of highest risk that I can make forward progress on, that's what I work on 🙂
I haven't expressed it explicitly yet, but what I'm considering most seriously currently is in the realm of thorium and bevy_reactor, with a more general system-driven reaction approach and a "reaction executor" that operates top-down
@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
yall get to choose what you work on? i feel i am rarely informed until it's too late and i've started.
Specifically, computing depth is "expensive"
Retirement, baby! I highly recommend it 🙂
However that approach has inherent limitations and I'm not yet convinced it is a good general solve
I get how those work in general (store a list of reactions in a resource or on entities, iterate them and pass &World to figure out what needs to react, then recompute the expressions and update component data), but how does that use relations?
Relations are what allows reactions to be entity-scoped
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>
It stores the reactions as relations instead of children, which allows reactions to independently operate on children, and solves the "cleanup issue" nicely
How though? I'm not intuiting it. Why do you need relations (between entities right?)? Why not just have a Reaction<T> component on the entity itself?
In my case, Reaction is a system wrapper, so you can't have more than one
Right, you need multiple reactions per entity, which means either a collection component (and the difficulty of spawning) or trait queries.
you'd really want a type-erased vector of effects, right? well when i see a type erased reference, i think of an entity, and when i see a vector of them i think of relations.
Very interesting that we landed on something so similar. Thank you so much for this overview. Makes me more comfortable solving the non-fragmenting problem first. Your years of experience are extremely valuable!
Every time you hear the word "reaction" replace it with "observer" and then think the logic through.
No, the way I had it in my PR was:
// I = input type to reaction, from the source entity
// O = output type to reaction, on the entity this component is stored on
struct ReactiveComponent<I: Component, O: Component> {
source: Entity
}
Yeah the biggest difference is the fragmentation on asset child & the guarantee that a table can have at most one child per asset parent. Which means you can do a few neat things like O(1) reparenting & bidirectional lookups* even for a children vector. And you can still iterate contiguous arrays when doing BFS, vs. having to do child.get<Transform>()
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
Yeah it does still seem worth investing in fragmenting relations. I like the "value component" path. legion (an older rust ecs) actually implemented those many years ago
I'm curious how the fragmentation works for the asset children? Do they get some unique id or is it special cased into the storage logic?
I'm not describing fragmenting relationships here 🙂 This is a new non fragmenting relationships implementation
Oh I guess there's an entity representing the child in the template that can be part of that id
Not special cased, what the asset instance children get is an (IsA, asset_child) pair. That ensures they end up in different tables, and also solves another problem I've had, which is that instance children couldn't inherit components from asset children
Gotcha. Definitely misread 🙂
Yeah, was a bit slow but I got there 🙂
In the new design both the instance root* and instance children have an IsA relationship to their asset
Definitely neater that way
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.
What happens if I try and break the unique parent guarantee, i.e. reparent one of the template children to another instance?
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
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
That map ends up being very useful for queries, as a query sometimes needs to know if the currently iterated table has any child for a specific parent
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
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
Yup, you can only do this for the children that are "known", which works for both static and conditional entities, but not for entities created in a loop
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
I remember reading up on it at the time and thinking that it seemed pretty difficult to do anything with the hierarchy after flattening 👍
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
Oh good that makes me feel less bad for procrastinating a design doc for a better API
Just put out a discussion focused on picking a better spawning API that builds on non-fragmenting relations:
https://github.com/bevyengine/bevy/discussions/16920
Discuss on Github please!
I assume the stuff explained throughout is what this refers to
Yup! Also considering adding an on_respawn hook to distinguish from collection removal
Wow! Thanks for taking the time to write all this out!
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.
Afaict it's just about spawning them, so triggering them should probably remain unchanged
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)
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)
I understand! I suppose my question spans outside of the context of this discussion
@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.
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? 🤔
You are correct in that it's mostly about how the DOM is updated. However, the difference in ergonomics is a consequence of this design decision.
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.
Right. Does that mean "output-diffing" is coarse-grained, and "state-diffing" is optimization technique for fine-grained? The only reason the diffing is there (for state-diffing) is as an optimization right? Technically you could run all the effects every frame and just finely update the components
Not necessarily. Quill is coarse-grained and uses state diffing.
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.
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....
I think that at this point the terminology fails us. However, I think your vision is essentially correct.
Ryan Carniato (author of Solid) has a series of blog posts on fine-grained reactivity: https://dev.to/ryansolid/a-hands-on-introduction-to-fine-grained-reactivity-3ndf
That being said, I don't think that series talks about the DOM much.
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.
Those blogs do a good job at introducing reactivity in a web-based context, though I've had some trouble trying to map the concepts to ECS/a non-JS environment. There are no property getters/setters in native languages, and wrapping every expression in a lazy eval container seems really expensive
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
Yeah, and the success of React and Solid is kind of a trap: on the one side, there's the danger of trying to replicate their architectures exactly in Rust, and on the other side there's throwing out the baby with the bathwater. Trying to find an appropriate middle path is hard.
I think Vue is interesting in that it offers a model for separating logic from presentation, which is not the case in JSX-based solutions like React and Solid.
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.
And do useful things with them, like iterating collections in sorted order, filter out elements, reduce them to a single value etc.
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
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.
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
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.
Agreed, but it would be even better if I could write the code in Rust and have fast hot-reloads.
My ideal vision would be writing all reactive scene/template code in Rust. Statically linked with none/minimal reflection for production, and hot-reloadable during development using a mix of reflection and dynamic linking.
Hm, but hot reloading can still be comparatively slow, and it requires a build infrastructure to exist. I want to be able to do this:
(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
There's a third option, which is to have scripts in their own dedicated asset type. I assume that you are going to want non-template scripts anyway for things like actor behavior and ai. So it's possible that the template asset could import functions from script assets. They wouldn't be in the same file, but they could be co-located.
Yup, or design the template language so that it can embed code snippets
That's also complex though, since you need to serialize from template -> native -> script runtime and back
Oh I love that. I am very impressed with your template system. Built a very ugly house in your playground the other day and had a blast 🤩
I wasted most of yesterday on this :p definitely has an addictive aspect to it
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.
Another challenge with the 3rd party scripting approach is that you now need to expose the ECS API to it as well, which is not straightforward
vs. a "native" scripting language that vertically integrates with the ECS type system
For my own scripting needs, I don't really need to expose much of the ECS API. I don't want to write "systems" in a scripting language. Rather, I want scripts to give high-level "stage directions" to actors, which are then implemented by lower-level systems.
Wow! That's way nicer than my house. I am missing a Triangle (or mesh) component 😆
Nice!
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
I use earcut
Agreed, I think what I want from a scripting language is (in addition to basic expressions, templates, reactivity etc)
- express all UI-only logic (this requires much of a full-fledged scripting language)
- define functions that return expressions (so code can be DRY)
- the ability to set components as a response to some UI action
- the ability to emit events
Do you know a good native CSG library for C++? Really hard to find one that also has a permissive license
Yeah, boolean operations would be cool. Being able to put a mesh as a child, that modifies the parent mesh(es) by subtracting itself from it
Last time I did C++ was about 10 years ago
You could port the JS one I suppose: https://www.npmjs.com/package/earcut
Yeah, or even just provide it as a primitive. Cutting rectangles out of rectangles is relatively easy
Full CSG would be nice but I don't know enough math to implement it
The Rust crate is just a port of the JS one: https://crates.io/crates/earcutr
Ah true, I'll take a look
It's not a full CSG, but it does triangulate polygons with holes.
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 handlergoals()- returns a tree of prioritized goalssense()- 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
}
Would you expose that as functions to the scripting language?
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)
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
Yes
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.
you'd think not, as bsn is supposed to be a human authoring format - if we wanted a full blown machine friendly persistence format the design space would be different.
Manifold if you want a good robust one. Godot also switched to it a month or two ago
https://github.com/elalish/manifold
Oh nice, will check it out
(I have not personally used it but have seen people recommend it for CSG a lot 😅)
Also, fun aside: Unity's "probuilder" tools for creating geometry in-editor save the meshes you author in the YAML scene files as inline text. Results in really unpleasant workflows if you want to avoid those downsides for things like sensible version control compatibility 🙃
(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)
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!
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
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?
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
One thing to note is that the objects being sensed don't have to be passive. For example, suppose you have a constable who chases someone after they have attempted to pickpocket. The constable doesn't try and analyze the actions of those around them to decide what is a crime and what isn't - instead, the act of pickpocketing broadcasts a "crime" signal.
Right. So is the window announcing itself to the wall that it needs to make a hole, or is the wall discovering that there's a window in a specific spot
Probably the first option, but not sure
I feel like you'd need some kind of composable/hook inspired thing. Not sure what it would look like in an ECS world though.
E.g: let monsters_nearby = use_query_in_proximity::<(Entity, With<Monster>)>(10.0); which would return something reactive
(where use_query_in_proximity is a composable defined somewhere with re-usable reactive logic)
Being able to "hook" into the ECS is essential imo, just like we can hook into contexts or global stores in JS frontend land.
That should work I think, but does feel a bit ad hoc 🤔 as in the proc gen "grammar" isn't really captured explicitly anywhere
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...
Oh, this has a lot of clues for how something like that could work https://tracery.io/editor/
I think, with enough work on the compiler and with work on that incremental linker wild, we can achieve these speeds
That'd be nice, but this hasn't really happened for C++ compilers which are roughly of the same complexity
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
There's also just aspects like how you might want to ship an actual app with hot reloading but not allow the arbitrary code execution that would come from loading Rust code. An example would be how many modding systems and other user generated content systems are moving towards something more sandboxed given how prevelant malicious mods have become
Writing a scene by hand and having it hot reload is definitely not the only usecase for scene files and hot reloading
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.
https://davidlattimore.github.io/posts/2024/11/19/designing-wilds-incremental-linking.html This guy is currently working on an incremental linker for rust, wild, and sounds like he also is willing to dig into the rust compiler, this is what he has to say on the matter
IMO it’s very risky to make a big architectural decision like this based on something that might happen. Also as the guy says himself, incremental linking is only one of many changes required for fast iteration times
I agree it's not good to make architectural changes based on what might happen, I do think it is achievable though
@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.
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,
}),
Definitely would make sense to dedup materials with identical properties when they are defined in bsn, tho if you duplicate materials across files it might be preferable to have an asset with those material properties and reference that directly (since ideally you don't have to go around editing 5 files to change a color)
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
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
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?
I think that was @native mesa 🤔
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.
Can't you return impl Bundle?
I don't particularly care about errors rn, I'm experienced enough to manage
it expands to direct variable assignment, and variables can't be boxed.
Template is basically just a clever type-erased wrapper around Bundle
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
If it can do really dynamic stuff like this https://github.com/JMS55/bevy_dioxus/blob/main/examples/demo.rs#L43-L59
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.
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.
I'm excited to try it! I really want to work on editor stuff lately but UI and asset tooling is not there yet :/
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 😅
this would be something like
fn query(query: <a query>) -> Template {
template!{
{ Text::new("Header line!") },
@{
query.enumerate().map(|(i, item)| template!{
{i}: { Text::new(item.name) }
})
}
}
}
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
As long as it works, it's way easier than updating it manually 🙂
i just got loom working with my little thread pool prototype and all the tests are passing, so it's probably time for a change of work. I'll try to find the time to fix the issues people have identified with template!.
i'll ping you when i think it's worth looking at.
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.
@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.
Excellent
Yeah, UI-as-assets is an extension to me: I mostly want a good code-first "multiple entities" abstraction
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.
Yeah, exactly
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".
On that note of the styling boilerplate
What do you think of mevy?
https://github.com/dekirisu/mevy
I looked at it briefly. I don't have anything against it, however I've been trying to focus my efforts on "plain rust" as much as possible. For me, the biggest win is simply making the styles re-usable, once I get that it doesn't really matter how the styles are represented syntactically.
@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.
I really like the aesthetics / parallels
And it looks like Invoke was constructable in user space?
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.
Yes
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.
Also, I'm not fond of the name DynChildren. Some alternatives I have considered are:
ContentsElementsOffspringProgenyMembersNested
Is this a good start to do PR's? If so, what would be some simple tasks to work on?
Elements is nice
@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.
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
Will bsn files have a dedicated bsn folder instead of "anywhere within assets folder"
dedicated folder wouldn't make sense in my opinion
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
Curious if you're interested in having this, what would we get out of it?
I had an idea that the editor could manage a bsn folder (inside assets?) automatically for the user, arranging the files in a hierarchy based off what patches what, but that'd be harder/unwanted if the user just gonna toss everything in the assets folder where ever they want.
Not very thought out idea tbh
Fair enough!
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
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
Ofc, I think toggles would be mandatory for a lot of features
are we getting "construct" in 0.16 or is this kicked down the road?
Pretty sure construct was what cart was talking about here?
#editor-dev message
It has not appeared yet and I’d personally be against merging cart’s changes this late in the cycle. So no probably not.
We got relations instead.
We got relations instead.
we need a #no-context-quotes channel 🤣
If you need construct now, I personally vendored Villor's bsn prototype from the bevy_editor_prototypes repo and am using that to prototype while waiting for first-party bsn/construct
agreed, it looks really good.
in fact, maybe @keen patio it would be good to publish a version of that on crates.io
I can see the appeal, but I'm not sure I'd like to maintain such a thing 😅 and I'm not sure how Cart would feel about getting sniped like that
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 🤔
that's an idea. i didn't want to sugest you maintain it, but there seem to be quite a few users who are eager to try it out on their own projects.
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.
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?
Having versions not start conflicting if multiple deps use it
Right, but if the versions (git tags) follow Bevy releases, with bug fixes exclusive to main, that probably would not matter much
in this case, probably not.
but having a "if you really want to try it, add this line" would be nice.
git dep can probably do that.
# 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
I'm glad to hear someone is trying it out! 😍 I'd be happy to hear any about any potential improvements/bugs
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
Thank you for working on it - it really is a game changer imo. I'm looking forward to bsn proper as well, but as a stop gap solution it lets me move forward without having to write ugly workarounds
I'd think that the <R: Relationship>::spawn would translate into bsn with the existing structure?
in the long term we need to be super carful about this to make sure that the files output by the editor don't have massive relations/ordering diffs when the structure changes.
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
e.g.
Node [
(
Text("Hello BSN!"),
SomeRelationship[ Foo, Bar ],
)
]
what if you want two siblings that a relationship between them?
in my current implementation it is very component-centric, bundles are lists of components, children are a separate thing
but yeah, with the new spawn APIs there is a lot of potential for improvement, it may end up just being a single bundle to construct in the end which contains all the components + bundle effects for spawned related entities
you need to use anchors/names imo
Ah interesting. But I don't think the related! macro solves for that either, no?
it does not
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")
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
yeah that makes sense
that's a really good point actually! when hand authoring BSN, I'd want to locate things hierarchically in a way that makes sense for me, but for "machine-edited" files, it might be better to just store a flat list of entities connected with keys/names 🤔
I like this syntax! Though I am still not sure whether it should go in the "component block" or the "children block" 🤔
Node [
(
Text("Hello BSN!"),
) [
Text("Regular child"),
SomeRelationship[ Foo, Bar ]
]
]
(Foo and Bar would be related to "Hello BSN", not "Regular Child")
cries in json serialization
Or to keep things consistent with the new spawning APIs, maybe the children block itself should go in the "bundle"
(
Node,
Observers[
// ...
],
[
Text("Child")
]
)
I guess an evolution on this is to modify the children block to make it the same as other relationships
(
Node,
Children [
(
Text("Hello BSN!"),
Children [(
Text("Regular child"),
SomeRelationship[ Foo, Bar ]
)]
)
]
)
Yeah I think what I posted is the same idea, but with the explicit Children declaration
(my example was incomplete and bad, edited above to be more clear)
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"),
]
]
Yep, super clear. Agree with the verbosity expansion concern, but might be worth the additional capabilities.
(also as someone who uses xml constantly at their day job and is dealing with that now, STILL BETTER THAN XML)
Yeah its not happening in 0.16
it's less that I need construct and more that I need the ecosystem to have chewed on construct for a hot minute and design tooling to have started to emerge 😅
In my case I'm building my own editor on top of "something EntityPatch shaped" and it would be a bunch of tech debt to try find a stop gap solution for myself with the new scene stuff on the horizon
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.
Totally valid - not trying to imply everyone needs to do what I'm doing (I have my own reasons)
Not everyone wants to build tooling before they can get to the making the thing part
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. 🤔
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?
Hmm I'm not sure I follow completely.. Do you want to take one child from a template and spawn just that child?
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?
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
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
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 😅
Can someone link me what this related! macro does?
Returns a SpawnRelatedBundle that will insert the given RelationshipTarget, spawn a SpawnableList of entities with given bundles that relate to the RelationshipTarget entity via the RelationshipTarget::Relationship component, and reserve space in the RelationshipTarget for each spawned entity.
Thanks, did not know it is on main :)
yeah it was added with the improved spawning PR
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
}
?
is there a place where i can see when different features in the proposal might be added?
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
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.
Yes this will be possible (at least, in the macro)
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
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
yup this is already the plan (specifically, things like px(10) returning Val::Px(10))
One of the major motivators for the function reflection feature in Bevy Reflect
some kind of "this is a name of a post-read-and-parsed, pre-spawn hook I've already registered with the engine with this name. Call it with this value[, and maybe the surrounding bsn structure as context]"?
Nice! I think that makes a lot of sense as a cutting off point for making BSN extensible but not so extensible that it opens up a huge can of worms.
Yeah exactly. Perhaps when creating a bevy App the dev can call a function “register_bsn_helper()” with a callback that BSN reaches out to whenever that helper is used in a bsn file
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?
@split harness
What do you mean by "compile time component registration"? BSN (the asset format / loader) relies on the Bevy Reflect type registry.
Manual registration of types (via plugins) is the current pattern for that. This PR would make that automatic, but it has tradeoffs:
https://github.com/bevyengine/bevy/pull/15030
hmm ok so it has the same problem im facing with the editor
Assume manual type registration is how it will work (both for scenes and the editor) unless 15030 is merged
Note that types have recursive registration, so you only need to register top-level types
according to ecs guys the registration occurs as the various systems using them are run
i ran into the problem of the editor not knowing a type because user code doesnt get run
meaning id need some form of registration during comp time instead runtime or enforce manual registration
The ECS folks are talking about a different kind of registration (registering components in the World / assigning ComponentIds)
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
hmm i can see that working for standard components but i struggle to see how we'd handle stuff like custom relationship components since th editor will need equivalent components to represent in the scene view's?
These would also be registered by the author of the relationship components
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
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!
Yeah it would be nice to get it in if possible. The wasm bloat worries me a fair bit (although thats really just a critique of runtime reflection at large, and its a problem we'd need to solve with or without auto-registration). I need to give the non-inventory platform support impl a bit more consideration
@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
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
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>
}
}
very exciting!
@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.
A flecs monitor is basically a callback (observer) that gets invoked when an entity starts or stops matching a query
In the button case, the background color isn't a boolean though: it's more like F(matches(pressed), matches(hover), matches(disabled)). So the question is not "does it match this query?" but "which of these three queries does it match?"
Yup I agree, monitors don't sound like a great tool for that use case
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
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.
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
the value would mostly be in having a visual tool for ordering verses the dispersed tree of befores/afters/in_sets that make it hard to spot issues
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)
Yeah, I would want it to be possible for weird modding use cases
But I would prefer a single clear way of doing it
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
i would love for it to just be code. issue is code gen is alot more difficult i fear
I'm arguing that our editor shouldn't be messing with ordering at all - if you want to change ordering, go to the plugin where you add the system and add the ordering like we do today. So the editor ONLY visualizes and doesn't edit
yes, but im predicting people are gonna go "why cant i just change it here? ugh"
visualizing only initally is fine but im predicting itll be a common feature demand
I would like "go to declaration" support!
that def would be a nice bridge point mhm
I was thinking about that too, though I don't know if we can do that without macros? Perhaps a better Rust nerd than I can make it happen though
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
should be able to do that via rust analyzer hook in?
what if the editor edited the code
Ah good point, I was only thinking of using the data in the ECS. That would at least get us to the system function, but I don't think we can get to the plugin that added the system.
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
some form of static code analysis will become necessary as feature demands grow, also just keeps the project code space cleaner that way
We can probably use dev mode location tracking for it 🙂
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.
shareable code by end of week: core scene system, BSN macro, templates
did this already happen somewhere? 👀
Haven't seen anything; I'll link relevant PRs here 🙂
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.
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?
eh this is also true for types that impl Reflect or serde
sure but alot of plugins make assumptions of "oh this component wont be used by devs i can do this behavior"
and they cant just toss reflection out cause then it cant be displayed in inspector tools
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
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.
Yeah, this is reflection in general 🙂
and serde!
Which is also reflection, secretly :p
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
That's "reactivity" 🙂
Exact designs are still be worked out
But there's consensus that we'd like this
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}");
})
)
}
yes I use this already, but in some cases I update the UI based on Changed<Component> instead of events, I don't think I can use observers with the change detection system
Just going to bump this, in case anyone knows of anything. I was told elsewhere that anything that implements Construct is compatible. But still want to learn more about the format in general 🙂
I think this thread and this gihtub discussion https://github.com/bevyengine/bevy/discussions/14437 is all we have about BSN, I might be wrong tho
Got it, ty
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
@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"*
Yeah I'm pretty sure that is the plan at some point!
yeah theres an issue for that, for now id use a regular system to propagate it
#[derive(Default, Event)]
struct OnChange<T>(PhantomData<T>)
fn trigger_changed<T>(mut commands: Commands, query: Query<Entity,Changed<T>>){
for entity in query.iter(){
commands.entity(entity).trigger(OnChange<T>::default());
}
}
oh this is pretty smart, imma do this as well
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.
What are you aiming for in your initial drop / how do you think it will be split? 🙂
- 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 ofChildren [])
- 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)
holds breath
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.
Right, so draft mega PR or something for cohesive feedback, then split for actual review?
As long as we're not pressing me on 30k lines at once I'm happy with that
Thanks for the update cart!! What's the rule: The first 90% of the code accounts for 90% percent of development time. The remaining 10% of the code accounts for the other 90% of development time.
Look forward to seeing the template design, are they like reactive templates? E.g. do template instances respond to changes in the template inputs?
They are not reactive / that aspect has been punted forward. Just a simple mechanism for taking an input context (with world access) and producing an output.
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
Awesome! 🔥 Looking forward to learning more about Template
I'm pretty sure @cobalt stone will be pleased with this design.
Curious to see how the input -> >>transformation<< -> output is expressed / if it's in any way similar with what I came up with
Will templates have access to dependency injection? Specifically, if I have a template that needs a custom material / icon / font, can I get access to the asset store without making the caller pass it in as a parameter?
Yup!
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
Ooh virtual file system nonsense?
Currently add_with_path skips the AssetSource system entirely and lets you associate whatever path you want
My next question is: what are you standing around talking to us for? cracks whip...
In practice that will cause problems for more complicated scenarios
So I won't have to pass assetserver everywhere? 🥹
Yup thats one of the biggest motivators for Template
Don't break the cart! It can only go so fast!
/j
After Project Moonshot concludes: Project Horse
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
Yeah, that seems much more sustainable than just mangling real paths lol
Good hack for prototyping though!
MemoryReader already serves this exact function for our unit tests, so it really just comes down to building the convenience API to make adding new assets comfy
Oh man, I didn't even think about how useful this would be for testing bevy_assets...
Yeah its used in all of the "full asset system" tests. Way better than writing temporary files to disk 🙂
- crawls back into his cave *
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
No it would still "load" it via normal asset flows. For in memory assets it would just use the in-memory asset source instead of the default filesystem asset source (ex: virtual://image.png vs /image.png)
Oh I see
Similar in function to the embedded:// asset source
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?
I don't think virtual is the right name for that source fwiw
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
Ah right yeah this would need to short-circuit
We want to store the Image directly
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
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
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
I think the better approach would be to make it possible for asset sources to arbitrarily define the AssetPath -> Image path
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 🙂
Hmmm ok. I see what you mean. I'd need to think about this situation more though
Yeah I haven't given it much thought yet either (clearly)
I know enough to know its a solvable problem without too many unknowns
transient:// or phantom://
Maybe just runtime though that could be confusing haha
ghost:// 🫣
I am a big proponent of diffing being the easiest way to write UI 😅
Also makes hot reloading trivial
Same. This sounds like basically everything on my wish-list.
Diff the output of the template? Or the input?
Output
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.
Diffing in the sense that if feels similar to write as immediate mode (like egui)?
Can't overstate my excitement. Maybe I should understate it to not add to the pressure.
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
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
that’s the design I came up with in my previous experiment with diffing
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.
"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.
That’s a good point. But intermediate doesn’t have to mean the whole tree. I could imagine using a intermediate/diff approach to update one of those entities. But the update itself would be triggered by a higher level input diff/observer
we may consider implementing a dedicated diff trait which takes this into account - and create a default impl using PartialEq
I think it’s generally a good idea to separate the state from the output anyway. Could be inserted during “setup” or simply be required components.
I think you'll end up boiling the ocean with that approach.
The update/reconciliation process shouldn’t touch components that it didn’t add
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.)
If I know React properly there are two kind of diffs right?
- The “reactivity” system diffing effect deps to see if they should run, powering memoization etc
- 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
I think that's right
I get your point, but I think this is what react does? and no one seems to complain about it
No, that's not what react does. React diffs the VDOM, but the VDOM does not have transient properties like scroll position or cursor position.
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.
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
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.
Diffing in the sense that you write functions to produce and combine UI trees and the library takes care of reusing or deleting entities/state as needed.
bevy_dioxus was very pleasant to work with https://github.com/JMS55/bevy_dioxus/blob/main/examples/demo.rs#L19-L32
But then you're talking about the functional outcome, not the implementation (which is what diffing is, an implementation detail, though often a leaky one)
Definitely agree that when done well this workflow is great though
When done poorly it becomes a nightmare however 
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.
are trying to work towards a general-purpose solution that’s usable for more than just UI.
Not trying, it's been working for a while. The traffic lights in this demo are reactive objects for example: https://flecs-hub.github.io/traffic/etc/
It says “work in progress” at the top, and I was under the impression the feature was still being worked out.
The demo is a work in progress, the feature has been available for a long time
Ah gotcha, admittedly I’m not super up to date about flecs
The last I read about it was in the “what’s next” section in the flecs 4.0 blog post https://ajmmertens.medium.com/flecs-v4-0-is-out-58e99e331888
I just published https://ajmmertens.medium.com/flecs-4-1-is-out-fab4f32e36f6 last weekend 🙂
The first version of reactive templates in Flecs landed in early 2023*
Right, I’m just saying in the 4.0 blog post it said “Flecs is taking its first steps into becoming a full-fledged reactive framework that is deeply integrated with the ECS” so i feel like it’s understandable to interpret “first steps” as “we’re still working on it”
That's fair, but also that'll be its state perpetually :p
I hope after a bit it’s no longer first steps but i get what you mean
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.
very exciting stuff!
we could generate an alternative representation of the UI components and replace each field by an enum which is either Set(T) or Unset. We could then diff only the properties that are set by the user, and ignore the others.
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.
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)
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.
we have a lot of use cases inside the engine that would benefit from reactivity (e.g. deriving textures from a window size, such that they are automatically recreated when the window is resized). less opinionated about whether it makes sense as an idiom for game design, but wanted to note that
I find that reactivity is great for doing goal-based hierarchical character behavior - but there's no diffing or patching involved.
cool example\
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
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.
A great example of using intermediate incrementalization outside the context of reactivity!
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
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...
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?
right... I didn't consider it from this angle at all to be honest.
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.
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
it may very well be that the components are the preprocessor
though I can see people wanting and making .bsn.preprocess_me
yep, I'm tempted to now...
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"
this is the conversation I'm trying to remember btw
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
blursed version of this: bevy-dhall
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.
yep, such helper functions would certainly be useful for my use case - I could provide built-ins that mods can use in the BSN files
it's difficult for me to say, and I have no authority on what BSN is. Modding systems are what you make of them 😅 lot of different judgements you could make.
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.
different incremental computation domains has very different data model / implementation details.
- libs for general "application state management" library, like
mobx-state-tree - libs for UI, like React
- 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.
Hey yall another quick update. We're getting very close now. Today I:
- 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.
- 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!
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
TemplateI didn't support the former, you had to have a pre-existing parent - kind of likespawn_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.
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 inDefault::default()during expansion. - A "fallback" value may be specified (e.g.
_ | <fallback>) for fields whose type does notimpl Defaultor 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)
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 },
}
}
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.
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
- The Template accepts an IntoSystem impl
- 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. TheStrongSystemId, much like theStrongHandleinsideHandle<T>, can notify a cleanup system when it is dropped. - 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> - 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 lastHandle<ScenePatch>and removing the scene with the templates inside).
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:
- 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.
- Named field structs without fields specified are ambiguous with expression blocks
foo: Foo {}
foo: {}
We could resolve this by just picking a winner (ex: empty expression blocks are actually empty struct patches, or vice versa). However that will make error cases (which will happen) potentially confusing for developers.
- Named field structs with fields require reaching further into the struct to disambiguate between expressions and struct patches.
// V V at both of the points marked with 'V' we don't know if this is an expr or a struct patch
foo: { x: 1 }
This is only a minor complexity issue from a parsing perspective as we can obviously look forward. However it has implications for autocomplete because when typing x we're still in a quantum superposition of "expression" or "field patch". We need to pick a winner, but a percentage of the time we will be wrong. In those cases, autocomplete will yield the wrong results, which will be painful and confusing for people.
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.
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.
Hadn’t thought about the autocomplete aspect of it! That’s a very good reason to be careful of abusing ambiguities. I guess one way reduce them would be to use a different expression syntax (e.g. {{ }}) to disambiguate. Which on the other hand makes it feel less like Rust ⚖️
Yeah I considered this exact syntax. Gets a bit weird for tuple-style, as you can't use double parens: Enum::Variant{()}
Is bsn_list! just a List of BSN's? Is it not possible to just support like a () tuple wrapping of top-level things to convey, this is a List of BSN's.
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"
I suppose I can always apply the template to a ghost node root 🙂
Dont quite know what inherit means in this context, but given there is a functionality difference, makes sense to have them separate. 👍
See the "inheritance" section here for an overview. That bit is largely unchanged since the last proposal: https://github.com/bevyengine/bevy/discussions/14437
Hmm, I will have to wait and see with more examples/code, that section mentions multiple inheritance using a list/collection of scenes to patch before the current patch, but I think I am just confused on terminology at this point. (My fault)
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
Reply ^
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
Wow
God I can't wait to use it
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
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.
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:
- 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.
- The
callbackwrapper. Afaik a constructor of some kind is necessary, as IntoSystem does not work withinto().
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
)]
}
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...
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
)]
}
Great minds think a lot
I want to make sure we support both the "inline" and "remote" use cases for callbacks:
- Callbacks defined as literals inline
- Callbacks coming from outside the hierarchy and passed in as params.
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
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
The same problem is everywhere in the world of computers: "All the good names are taken".
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
Hmmmm
Currently functions in that position are assumed to return arbitrary scenes, not "passed in values that serve as a "whole value patch"
The issue I had was no one added context, "oh its props" "wdym? What actually does it take?" "props"
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
OK so that's fine for leaf widgets. However, a lot of composite widgets need to do some housekeeping before they return a value - and that might require things like loading an asset, registering a one-shot, or doing other worldy (or at least commandly) stuff.
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
OK. So I've been holding off on a lot of the composite widget work until I see the shape of BSN, but once it drops I expect to stress test it pretty hard.
Or rather, a Scene is resolved to a hierarchical set of Templates, which you pass a World into to spawn the entities
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.
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 { })
})
}
}
Interesting
Just edited in MyComponent to illustrate a template that produces a component
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
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)
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.
pub fn menu(menu_items: impl SceneList) -> 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,
} [
{menu_items}
]
}
}
All right, good
Anxious to get started 🙂
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.
Really glad I had bevy_feathers to test on as there were a number of papercuts to resolve that would have blocked you right away 🙂
I'll prioritize getting this out now that I know its actually usable
In React, the children parameter is special.
Let me see if I can find a reference.
Pretty much all web frameworks have an answer for this. I've used the React approach in the past
why is there no comma?
I care more about the DX for the template invoker than I care about the DX for the template writer. So the extra syntax doesn't bother me.
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.
I'm planning on doing a writeup on why not when I put this out, but in short "I believe this results in better legibility and ergonomics"
In general I'm on team "lets make this as rusty as possible" / commas are used in every other position
I am in favor of judicious use of punctuation.
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.
Is there some way we could encode the intent of hijacking the children list into the function signature? A magical parameter type or something? Or is the type not known at this point?
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
My main points are:
- Flat entities (no wrapper tuple) are allowed at the root. In Rust,
x, y, zis not a valid expression - Relationships are now expressed as peers of the components (old proposal:
(COMPONENTS) [CHILDREN], new proposal(COMPONENTS [CHILDREN]). I think in all cases, they are more legible without commas, especially in the very simple / very common case ofSomeComponent [ CHILDREN ]. Comma version of this looks weird, especially without the tuple wrapperSomeComponent, []. - 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
Guessing you’ve already thought of this, but would it work to require an extra token between relation names and items? Like:
SomeMarkerComponent
Observers: [
]
seems like it would disambiguate but maybe it’s too inconsistent
It would! Only reason not to is I don’t like it as much 🙂
makes sense! glad to add something to the bikeshedding pile at least :)
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 🙂
We already support adding additional entity targets at runtime FYI. Just not removing them lol
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
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 🤔
Metadata for observers. Stores a cache mapping trigger ids to the registered observers.
Collection of ObserverRunner for Observer registered to a particular trigger.
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.
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
Yeah. I looked at that, and it would be nice from a complexity perspective. I was very nervous about the perf implications though.
@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.
@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}}.
is there any progress on reactivity ? is there anything I can do to help ? is there existing PRs or experiments ?
initial bsn landing => feedback => working on reactivity, is I believe we're it's going.
I'm considering providing a temporary reactive solution as a third-party crate, once BSN ships.
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.
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?
would it be possible to replace thoose one-shot system with component change detection system ?
And then by 0.18 it will be a proper part of bevy, I imagine?
It's a hard problem, and I don't know the answer. There's been some proposals for change detection on queries, but the problem is not well-defined.
I'm looking forward for it, I would likely use it.
We need to get a little farther down the reactive system/system param route before this becomes reasonable.
Part of the goal is to get a large ecosystem of use cases
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.
I agree, but is this in response to the idea of using Reactive Systems/Params?
Not whether reactivity will be used, but how.
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.
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.
The second part can have issues though, if you aren't presented with enough specialized designs/use-cases then the low-level abstraction you end up with can be less broad then it ought to be, leading to what you are saying where you need access/knowledge of as many use-cases as possible before attempting the low-level conversion.
Forming opinions on haalka and flecs's approach would be really helpful
It would look like this:
(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"
Yup this would work. It just creates potential confusion with the {} syntax. Almost certainly better than template_value() of course. {} vs {{}} could be confusing for people, but so could {} behaving differently in different contexts
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.
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.
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.
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
I think the differences are learnable, and easy to spot once you learn the difference; also I think there's value in stealing cognate syntax from other templating languages, so long as the meaning is analogous.
Remind me again what the meaning of the colon is in this context.
“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
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 🙂
It’s semi-necessary in that if we drop “:” the struct-style scenes we talked about become ambiguous with template patches
OK so two sibling buttons looks like this then?
:button(ButtonProps)
Autofocus
AccessibleName("My Button")
:button(ButtonProps)
AccessibleName("My Other Button")
Those would each be enclosed in parentheses and comma separated in the list
Ah ok
Although supporting omitted parentheses in that context is on the table
Parens is not too bad
I'd prefer to have the parens
Increased clarity
I think omitting it in that case harms legibility though
So
(
:button(ButtonProps)
Autofocus
AccessibleName("My Button")
),
(
:button(ButtonProps)
AccessibleName("My Other Button")
)
?
That's better
@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?
BSN is "resolved" to templates (by going through each inherited scene once they are all loaded) stored in a dynamic in-memory format, which is currently stored inside the scene asset adjacent to the unresolved scene. Where this ends up living in the end is TBD, as I don't like that we're mutating the asset to add "new" information
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
will it be reasonable to diff templates ?
unless I missed something, haalka's weakness is that all the UI state must be inside some Mutable<T>, so it cannot operate on top of existing components. I don't want to write systems that mirror every relevent data from the world to the UI state
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
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)
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.
Yes, but only in the current form, where dynamic per-field representation was not prioritized. Adding that in is not a particularly large change and theres room to experiment / do some cost-benefit
If my component stores a Vec with a dyn trait inside, will it be possible to instantiate it using bsn? 🤔
@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:
DialogContainerDialogDialogHeaderDialogContentDialogFooter
WhereDialogContaineris 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.
only way to store/spawn stored component is via reflect
proposal: server logo stays pride themed until bsn branch drops
With BSN you define scenes, which stores components.
And I just curious if it will be possible to create components that store boxed traits 🤔
It seems like bsn is really two closely related languages. There's the serializable static format, and then there's the macro.
With the macro, I imagine it will be possible to do stuff like this. With the static format, potentially not.
Makes sense, thank you!
Tbf I struggle to imagine a serialized boxed data
Might just require some way to tell bsn how to properly initialize said component
there's boxing as it exists in relation to the stack, and then there's boxing as it is used to create dyn Trait objects.
Maybe not dyn Trait, but maybe an ReflectTrait version.
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
I completely agree with leaving out the reactive stuff for now. I think that the way forward is to have one or more third-party crates that provide an interim reactive solution that works with BSN, use those to collect a healthy population of practical examples, and then use that to design the final framework.
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.
I do the same. Been thinking about how to do that incrementally, but that's really hard
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... 🤔
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)
I've just pushed my Next Generation Bevy Scenes draft PR. Please thoroughly read the description before commenting!
https://github.com/bevyengine/bevy/pull/20158
Achievement Unlocked
yay!!
right well there goes anything i was planning for the next week 🥳
Left some comments.
One thing I didn't comment on because I'm not sure it's really related to BSN, is I don't see a way to do callbacks that aren't registered as systems with the world, which is a big limitation./
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.
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.
bevy_(dioxus) for instance just has the single Element type https://github.com/JMS55/bevy_dioxus/blob/main/examples/demo.rs
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
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.
This is unlikely to land in the upcoming Bevy 0.17
😢
But understandable, it's a huge pile of work.
_ _
_ _
_ _