#Next Generation Scenes

1 messages · Page 11 of 1

rare whale
#

I think the truth of that proposition varies based on the scale of the hierarchy: as you get to larger and more complex scales, the meaning of things gets embodied in the shape of the tree rather than individual components. Not only in UI work, but in 3d scenes, a lot of the higher-level entities have a purpose which is organizational.

For these kinds of groupings, it doesn't hurt to insist that they have a concrete component, but it isn't necessary either.

split harness
#

I don't think we need everything to be a component scene. Only things that are behavior-defining / object-like

#

Ex: every "control" in Feathers benefits from being a "scene component"

thick slate
#

Doesn't this break our bevy_ecs / bevy_scene division?

split harness
#

And it could be behind a feature flag

thick slate
#

I still don't like it as much

thick slate
split harness
thick slate
#

(obv feature flagged lol)

split harness
#

They are "core data model stuff"

thick slate
#

Yeah, I think I agree there

#

All of my previous suggestions / early thoughts on this problem space were framed in terms of "thing that lives in the ECS"

split harness
#

However, I do think organizationally and perceptually, it is better for them to be separate crates

thick slate
#

Mhmm

#

The serialization stuff also complicates things

split harness
#

But I think this one specific tweak is a good idea personally

#

In the interest of making things clearer / easier for users

thick slate
#

I could live with it lol, but I am going to make you write good docs for the derive macro finally

rare whale
#

These have no inherent behavior

#

Anyway, I don't really want to argue about this, I just want to note my position for the record 🙂

silk lava
#

By querying for scenes, what does that look like? Cause even thought I'm not sure we will be able to uphold it for much longer, but I thought the idiom was that the things you query for should be obvious and preferably the type itself. Aka dont have QueryData for a set of components, just query the set of components.

split harness
silk lava
#

Oh okay, we have that today with required components. 👍

split harness
#

You aren't writing a query for "something that matches the full description of a scene", which would be massively inefficient. You just know that anything with the Player component will have the "player scene" ready to go

silk lava
#

If you were to make the SceneConstructor part of Component, could you then make the Bundle for the Component have an BundleEffect of the scene, where a non-scene Component still has no BundleEffect thonk
Then you wouldn't 'have' to use spawn_scene and you would still get somewhat valid results with spawn.

split harness
silk lava
#

Hmm true, thing is when compared to the other component types they all work even when not using their intended paths, except scenes and assets which is unfortunate, but not sure how you mesh the async nature to make them work in the sync spawn context.

split harness
#

If we're feeling spicy we could add an associated type to Component and use that to block it from being added via a normal bundle

#

But I think the hook is the least invasive / lowest complexity approach. Imo its the move for now

queen oak
#

Yes, I like this

#

I think thats less confusing

thick slate
rare whale
#

TBH, I liked the previous approach better: there were different moving parts, and I understood the role of each part and how they fit together.

split harness
rare whale
split harness
#

If it is separate, then the user needs to wire up that validation themselves

split harness
#

Or an example that uses the derive?

rare whale
#

What would the feathers button scene type look like in this new scheme?

split harness
split harness
split harness
#

FeathersButton users could just add the ButtonVariant component manually when they want to set it

#

(given that it doesn't drive the Scene outputs)

rare whale
#

Anyway, this is a separate argument, and button isn't a great example, I should have picked a better one.

thick slate
#

(back burner for now, but tell me when you want it)

rare whale
#

An example that comes to mind is ListView: in order to get scrolling to work correctly, the rows have to be the grandchild, not the child, of the list view entity.

#

So we can't just patch them in with Children [].

tardy beacon
#

I don't know if this is a problem or not, but I noticed that the debug symbols for an app with a lot of BSN are... quite something.

#

For feathers_gallery, the longest function signature is 121,645 characters.

tawdry owl
amber pebble
#

I'm also curious how heavy use of bsn affects binary size

tardy beacon
#

The code that's specific to feathers_gallery is 44.5KB (windows x86, release + thin-lto). I'd say that's not great, but not the end of the world either. (The rest of the engine is waaay chonkier)

#

I might poke around a little more as I'm not sure why some of the drops are needed. I think they might be there to handle Vec allocation failure... but that seems wrong - I thought allocation failure in Rust was supposed to be a panic.

#

On compile times, standard_widgets is 8.1s, feathers_gallery is 11.3s (+40%). That may not be a fair comparison, but they're roughly the same size in terms of code. (Tested with release profile, windows x86, touching just the example file. No dynamic linking, linker = LLD)

tardy beacon
copper dragon
lime veldt
#

though if you have the RAM it is maybe fine to have these unhelpful blobs lying around?

copper dragon
rare whale
#

@split harness @thick slate I'm looking at the crate docs for BSN, and I saw this line:

this blanket impl always replaces the entire value — there is no field-level merging. If you want per-field patching, you must derive FromTemplate explicitly.
(From https://dev-docs.bevy.org/bevy_scene/index.html#composition-and-patching)

If that's the case, then how is Node patchable? Node derives Default and Clone. Later in that same section it states:

Deriving FromTemplate and Default on the same type is not allowed
Either my understanding is faulty or this doc is.

thick slate
#

Docs must be wrong then. I thought that was the key trade-off 🤔 Issue please

split harness
thick slate
split harness
split harness
#

The type-driven approach was largely a legibility / hand-written-code-clarity driven play

#

For example this:

MyComponent::patch(|template|  { template.field  = 10 })

Is a bit easier to type and read than this:

#
|context, resolved_scene| {
  let template = resolved_scene.get_or_insert_template::<<MyComponent as FromTemplate>();
  template.field = 10;
}
tribal yacht
#

btw, I think the A-scene label for triage is no longer accurate

#

it talks about serialization, which is no longer in the bevy_scene crate

thick slate
split harness
#

@thick slate my doc plan is to port the PR description over to the Scene docs

silk lava
#

Is it gonna be possible to create an LSP that gives you both structs(Comp&Props) fields to auto-complete? Also does the order in which they are input matter? (Props before, Props after, or doesnt matter)

cobalt stone
#

What does this solve that either:

  • An actual seperate scene (why are we tying scenes to components?)
  • Required components

Don't solve?

silk lava
#

Still think I would rather prefer an syntax as such:

:SceneStruct{ field_a: ... }(SceneStructProps{prop_a: ...})

Then its less implicit as to what is happening, as you see the function call syntax which is kinda what the scene component semantics represents that a component can represent an function that returns a scene, and also get to see the actual type being passed as props and can more easily look it up.
Downside, is that you must know what the Props type is to type it out, although I think that due diligence will lead to less bugs overall.
I dont know if this syntax might be ambiguous/not-possible though. 🥹

dapper sky
#

I guess templates also do that

silk lava
#

The above is valid rust code.(minus the :)

dapper sky
#

yeah it was a poorly thought out jab

#

I think maybe the thing that irks me here is that SceneStruct + SceneStructProps is a weird duplication of similar terms.

#

and that a desire would immediately arise for that duplication to be de-duplicated

leaden dew
#

I've been wanting an ergonomic way to define a proper constructor function like this in like every jam and from a brief survey of bevy jam 7 I did a while ago almost every open source project I looked at was solving this problem using On<Add, MyMarker> observers which requires a bunch of extra legwork (plugins, systemparams)

cobalt stone
#

both of those approaches have holes: the separate scene isn't associated with a queryable component on the type level

#

But why do we want this?

#

The player example is kinda contrived. I'd just use a Player::spawn() function that returns impl Scene

#

Or just a standalone spawn_player() function

#

I'm not seeing the need to tie components to specific scene instances

leaden dew
#

hmm. it is sort of a reification of the idea of "identity components" - components exactly like Player (but also specific enemies, items, props, whatever) that carry a single canonical representation and can be queried against for entity-specific behaviors. it's at the very least extremely common from what i've seen in bevy games, but it is also arguably not proper ECS

#

justification seems like a fair question

silk lava
# leaden dew hmm. it is sort of a reification of the idea of "identity components" - componen...

Identity components don't really matter for reasoning, because you can do them with Required Components as well and have the same downsides of lacking archetype invariants to enforce the component set.
BSN upside: allows external state to be passed in
RC upside: allows adding/removing at runtime.

The BSN upside could be transferred in some way, by allowing FromWorld to take props.
The RC upside could be transferred, if the scene function is stored as a function pointer on the ComponentInfo, such that you could access it, take it, then create a new scene that inherits from it or something completely different.

split harness
# cobalt stone I don't really get the motivation for this :/

The big gap that this is filling is around abstraction / interface building. Many categories of component are built under the assumption that a scene / hierarchy of entities will be present. Yes we could just have a Player::scene function, but there is nothing that ties Player and Player::scene together. I strongly believe that there is a benefit to having a single importable / nameable type that people can refer to that is the "thing" that is being spawned. Forcing people to know that the ButtonWidget component requires some other arbitrary scene symbol (and where that symbol "lives", ex button_widget() or ButtonWidget::scene) to satisfy its functional requirements is error prone and overly complicated. Additionally, scene functions on their own are not "nameable" in the ECS. You can't open an inspector to see all of your button_widget() scenes. You can't write a query to iterate over them. You can't write an observer that runs when button_widget is spawned.

Additionally, many users of raw scene functions have explicitly requested "struct like" scenes, as my_scene(props: MySceneProps) are a common pattern, and it is more boilerplatey than they would prefer (ex: @rare whale hit this with the Feathers widgets, and I agree that the ergonomics were suboptimal).

split harness
# cobalt stone What does this solve that either: * An actual seperate scene (why are we tying s...

The BSN vs Required Components use case overlap is something we've discussed in the past. I agree that it feels like these could be reconciled, and I agree that forcing users to choose between two abstractions is not ideal. The fundamental difference between them is that BSN is dependency-aware and has access to a World context / entity spawn context, whereas Required Components are not dependency aware and do not have access to a World / entity spawn context. Required Components are also much lower overhead, as they're literally just a constructor pointer.

Unifying them might be possible, but it would require a pretty signifcant change to our ECS spawning internals. Currently it is extremely unsafe to provide World access when constructing required components.

silk lava
# split harness The big gap that this is filling is around abstraction / interface building. Man...

I agree with initial statement, but what about reversing the logic: world.spawn(SceneComponent), then the user needs to know that this component is a scene, as this will error.
You could argue the error is for that, but then why not error when: world.spawn_scene(SceneComponent) with error message: SceneComponent: !Scene, Did you mean SceneComponent::scene()?
This hints of a bias towards scenes, where most users will end up fn scene() {bsn!{MyComponent}} to feed into that bias.

split harness
#

I do think it is worth spending time trying to reconcile the two, but its not going to be a trivial change, and I think the benefit in practice would largely be theoretical

split harness
# silk lava I agree with initial statement, but what about reversing the logic: `world.spawn...

I agree with initial statement, but what about reversing the logic: world.spawn(SceneComponent), then the user needs to know that this component is a scene, as this will error.

Yes to avoid a runtime error they do need to know that SceneComponent is a "scene component". I'm pretty sure we can make this a compile time error, it would just be a bit invasive. I think it is pretty uncontroversial that logging an error when a component that definitely requires a Scene is spawned without a scene is much better than the status quo of not logging an error and things breaking in arbitrary and likely confusing ways.

You could argue the error is for that, but then why not error when: world.spawn_scene(SceneComponent) with error message: Did you mean SceneComponent::scene()?
How would it know how to report that without some sort of static tie between the function and the component?

split harness
silk lava
split harness
silk lava
#

Also in a generic context how does this work?

fn system<T: Component + ?SceneConstructor>(world) {
    if const{T as SceneConstructor} {
        world.spawn_scene(bsn!{:T})
    }
    else {
        world.spawn(T);
    }
}
tribal yacht
#

This is not an endorsement for #24008, as I'm unsure how usefull it will end up being. But if this is the direction Scenes are going to go in, could you also implement it for Resources @split harness ?

#[derive(Resource)]
#[resource(scene)]
struct ResourceA;
silk lava
split harness
silk lava
#

That is my point though, if they are fundamentally different things, but they both produce components, and as you suggested the above syntax may never be stable, then how in a generic context can anyone work with Components anymore?

#

Maybe some kind of supertrait Component: Data + Scene: Data then a Scene Component can just impl Scene, and the ECS only cares about Data, and then you can bound your generics on either Component or Scene and know what calling context you must use. Probably not, but I dont see any other way.

split harness
#

Or perhaps the better way to put it is "the set of components that theoretically shouldn't be spawned right now is infinite". Context has always mattered

#

If you are generic on Component and you do INSERT_ARBITRARY_ACTION_HERE, there are some components for which that would result in an error

#

Resource components are an easy example of this

silk lava
#

Resource wouldnt error there, if we had allowed unpinned resources, but thats long gone now.

split harness
tribal yacht
#

it can definitely wait, but I want to point out that these sort of things are now pretty easy to do

#

if there's a good reason to do them

split harness
split harness
#

@silk lava can you post your PR message as a comment on a line of code so we can have a threaded conversation?

#

Oof I deleted it thinking I'd still have access to the message in the history. Sorry if that made you retype it

silk lava
#

No worries, it wasnt very long. Im gonna leave off the @ vs {}()syntax yap, because its just my pure opinion.

leaden dew
# silk lava Identity components don't really matter for reasoning, because you can do them w...

I would argue scene components are a massive step beyond required components in terms of reifying identity components (making the concept "real" in the engine), in particular using required components this way can even now be interpreted as a kind of "misuse" of them (and indeed it feels kinda bad to do too much via RC especially when hierarchy or observers requires you to split it up). they're certainly intended more for helping to enforce invariants so that it's easier to make assumptions when writing systems

#

but anyway I only brought up the identity components thing because it seemed like maybe what Jasmine was getting at. I think it's a better data model either way or at least is more likely to be useful for people in the wild

viral roost
#

So my understanding is that a Scene Component is there to define the typical construction of a marker component. How would you handle having a Robot component, but wanting to have a scene construction for "robot with lasers" versus "robot with swords" versus "naked robot"? In this case, all Robot's have the same basic systems

queen oak
#

i'm a very big fan of these scene components, however i take issue with this note:

Attempting to spawn a scene component on its own without the scene (ex: commands.spawn(Player::default())) will log an error, as Scene Components should never exist without their scene.
See i don't like this, because we aren't keeping the fact that all the other components in the scene have to exist. We can insert a player, we can remove player, we can insert and remove other components in that scene fine. If we want to we could try to at runtime prevent invalid moves like this, but as of now this seems sorta a weird thing to do 🤔

#

instead of #[require] #[always] kinda move, but that doesn't exist yet, so i don't think we should prevent commands.spawn(Player::default())

split harness
#

(based on arbitrary app logic)

split harness
queen oak
#

so the intention is not to do Query<&Player> in isolation, but to do Added<Player> or On<Add, Player> etc

split harness
#

The intention is to make Query<&Player> work "by default" for things like accessing Player/Hand. As long as the author doesn't remove Player/Hand anywhere, then every Query<&Player> system will "just work"

#

Without needing to worry about things like "ensuring the Player scene is fully loaded first"

leaden dew
#

is that Ready trigger theoretical or does that exist right now on main or in your PR?

split harness
leaden dew
#

it sounds like a godsend for loading screens

split harness
split harness
# viral roost So my understanding is that a Scene Component is there to define the typical con...

You have many options, based on the needs of the situation:

  1. Parameterize "Robot with lasers" using Scene Components + props: :Robot { @lasers: true }
  2. Define "lasers" as a child scene component :Robot Children [ :Lasers ]
  3. Define lasers as a child component :Robot Children [ Lasers ]
  4. Define lasers as a root component :Robot Lasers
  5. Define lasers as a scene function: :Robot lasers()
  6. Define "slots" for addons, which the Robot scene component can place in the appropriate part of the hierarchy: :Robot { @left_hand_slot: {bsn!{ Lasers }} }
viral roost
#

I guess, what would my #[component(scene ... look like for Robot ?

split harness
thick slate
split harness
#

Are you willing to take that on too?

thick slate
#

I want to get more hands-on experience with bevy_scene early

#

To avoid a repeate of bevy_assets

split harness
#

Landing with Ready would be very nice

split harness
#

@thick slate pretty sure we want to trigger Ready right after the apply_related calls in resolved_scene.rs

#

I believe there will be some "trickery" necessary to ensure that we don't call Ready twice when spawning with the cached ResolvedScene::inherited scene

#

Actually maybe not, because it inlines the logic rather than wrapping it (thanks to the BundleWriter changes)

#

But if you see a double Ready trigger, thats a good place to look

rare whale
#

@split harness So I take it you prefer FeathersButton over feathers::Button

bitter patrol
#

I'd say feathers::Button makes it look like just a Button component that happens to be inside of a "system" called Feathers, instead of it being some kind of "feathery button"
goes better with third party libraries in the future which could be called whatever::Button

#

just my two cents

#

imho feathers::Button looks more serious

#

a bit bikesheddy tho

lime veldt
#

I prefer to not have to import things as SomethingElse so it does not collide with some other Button though

karmic rock
# split harness My Scene Components PR is out! ("big" because of examples / ports / tests / docs...

Interesting how we've seemingly landed on opposite approaches:

Note that these ("props") are not available on the final component.
In flecs a scene component (template) is a regular component where the props are component fields

as spawning a standalone MyButton component without the associated scene is very likely to be broken
In flecs the MyButton component is the thing that spawns the scene, which is just entities and components. There is no separate scene object.

What I did with flecs script is basically just create a notation that spawns entities and components. Similar to webdev, where a React/Vue component just spawns HTML/CSS/JS. The browser doesn't need to know how that data was spawned.

#

It sounds like with the introduction of BSN bevy is moving away from using the ECS as the data layer, and turns BSN into the data layer

#

(or at least the scene model that's implied by it)

silk lava
karmic rock
#

Yup

#

That's exactly how it works

queen oak
# rare whale <@153249376947535872> So I take it you prefer `FeathersButton` over `feathers::B...

I wayyyy prefer feathers::Button, and I would very much like to try to convince everyone here that its a way better way to do this kind of thing.

For example, in alternative frameworks one could have malek::small::Button, etc.
By making it possible to do my own default styling and moving things around I can easily copy and modify scenes by just swapping out the use statement.

Furthermore this is what namespacing is for, looking at feathers::Button I know that I can just look at Button and I can easily filter out, mentally, the feathers section, it makes it far more readable.

karmic rock
silk lava
#

Is it expected the scene will be fully available/loaded by the time the hooks are done running for the Button Prop Component?
If someone were listening for On<Add<Button>>, would they be able to access the scene stuff it produces? Or separate Ready/Scene event?

karmic rock
#

It's synchronous, so yeah the entire scene is available once the hook finishes. Which means you can register an OnSet observer for the component which can make assumptions about the scene being ready

queen oak
silk lava
karmic rock
#

The scene spawning code is only responsible for creating the entities+components, not for the second-order effects this produces

#

You can load assets synchronously or not, that's up to the application

silk lava
#

Heh, reminds me of Firefox and Chrome back in the day, one used to load piece-meal as it was recieved, the other only presented once everything was loaded. (I changed to the former, because I disliked the latter 😛 ) /off-topic

exotic panther
tardy beacon
#

I also turned feathers_gallery into a benchmark - the current benchmarks were hiding issues because they're so small. I can turn that into a PR if desired, although it's a bit of a cut and paste job.

split harness
split harness
split harness
split harness
karmic rock
#

Yes, but this is not coupled to asset loading, just populating the ECS with the right entities

split harness
#

(but do not have to)

karmic rock
#

Yeah that’s not very different I think from what I do. This is integrated with glTF loading in my own engine. The key difference is that the actual loading of textures, meshes etc from disk can be asynchronous.

split harness
#

Ex: by default Handle<Image> assets are loaded asynchronously / on their own time and they "pop in". But the system is built such that you could make Handle<Image> loads block scene loads

karmic rock
#

Yup same

split harness
#

Presumably in the second case (image "required" by scene), if you try to spawn the MyButton scene it would block synchronously until the image loads?

karmic rock
#

It's fully up to the application. The only thing that my implementation does is instantiate entities & components synchronously (you can't really asynchronously instantiate things into the ECS anyway). Any second-order effects like texture/mesh/... loading is implemented with hooks or observers.

#

For example, if the scene instantiates a Texture component, the hook for that component would handle loading the actual texture (async or sync)

#

The scene implementation has no idea, it just populates the ECS

#

What happens when those entities/components get instantiated entirely depends on which hooks/observers are registered

split harness
#

Makes sense. There isn't really the concept of a blocking asset load in Bevy (you can theoretically do it but it isn't a part of the standard flow). An asset is either ready now (and you can do the thing that depends on it) or it isn't (and you need to wait). Blocking the ECS thread (the thing holding World) to do things like file io is pretty much never what we want.

So something like a system that does large amounts of arbitrary blocking work (ex: loading and resolving an arbitrary number of BSN files and other assets) to make a directly spawned / standalone MyButtonscene component valid "right now" isn't a path I'm particularly interested in.

#

That type of immediacy does have the very cool property of making a MyButton scene component spawn via normal sync world APIs "just work". But I think it is desirable to push people into async / up-front-dependency-aware APIs (ex: encourage them to either build loading screens that don't freeze, then spawn when things are fully loaded, or "queue" operations to happen in the background that resolve in their own time whenever they are ready).

karmic rock
#

I can see the reasoning behind that. I personally prefer the web model where data (/HTML) can be instantiated immediately and unconditionally, and have the engine/browser handle the async logic. It also creates a clear separation between a data & presentation layer, where a renderer just presents what it finds in the ECS

#

& the ECS can contain any entities/components the application wants

#

e.g. it's not inherently important for the ECS/data layer to know whether an image is loaded, as long as it knows that there is an entity with an Image component. Whether the image is loaded or not is only important for the renderer

#

(simplifying- of course in some cases you want application logic to also be aware of that, just pointing out that this is determined per subsystem)

split harness
# karmic rock I can see the reasoning behind that. I personally prefer the web model where dat...

I do generally agree here for many categories of async things. I think components like Sprite { image: Handle<Image> } should be spawnable immediately / the image can "pop in" on its own time.

I think an important distiction in "web terms" is that an <img> element is an object whose properties are fully known at runtime, whereas some button_template.html file on a server somewhere is not.

#

Someone building a web application that instantiates button_template.html would either need to ensure that it exists in memory on first load, or use eventing to wait until it is ready.

karmic rock
#

That is all handled by the browser though. It loads the HTML file, dependencies etc. I just provide the browser with the entry point. It makes sure that the DOM is asynchronously loaded, and synchronously updated

split harness
#

Haha I doubt we need to "websplain" to each other. I'm pretty sure we're both well versed in the web world.

karmic rock
#

Right, I agree that's an important aspect of asset loading. I guess what I'm saying is that I see this as three separate layers:

  • one that fetches documents, dependencies etc.
  • one that (synchronously) instantiates documents into the data layer
  • one that (synchronously or asynchronously) loads assets depending on what got instantiated in the data layer
#

None of the layers have a dependency on the other ones. I can instantiate things in the ECS directly, I can just run a script directly

#

I would have expected BSN to implement 2), not 1)

split harness
#

Makes sense! My argument is that some things that you would want to "synchronously instantiate in the data layer" can only happen after some asynchronous process (even if you know the "name" of that data).

#

Our difference in design comes down to whether we are willing to transparently treat the asynchronous process as synchronous.

karmic rock
#

I think it’s fine to have an explicit asynchronous step, but I see that as a separate thing.

I think there’s value in a DSL that just makes it easier to populate the ECS and which is decoupled from resource loading.

viscid cradle
karmic rock
#

In any case, interesting to see the different approaches / tradeoffs 🙂

split harness
split harness
heady latch
# split harness I do generally agree here for many categories of async things. I think component...

This may be different for others, but in my personal productive work, this is one of the biggest anti-features I have to fight. I frequently need access to assets, and having to deal with the question of "is this loaded or not?" for everything is a big amount of bloat along an entire game. This is why I always preload everything I may need for a level whenever the player enters it. An async loading API is nice and all so the loading screen can advance without freezing the window, but the API behind asset access is imho very poor due to this mechanism.

split harness
heady latch
split harness
#

Yup!

heady latch
split harness
#

Disclaimer: that bit isn't wired up yet. I had it partially implemented awhile back but trimmed it back in the interest of moving forward

#

But the interface / dataflow would look something like:

bsn! {
  Sprite {
    image: HandleTemplate::path("sprite.png").dependency()
  }
}

This becomes a final Template in a ResolvedScene, which has Template::register_dependencies(), which we call after the scene is fully resolved, feed the results into the asset system and defer spawns until everything is loaded.

split harness
heady latch
heady latch
exotic panther
#

more than willing to get rid of being generic over the component though

split harness
#

Ex: you aren't generic on a specific single relationship. The path defines what relationships are necessary at each node in the path

exotic panther
#

could that not be on a more generic method that deferres to the methods I've added?

split harness
#

Hmmm maybe lemme review

#

Kind of. To support arbitrary relationships in path strings it would need to be dynamic

#

But functionally I think your methods otherwise work. I guess I'm not opposed to the general strategy here, provided we have a friendly top-level API that defaults to ChildOf / Name / strings (and relies on a slower dynamic path for other relationships)

#

We could also have a faster non-string runtime "EntityPath" representation that is more typed

raw mesa
split harness
exotic panther
cursive parrot
split harness
#

We could also make them configurable (ex; HandleTemplate::UseContextDefault)

split harness
exotic panther
#

I don't have the reflect knowledge to do the wizardry required to get support relationships in the string so I will leave it generic over relationship in the hopes that others can pick it up later and do what I can't

split harness
#

(and feel free to skip the dynamic reflection bits)

exotic panther
thick slate
split harness
tardy beacon
# split harness Thanks for investigating! I left a reply. I suspect that you didn't fully explor...

Yep, I misunderstood! I thought you meant replacing TemplatePatch with a closure and then finding a way to implement Scene for that closure.

On what you're proposing, I'm still a bit unclear. The idea is that the bsn! would have one giant closure which effectively implements one giant Scene::resolve? So a good chunk of the codegen will need restructuring? (Full disclosure - I do not have the energy for something like that, so just asking out of curiosity).

split harness
#

I think in pratice it wouldn't be a major overhaul. Just some top-level orchestration changes

#

But yeah I'm also probably the person best equipped to explore that space. I just sadly won't be able to prioritize it for awhile

thick slate
silk lava
#

Is it intentional, that Scene entities are always only ever a single scene? So there is no way to have 2 separate scenes on the same entity? The SceneComponent meta-marker would make me assume so.
That seems like a big downside, and may lead to entity objects, where most entities are just a single scene, and you use the hierarchy to add functionality via another scene, which starts to look a lot like OOP designs.

split harness
#

SceneComponents are definitely very "object like". Cohesive units of functionality

#

Notably this does not force the "inheritance dependency hell" problem, as you can always add more components / mash more scenes on top

#

I will also note that in a world where multi-inheritance was allowed, if you took a ButtonWidget scene component and you could mash it together with a ScrollbarWidget scene component, you would get nonsensical / broken results.

#

Hierarchical scenes are not inherently composable in the same way that flat components are

silk lava
# split harness I will also note that in a world where multi-inheritance was allowed, if you too...

Right, but if you wanted to add an 2D Sprite setup, which is its own scene, and a Player controller, also its own scene, and you want to add that to the player you are forced now to do inheritence OOP style.
This wouldn't be that big of a deal, if the Scene/Indentity component wasnt apart of the behavoiur set, but they are, meaning this limitation of one scene per entity also means one identity per entity, which is literally OOP.
Given that the Scene system is going to be quite prevalant, I would argue it would be used the majority of the time as apposed to spawning individual components or bundles anymore.

Therefore the default structure/workflow is to create Scene Components for Identity's to behavoiurs, of which now each entity will only be able to subscribe to 1 behaviour set. This means two separate systems that rely upon two separate identities will never run over the same entity. That is anti-ECS.

silk lava
# split harness I will also note that in a world where multi-inheritance was allowed, if you too...

Besides for archetype invariants, provided by the components on the entity, there should ideally never be anything that prevents me from adding/removing a component/set of components/scene of components from an entity.
Where archetype invariants, could be used to uphold no 2 scenes/entiities shall contain the same Indentity component. That however is orthogonal to how I add/remove said components.

split harness
#

This is solved any number of different ways. If you are trying to mash together two arbitrary hierarchies, you have created a bunch of problems for yourself (things are likely to break in arbitrary ways, performance is a concern because arbitrary compositions of scenes require re-computations). In general the strategy should be

  1. Default to using Components to define sharable logic. Low complexity, maximum reusability (no hierarchy mashing), highest performance
  2. Use non-inherited scene functions to define shareable logic. Higher complexity, medium reusabiltiy, low performance (full recomputation of the final scene).
  3. Use oo style inherited scenes. Higher complexity, low resuability, medium performance (inherited scenes can be cached / precomputed).
#

In theory we could provide a non-inherited/non-restricted version of scene components that fills the same role as (2). We'd need new syntax, and I'm honestly not sure it is practical in the real world

#

We can adjust as we go

silk lava
split harness
silk lava
#

Okay, Two ResolvedScenes then kek

split harness
#

ResolvedScenes are not the public interface that most people interact with. The Scene API does not speak "resolved scene" only patches

#

Also, writing ResolvedScenes on top of each other could invalidate the assumptions of the scenes. If they have overlapping Transform components, the second ResolvedScene's component would clobber the first one

silk lava
#

ResolvedScenes should be interacted with though, otherwise this issue that we are discussing shows its self otherwise. If you could have multiple ResolvedScenes on an Entity, then you wouldn't need to recompute the non-changed one, sure the second ResolvedScene may clobber some of the other ResolvedScenes entities/components but that is the nature of adding whatever you want to entities, you the user are responible for knowing what they have and what you add to them will work.

split harness
silk lava
split harness
#

And notably, "scene component-ness" can be statically enforced, if we decide it is necessary and the tradeoffs are worth it.

leaden dew
#

wouldn't it be possible to forcefully mash together two scenes anyway by chaining commands.spawn_scene(first).apply_scene(second)

split harness
#

For exactly the reasons we're discussing here

silk lava
#

Got my points out of the way, I still disagree about Scenes being limited to one, or not allowing multiple ResolveScenes per entity. It will lead to more OOP behavior which will hurt performance in the end.
Either way though, I appreciate the hard work. 🛒 🩵

split harness
#

Given the tradeoffs, I'd prefer to start with constraints that can perform well and behave predictably and then later lift them, than to start with the wild west (both from a performance and behavior perspective)

#

Also as a disclosure: I think "pure-ECS at all costs" is just as bad as "OOP at all costs". Rust would be a better language if it embraced inheritance more fully

#

There is no one tool that is best at everything. Some problems are OOP-shaped and thats ok.

#

(for those who aren't aware, Rust is an OOP language, it just doesn't go all the way)

silk lava
# split harness Also as a disclosure: I think "pure-ECS at all costs" is just as bad as "OOP at ...

Oh for sure, in fact I think I may be the only person that has suggested multiple times recently that we should have a form of OOP storage, where OOP semantics can be practiced, because in certian data-access-patterns/use-cases OOP is exactly what you need. Although I wanted this to be based on Storage, with as little change the rest of the API as possible, basically unifying it with the other storages.
Although I have yet to figure out what that unification looks like, besides for just wrapping the Components in Arc<Mutex<C>> and storing them inline in the Archetypes. Theres definitely something there, just gotta get that glimpse.

thick slate
vapid forge
#

edit: PR created
is it intended/required that Scene andSceneList are implemented for ()? Its easy for this to lead to errors like fn thing() -> impl Scene { bsn! { Node }; } silently doing nothing, which would be alleviated by not implementing Scene and SceneList for ().

On the other hand, theres a test case called empty_scene_expressions, but its also the only thing which breaks if Scene is not implemented for ().
For SceneList, the unit type is used in a lot of cases through scene_list!() tho it seems possible to have this return something other than (), maybe an empty Vec, for the empty case. That way, the impl for unit could be removed, raising unimplemented error on accidental ;

lime veldt
#

I kinda want that for Bundle too for the same reasons

exotic panther
vapid forge
vapid forge
vapid forge
split harness
# vapid forge replied!

I think I'm most amenable to this in the form of "we shouldn't support empty tuples at all", rather than hacking it

#

Although I still think theres significant value in supporting (), largely from a devex standpoint (people start with an empty tuple and build out, the equivalent of an "empty" argument when accepting impl Scene as an input parameter, etc)

thick slate
#

@split harness @vapid forge could we use macro-parsing to detect when we have (some top-level code) ended with a semi-colon and manually throw a compiler error?

split harness
lime veldt
#

I actually would like that be a RA/clippy thing

split harness
#

It would have to be wrapped one level up (ex: at function level):

bsn_fn!(my_bsn_fn, {
   #Hello
   Children [ #World ]
})

But at that point the problem isn't a problem anymore

#

And also ... ew

random river
split harness
#

Although I suppose bevy::feathers::controls::* already does that pretty nicely

vapid forge
vapid forge
#

wait

#

that also works for Bundles which is fun

#

less useful, because theres no central place to apply it i think

lime veldt
#

yeah there is no into_bundle you could put that on

vapid forge
#

wait, must_use can be applied to whole traits

#

alas, that doesn't work

#

its apparently not checked that way for traits

lime veldt
#

quite sure you don't want to turn every () in the app to #[must_use] thonk

vapid forge
#

true, which is probably why it doesnt work like that for traits

#

alas i dont think the Component derive can applly must_use either

random river
vapid forge
rare whale
split harness
#

(Aka the headless widgets)

rare whale
split harness
#

The longer names also makes mixing and matching widgets across libraries much clearer

split harness
rare whale
#

@split harness Speaking of which, I'm wondering what is the plan for bsn assets that inherit from bsn macro scenes. Like if I have a library of widgets that are defined in rust code, and I want to invoke those widgets in an asset.

split harness
#

Things get harder for generic parameters (ex: fn scene(input: impl Scene))

#

But things like Box<dyn Scene> on scene constructors should be pretty straightforward to support

#

So in general the plan is "make it work" and I don't anticipate major technical barriers

vapid forge
silk lava
#

Is it possible to replace the () empty Scene, with Option<T: Scene>?
Then you cant mess up producing an empty scene.

split harness
#

Alrighty I've made some changes that resolve the concerns about stuffing "scene logic" into the component derive / bevy_ecs:

  • SceneConstructor has been renamed to SceneComponent and is now derivable. It lives in bevy_scene. It automatically derives Component internally, and can be configured in all of the same ways.
  • I've refactored the Component derive to be reusable and extendable. See the new bevy_ecs_macro_logic crate.
  • I took the liberty of porting the Resource derive / removing all of the redundant logic, as it uses the exact same pattern.

This notably improves ergonomics even further:

#[derive(SceneComponent, Default, Clone)]
struct Player;

impl Player {
  fn scene() -> impl Scene {
    /* scene here */
  }
}

Configuring the SceneComponent derive now uses the #[scene] attribute:

#[derive(SceneComponent, Default, Clone)]
#[scene(PlayerProps)]
struct Player;

impl Player {
  fn scene(props: PlayerProps) -> impl Scene {
    /* scene here */
  }
}
thick slate
#

(I really like the refactoring though!)

brittle mortar
# split harness Alrighty I've made some changes that resolve the concerns about stuffing "scene ...

Why not just use Player as the props ? It cauld reduce the complexity of bsn and reduce boilerplate code.

before:

#[derive(SceneComponent, Default, Clone)]
#[scene(PlayerProps)]
struct Player {
    value: usize
}

#[derive(Default)]
struct PlayerProps {
    border: bool,
}

world.spawn_scene(bsn! {
   :Player {
       @border: true,
       value: 10,
   } 
});

after:

#[derive(SceneComponent, Default, Clone)]
struct Player {
    value: usize,
    border: bool,
}

world.spawn_scene(bsn! {
   :Player {
       border: true,
       value: 10,
   } 
});
thick slate
#

At least, that's the hope for UI

split harness
#

Additionally, the distinction is important because props are not "patchable" after they are set.

rare whale
frosty shell
#

is there somewhere that documents the syntax of the bsn! macro?

lime veldt
heavy peak
#

The scene component stuff confuses me. Doesn’t’ a scene have a player component? Why does a player have a scene component? Assuming scenes are like levels in a video game

pale edge
heavy peak
#

Ahh, so this is intended to be used inside of a larger scene. That makes sense

pale edge
#

It's also about making a player component spawn automatically with other things it needs

thick slate
slender lion
#

Is RenderTarget usable in bsn at the moment? Seems like an enum without a Default or a FromTemplate

thick slate
frosty shell
split harness
#

@thick slate I've resolved conflicts and pushed docs to Scene Components. Should be good now

split harness
thick slate
# split harness Looking now

Thanks for the review; I'll get on those changes!

One thing though: I wanted to be unusually aggressive on the "explain standard Rust syntax" in these docs. You complained about this in a couple places, but I think it's important here.

If things go according to plan, we should expect users who do not know how to write Rust to author, or at least tweak, BSN files. Glyphs like // and <T> are extremely mysterious for these folks (I was one of them), and quite hard to search (better in the age of the LLM, but not all users will use those).

#

I can de-emphasize them though!

leaden dew
#

covering basic rust syntax i think would go perfectly in like documentation of the asset format in particular

thick slate
#

But I couldn't find a good home for it at present

leaden dew
#

makes sense

thick slate
# split harness Looking now

Feedback resolved! Thanks for teaching me; there were a number of things I was unclear on and I very much feel like a novice when it comes to working with BSN still.

#

The tradeoffs of composition methods in particular were very hard for me to piece together, so I'm glad that we have clear, recorded guidance there

split harness
#

I've been working on cutting down on codegen / debug symbols in BSN:

#

Notice how "adjacent" template patches are all merged into the same Scene impl (the new SceneFunction)

#

Also no more PatchFromTemplate::patch intermediary

lime veldt
#

can't the concat of symbols for generic types be done lazily somehow?

#

so the binary only contains the parts of the names, not a bazillion number of combinations

split harness
split harness
# split harness I've been working on cutting down on codegen / debug symbols in BSN:

Also the source bsn! for reference:

bsn! {
    Node {
        width: percent(100),
        height: percent(100),
        align_items: AlignItems::Start,
        justify_content: JustifyContent::Start,
        display: Display::Flex,
        flex_direction: FlexDirection::Row,
        column_gap: px(8),
    }
    TabGroup
    ThemeBackgroundColor(tokens::WINDOW_BG)
    Children[
        :demo_column_1,
        :demo_column_2,
    ]
}
lime veldt
# split harness I'm not sure I understand

When I look at this: #1264881140007702558 message I notice many many repeating segments. Do these segments appear only once in the binary or is this monstrosity one full single str?

split harness
split harness
lime veldt
#

my suggestion was if we generate these full type names lazily at runtime instead of compiletime

split harness
lime veldt
#

because we use stuff like type_name?

split harness
#

As in using a debugger

#

Put another way: these symbols will not be produced in release builds

lime veldt
#

gotcha

lime veldt
#

I can imagine they help a bit here

#

though that what comes before it, the location of the closure, can still be wordy

split harness
dusk lynx
#

Was there a way to pass #Entity as an argument to a function within bsn! yet?
As in

bsn! {
  #Root
  Children[:myChild(#Root)]
}

fn myChild(root: ???) -> impl Scene {
  bsn! { }
}
tardy beacon
tardy beacon
#

I'm starting to suspect that spawn_scene/spawn_scene_list being generic on Scene plus the function() -> impl Scene idiom might turn out to be a problem. I can see that one instance of spawn_scene_list is ~1KB, although hard to get accurate numbers on the total cost and how it might change with boxing. The real costs won't become apparent until there's an app that's spawning hundreds of different scenes. So I might be wrong, but seems risky.

#

I'm wondering if it would be safer to make bsn! return Box<dyn Scene> and change spawn_scene/etc to take a box? Currently it gets boxed anyway in ScenePatch::load_with, so we're just moving that box earlier.

#

As a bonus, would be a minor boilerplate reduction for cases like this that currently need to box explicitly:

button(ButtonProps {
    caption: Box::new(bsn_list!(
        (Text("Normal") ThemedText),
    )),
    ..default()
})
dusk lynx
tardy beacon
frosty shell
#

~~I got one-shot system templating working: https://github.com/bevyengine/bevy/pull/24072

Should've been half the size but portable-atomic's Arc not having special powers like std::sync::Arc means no-std-portable-atomic wouldn't be able to use one-shot systems (which I believe we only support on a best-effort basis). If the right rust feature gets stabilized though, it can support one-shot systems again~~

Got a better alternative figured out: https://github.com/bevyengine/bevy/pull/24087

GitHub

Objective

Simplified alternative to Arcify one-shot systems to enable SystemId templating #24072

I have a bunch of Bundle-style code that I want to replace with the new bsn! Scene-style macro:
pu...

vapid forge
frosty shell
#

Yea templates being repeatable is the big thing

vapid forge
vapid forge
silk lava
vapid forge
# silk lava Would this run into the same problem that the `_cached` variant of one-shot meth...

hm? i dont think so, i explained it more in the review comment on the PR, but basically: currently, a Arc<Mutex<dyn System>> is being cloned and re-registered when the template is cloned. Instead, if the Arc<Mutex> contains a dyn System or a SystemId, then on the first time this system is registered, it could change the dyn System to the resulting SystemId so that it acts like the case of passing a SystemId to the template from the start.

vapid forge
#

Has anyone been able to use a named entity reference in a impl Scene function argument or in a Scene Component value?

fn test(e: Entity) -> impl Scene {
    bsn! {
        ChildOf(e)
    }
}

fn main() -> impl Scene {
    bsn! {
        #Root
        Children [
            :test(#Root)
        ]
    }
}

:test(#Root) could also be :MySceneComponent(#Root) assuming struct MySceneComponent(Entity)

it seems named entity references can't be passed to scenes at all...

#

no matter what i try, i either end up with syntax errors, or a type EntityTemplate

vapid forge
#
bsn!{
    #Root
    //...
    :pane_header Children [
        :flex_spacer,
        :CloseButton
        CloseButtonTarget(#Root)
    ]
};

This is the closest i was able to get, I'd love to be able to do :CloseButton(#Root)

cobalt stone
#
            Camera3d
            template_value(Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y))
        )

Why is template_value used here?

#

Also I can't nest like this?

error[E0308]: mismatched types
   --> examples\asset\compressed_image_saver.rs:22:42
    |
 22 |                 base_color_texture: Some(asset_value("textures/GroundSand005/GroundSand005_COL_2K.jpg")),
    |                                     ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Handle<Image>`, found `HandleTemplate<_>`
    |                                     |
    |                                     arguments to this enum variant are incorrect
frosty shell
vapid forge
frosty shell
#

I only read your discord comments

#

and then looked at how HandleTemplate is implemented

#

swapped my game to this branch and was able to clean up spawning significantly glajj

rare whale
#

@split harness I'm still having issues trying to pass a #Name as a scene property:

Children [
    // Inner part that scrolls
    (
        #inner
        Node { ... }
        ScrollArea::default()
        Children [
            {children}
        ]
    ),

    // Scrollbar
    :FeathersScrollbar {
        @target: #inner, // ERROR!
        @orientation: {ControlOrientation::Vertical}
    }
    Node { ... }
]

Neither @target: #inner nor @target: {#inner} will compile.

rare whale
thick slate
lime veldt
#

oh so it does compile but RA flags it?

#

reminds me I wanted to report a RA bug too!

split harness
split harness
split harness
cobalt stone
#

It seems like you can't do

let x = MyComponent;

bsn! {
  {x}
}
#

You need to wrap x in template_value, and if you don't you get super confusing errors

split harness
#

Supporting arbitrary expression here is possible. Once again a similar challenge to the "#Name in function argument position" problem above.

It is trying to marry arbitrary Rust expressions with more constrained BSN semantics

queen oak
#

We can do it!

#

We will grind it out next cycle catnod

split harness
#

Yeah. A general class of problem I would like to solve sooner rather than later / I think we can get there shortly

split harness
# cobalt stone What about template_value?

This is the result of a semantics choice rather than a technical limitation.. {x} expects an impl Scene (ex: a bsn! block), but MyComponent is a raw final component value. Scenes are things that are "dependency aware patches that can contribute to a ResolvedScene", which is not what MyComponent is.

template_value(my_component) returns a Scene impl that completely overwrites the template of that type with the given value.

#

We could make bsn! { {my_component} } assume my_component is a "component template value that completely overwrites what is there". But then you couldn't do something like:

let x = bsn!{ Node MyComponent };
bsn! {
  {x}
}
#

Because x is an impl Scene, not a "component template value"

#

If we could impl<C: Component> Scene for C then we could make it behave like template_value implicitly, but I don't think thats possible.

split harness
# split harness Supporting arbitrary expression here is possible. Once again a similar challenge...

The big challenges here are largely:

  1. Treating #Name as what amounts to a "new" Rust value type. We might actually be able to do this in "layers" with some sort of prepass that does the replacement on the raw tokens, rather than trying to write ourselves directly into the Rust expression parse logic.
  2. Supporting "type patching" in the appropriate places while also supporting arbitrary rust expressions. This is the gnarlier problem in my mind. Definitely still a solveable one.
#

(2) requires flipping from "i am patching fields directly on this type" to "I am now assigning a whole overwriting value to this template via a Rust expression" at the appropriate time.

#

We already do (2) in the context of Transform::from_xyz() vs Transform { position: Vec3 { x: 1.0 } }, but it is accomplished by manually parsing "constructor syntax vs field syntax". We aren't parsing these expressions as syn::Expr

#

I think the solution might actually be to parse them as Expr, which we then interpret

#

Although that does limit us as it means we need it to be a fully valid rust expr tree all the way down

vapid forge
#

I did mess around with it a bunch, trying tuple structs or named field structs for the SceneComponent and the props struct, surrounding it by all kinds of brackets/parantheses, etc.

split harness
split harness
#

Gotcha

#

Then what is an example of "non scene component inheritance"?

vapid forge
#

:[a-zA-z_][\({]

#

(if i had to write a regex to find failing cases)

vapid forge
split harness
#

My point of confusion is that any inheritance where you would assign #Name to a field is "scene component inheritance"

split harness
vapid forge
#

maybe i'm misusing the terminology

#

i thought inheritance always needed : before the thing

split harness
#

Those are indeed both inheritance and they are indeed both broken (but for different reasons)

#

SceneComponent inheritance should hopefully be an easy fix

vapid forge
split harness
#

function params slightly harder, especially if we want to support arbitrary #Name expressions

#

But yeah I'll try to get both working as soon as possible 🙂

vapid forge
#

basically take this #1264881140007702558 message and instead of a impl Scene function which takes Entity as an argument, make a struct Foo(Entity) which derives SceneComponent
or make it a unit struct and do FooProps(Entity), neither work

#

as in, neither allow me to pass in a #Name named entity reference as an entity

#

tho for one case, the props one iirc, or maybe the non-: patch syntax, i get a type error instead, due to getting EntityLinked or something (i forgor and am on phone rn)

#

if i wasn't in bed rn i'd go back and write a proper issue, it just seemed so obvious at the time that this would be something you'd wanna be able to do... i honestly didn't consider it

split harness
split harness
#

This is essentially the same problem as wanting to create a new "entity reference" like this;

fn my_scene() -> impl Scene {
  let entity = EntityTemplate::new_ref();
  bsn! {
    SomeComponent(entity)
  }
}
#

If we're willing to embrace a global atomically incrementing counter for scopes (and pay for hashing the scope index rather than a dense array index), then we could make this happen

#

It would unlock a lot of flexibility, but it would also increase the cost of spawning things

#

Hmm I miiiight also be able to move prop evaluation into a Scene impl

split harness
split harness
# split harness If we're willing to embrace a global atomically incrementing counter for scopes ...

Kind of a tough call here, given the performance. Additionally, this would make things like "hot reloading" / reactive diffing harder, as the scope index would change on each evaluation of the scene.

A given EntityTemplate::ScopedEntityIndex generated for a #Name would change from this: { scope: SOME_INDEX_RETURNED_FROM_THE_ALLOCATOR, index: 0 } to this { scope: SOME_OTHER_INDEX_RETURNED_FROM_THE_ALLOCATOR, index: 0 } each time the Scene is re-generated

#

Iirc i_cant_believe_its_not_bsn used a hash of the source file location for this, which would be stable in those contexts. But that is also massively more expensive to compute

#

Although I guess in theory we could do the hashing at compile time

let x: &'static str = const { file!() };

I don't love that either from a compile perf perspective, but preferable to doing at runtime

rare whale
#

@split harness I want to also have you think about the other issue that is blocking me, which is assigning handles to fields in inner structs, which I mentioned a couple days ago:

bsn! {
    Node {}
    Button
    Hovered
    EntityCursor::System(SystemCursorIcon::Pointer)
    StateSource::SelfState
    VisualStates [
        StyleRule {
            pattern: StatePattern {
                mask: WidgetState::DISABLED,
                required: WidgetState::DISABLED,
            },
            background: {ImageNodeTemplate {
                color: Color::WHITE,
                // image: ImageTemplate::Handle(""), // NOPE!
                ..Default::default()
            }},
        },
        StyleRule { ...  },
    ]

Here we have a StyleRule component which internally keeps an ImageNode containing a handle. I can assign the handle to fields of StyleRule but not to fields of templated structs within StyleRule.

#

Note that in this case, ImageNode is a component but we're not using it as a component because we want it to remain hidden until the state pattern matches (at which point it will be cloned and inserted into the widget, overwriting the previous image node).

#

I could solve this by hoisting all of the properties of ImageNode into StyleRule and then copying property-by-property but I'd prefer not to do that.

split harness
# rare whale <@153249376947535872> I want to also have you think about the other issue that i...

I would like to (once again 😃 ) reiterate that nesting templates inside of each other is supported and functional.

I see a number of issues with your code. First (and I think this is the core issue): you're doing an explicit Rust expression block when you don't need to / it is making things harder on you. This will compile:

#[derive(Component, FromTemplate)]
struct StyleRule {
    background: ImageNode,
}

fn scene() -> impl Scene {
    bsn! {
        StyleRule {
            background: ImageNode {
                image: "icon.png"
            }
        }
    }
}

Nested templates can/should be "patched" just like everything else, unless you really want / need to do explicit Rust expressions.

If you do want to do an explicit Rust expression for the ImageNodeTemplate, the one you wrote seems wrong:
(1) ImageTemplate isn't a type. You probably want HandleTemplate
(2) HandleTemplate::Handle("") doesn't make sense, as the ::Handle variant expects a Handle. you want the Path variant.

This compiles (although you should prefer the "patch" approach above):

fn scene() -> impl Scene {
    bsn! {
        StyleRule {
            background: {ImageNodeTemplate {
                image: HandleTemplate::Path("icon.png".into()),
                ..Default::default()
            }}
        }
    }
}
rare whale
rare whale
#

E.g.:

#[derive(Component, Clone, Default, FromTemplate)]
pub struct StyleRule {
    pub pattern: StatePattern,
    #[template(OptionTemplate<ImageNodeTemplate>)]
    pub background: Option<ImageNode>, // None = render nothing for this state
}
split harness
# rare whale How would it look if `background` was `Option<ImageNode>`? Like say I want to ha...

This bit is where things start to get "interesting". This is probably the most ergonomic form at the moment:

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

fn scene1() -> impl Scene {
    bsn! {
        StyleRule {
            background: {ImageNodeTemplate {
                image: "icon.png".into(),
                ..default()
            }}
        }
    }
}

I was a bit surprised when this didn't work:

bsn! {
    StyleRule {
        background: OptionTemplate::Some(ImageNodeTemplate {
            image: "icon.png"
        })
    }
}

It generates the patch, but it doesn't do implicit defaults there, so it requires specifying every field on ImageNode. This feels overly strict / I think we can make that one work

#

I believe the current behavior is an over-extension of the "nested enums require specifying all fields" behavior

split harness
split harness
split harness
vapid forge
#

i should probably write a few tests at this point

split harness
# vapid forge oh, to be clear, i also tried with named fields and #Name also didn't wanna work

Named fields work for me. This test runs / passes:

    #[test]
    fn scene_component_name_reference() {
        #[derive(SceneComponent, FromTemplate)]
        struct Widget {
            entity: Entity,
        }

        impl Widget {
            fn scene() -> impl Scene {
                bsn! {}
            }
        }

        let scene = bsn! {
          #Name
          Children [
              :Widget { entity: #Name }
          ]
        };

        let mut app = test_app();
        let world = app.world_mut();
        let entity = world.spawn_scene(scene).unwrap().id();
        let root = world.entity(entity);
        let children = root.get::<Children>().unwrap();
        let child_widget = world.entity(children[0]).get::<Widget>().unwrap();
        assert_eq!(child_widget.entity, entity);
    }
vapid forge
vapid forge
# split harness function params slightly harder, especially if we want to support arbitrary `#Na...

I wrote some tests for this:

  • scene function (-> impl Scene) #Name entity reference
  • named struct scene component #Name entity reference (additionally to tuple struct)
  • scene component props #Name entity reference

https://github.com/bevyengine/bevy/compare/main...laundmo:bevy:bsn-failing-syntax-tests
I'm not sure if this could be a PR since these tests lead to compile/macro errors, but i'm also not testing to ensure they continue to lead to errors, instead using tests to showcase where syntax could be improved...

vapid forge
#

Okay, i've looked at the macros and their output quite a bit, and i can understand why this is so difficult.

The call to SceneComponent::scene() or scenefn() happens directly while the macro is being called, inside a block.

{
    (
    scene_function(), // where we wold like to pass Entity
    Foo::patch(move |value, _context| {
     // even this isn't actually an Entity, its EntityTemplate
      value.entity = EntityTemplate::ScopedEntityIndex(
          ScopedEntityIndex {
              scope: _context.current_entity_scope(),
              index: 0usize,
          },
      );
  }),
  )
}

This means all it does is add a template patch to be called with context later to the list of scene items. The way EntityTemplate handles this is by resolving during the time templates are applied.

I think a "solution" to this might actually be a compromise between making it as easy to use as it could be, somehow getting the #Name resolved to Entity before the scene call, and the current stat of it being impossible: By providing tools for impl Scene functions to take not the Entity itself, but something that can expand to it later. The main difficutly here, i would think, is that whichever this new NamedEntity (just my idea for a name) or whatever is, it would have to somehow store the context of the outer scene.

rare whale
vapid forge
#

the issue really is how the implementation would look, because of the way the scoping works out

#

There needs to be some globally unique identifier for scopes i'm afraid, to be able to pass the outer scope into a different SceneScope

vapid forge
#

I am SO CLOSE to getting it working. The only issue is i had to no-op the register_dependencies which i'm sure is not good

#

Like, it actually works!

but i think it works in a way such that assets will not be registered, which is ugh

#

but i somehow got it to pass every test in bevy_scene/src/lib.rs even the ones i added

vapid forge
#

wait i forgot one i commented

#

ughhh it would be so much better if there could be global state during macros. Just increment a counter for each unique #Name + Scope and be done with it

split harness
vapid forge
#

at this point

#

i also got #name as props working

#

its hacky tho

#

i put the chance it will be merged like this at a solid 0.1%

split harness
#

The only issue is i had to no-op the register_dependencies which i'm sure is not good
Yeah this is very unlikely to fly

vapid forge
#

mhmm

split harness
#

By providing tools for impl Scene functions to take not the Entity itself, but something that can expand to it later.
This is essentially what EntityTemplate::ScopedEntityIndex is

vapid forge
#

yup

#

basically, my solution looks like making the macro pass EntityTemplate when #name is referenced

#

the issue is: i need the current scope, no the inner one

#

so i had to hack my way around somehow getting the scope index before the scene function is called

split harness
#

Thanks for investigating ❤️

vapid forge
#

yeah, it seems so close to possible

#

its... annoying

split harness
#

I'm off BSN until I can wrap up the Bevy Foundation's annual report to the IRS, so its great that you've picked up the mantle

vapid forge
#

if i could call register_dependencies from within resolve it'd work

#

but i'm not sure what the implications of that would be

split harness
vapid forge
#

i might investigate if theres a way to have global state in the macro, but i suspect the outcome of that will be "fuck no"

#

could generate a UUID but thats also a relatively large amount of extra work

split harness
vapid forge
#

yeah i'm afraid not, thats the first thing i thought of too

#

but perhaps a compile time uuid isn't the worst idea

split harness
#

Also anything involving randomness will cause problems for reactive diffing / hot reloading scenarios:
#1264881140007702558 message

vapid forge
#

mhmmm

split harness
#

Const-hashing file/line location is an option: #1264881140007702558 message

vapid forge
#

tho with the way i'm thinking it wouldn't persist after the scene is spawned. not sure how that would cause issues?

#

not spawned

#

resolved

split harness
#

(which is really the only thing you could diff)

vapid forge
#

yea, hence, random in that place would just barely be fine as its gone one step before it becomes an issue

split harness
#

Yeah I think thats probably our best path forward

vapid forge
#

tho i actually dont know whether hashing file loc or generating a uuid would be cheaper

#

i mean, once the system is based on a global id instead of local, its easy to test either

split harness
#

most file names 🙂

vapid forge
#

i dont think the size matters that much

#

its more about how fast the hashing is vs how slow the uuid (or something with an equivalent level of randomisation) is to generate

#

hell: a millisecond precision timestamp + 32 random bits is gonna be unique enough to never collide

split harness
#

Yeah the real answer is do both and measure

vapid forge
#

the real answer is: first, i gotta work on actually seeing how implementing it like this would work out. I suspect it would simplify a lot of the current hassle done for scoping, but then again, i'll probably break 15 things in the meantime

split harness
#

Although we can cross that bridge when we come to it / better to do the simple + direct swap first

vapid forge
#

hey uhh so @split harness how is your merged scene implementations PR https://github.com/bevyengine/bevy/pull/24124 any less breaking for register_dependencies than my attempt at resolving this? The questionable part, the Scene impl for a arbitrary function, looks exactly the same...

#

compare:

pub struct SceneScopeFn<F, S>(pub F)
where
    F: Fn(&mut ResolveContext, &mut ResolvedScene) -> S,
    S: Scene;
impl<F, S> Scene for SceneScopeFn<F, S>
where
    F: Fn(&mut ResolveContext, &mut ResolvedScene) -> S + Send + Sync + 'static,
    S: Scene,
{
    fn resolve(
        self,
        context: &mut ResolveContext,
        scene: &mut ResolvedScene,
    ) -> Result<(), ResolveSceneError> {
        let inner_scene = self.0(context, scene);
        let scope = SceneScope(inner_scene);
        scope.resolve(context, scene)
    }

    fn register_dependencies(&self, dependencies: &mut SceneDependencies) {
        // uh oh, this *cant* be a no-op...
    }
}

vs

/// A [`Scene`] that uses a function `F` to perform arbitrary [`Scene`] logic.
pub struct SceneFunction<F: FnOnce(&mut ResolveContext, &mut ResolvedScene)>(pub F);

impl<F: FnOnce(&mut ResolveContext, &mut ResolvedScene) + Send + Sync + 'static> Scene
    for SceneFunction<F>
{
    fn resolve(
        self,
        context: &mut ResolveContext,
        scene: &mut ResolvedScene,
    ) -> Result<(), ResolveSceneError> {
        (self.0)(context, scene);
        Ok(())
    }
}
split harness
#

That is the difference between EntryResult::SceneImpl and EntryResult::CombinedSceneFunction

#

SceneImpl is for any BSN syntax that could generate a dependency

vapid forge
#

huh, yeah, i got confused by SceneExpression(block) for the like, 8th time today...

#

wait, but FromTemplatePatch?

split harness
#

Thats still just setting fields on a Template of a given type in the ResolvedScene. It will never introduce new dependencies

vapid forge
#

okay, i tested what i was thinking, yup

vapid forge
#

I didn't realize CI runs for Draft PRs as well...

vapid forge
#

got the @props/impl scene function passing working as well!

#

well, as EntityTemplate not as Entity, but thats still really good i'd say.

rare whale
#

@vapid forge If you take a look at my listbox PR (https://github.com/bevyengine/bevy/pull/24092), there's a place in the code that's currently commented out: (in listview.rs):

:FeathersScrollbar {
    // @target: #inner,
    @orientation: {ControlOrientation::Vertical}
}

The intent here is to tell the scrollbar which entity is being scrolled by it. This entity id is passed through to the headless Scrollbar component, which adjusts the scroll position when the scrollbar is dragged, and conversely, updates the scrollbar when the area is scrolled with the mouse wheel.

#

Currently the scrollbar is visible in the feathers gallery demo, but does nothing since the scrollbar isn't connected to a target entity.

vapid forge
rare whale
vapid forge
#

its not even messy, its just the type thats different, other than that, its the same

rare whale
#

The FeathersScrollbar scene currently looks like this:

impl FeathersScrollbar {
    /// Scene function for scrollbar.
    pub fn scene(props: FeathersScrollbarProps) -> impl Scene {
        bsn! {
            Scrollbar {
                target: {props.target},
                orientation: {props.orientation},
                min_thumb_length: 8.0
            }
            Node {
                border_radius: BorderRadius::all(px(3))
            }
            FeathersScrollbar
            ThemeBackgroundColor(tokens::SCROLLBAR_BG)
            Children [(
                Node {
                    position_type: PositionType::Absolute,
                    border_radius: BorderRadius::all(px(3))
                }
                Hovered
                ThemeBackgroundColor(tokens::SCROLLBAR_THUMB)
                ScrollbarThumb
                ScrollbarThumbStyle
                EntityCursor::System(bevy_window::SystemCursorIcon::Pointer)
            )]
        }
    }
}
vapid forge
# rare whale I'm ok if the syntax is a bit messy so long as it's possible

from the tests i added in the second PR:

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

#[derive(SceneComponent, Clone, Default)]
#[scene(WidgetProps)]
struct Widget;

#[derive(Default)]
struct WidgetProps {
    entity: EntityTemplate,
}

impl Widget {
    fn scene(props: WidgetProps) -> impl Scene {
        bsn! {
            Reference(#{props.entity})
        }
    }
}

let scene_prop = bsn! {
    #Name
    Children [
        :Widget {
            @entity: #Name
        }
    ]
};
vapid forge
vapid forge
#

tho at this point i might be just a few days too late

#

which is honestly quite annoying

rare whale
slender lion
#

Is it correct that the asset_value isn't recursive right now, so can't be used for a MeshMaterial3d::<StandardMaterial> with textures?

#

right now I'm covering for that by using template and accessing the asset server to load the textures, so its not so bad

split harness
rare whale
#

@split harness I had a random thought this morning about using BSN scenes to do dynamic patching for state transitions. Right now in my BYOA widget prototype, I have a list of pattern that match various widget states, and associated with each pattern is a set of mutations. Currently the only mutation supported is changing the ImageNode but one could easily imagine wanting to change other component properties such as font color, outline, and so on.

Rather than trying to invent some Rust struct that encodes all of these different and optional mutations, I was wondering if this couldn't just be a patch. However, this raises a number of challenges:

  • Currently the top-level API for invoking a scene (queue_spawn_scene) spawns a new entity, rather than applying the changes to an existing entity.
  • BSN currently does not define a syntax for removing a component. I can imagine something like Remove::<Outline>, when present in the scene description, would cause the named component to be removed.
  • The biggest issue is that the patches need to remain unevaluated until it is time to apply them. This means that if you are loading the widget as an asset, the partial scenes enclosed in the state table are scenes within scenes, such that the outer scene is actually instantiated, but the inner scenes remain as unapplied patches.

I don't know whether this idea is worth pursuing, but I thought I'd put it out there and see what folks think.

#

Although, I suppose that all of this is really just a workaround for the lack of reactivity. On the third hand, even if we had reactivity, we don't currently have a plan for representing reaction code within assets, so a purely data-driven approach is still needed.

split harness
# rare whale <@153249376947535872> I had a random thought this morning about using BSN scenes...

We do have entity_mut.apply_scene, which applies the scene to an existing entity. I'm not immediately opposed to having removal syntax. I suspect that someone will eventually want to inherit from a scene, but remove a component from it. However semantically "removing a component template from a ResolvedScene" is different from "queuing a remove of a component on an already spawned entity".

I suspect that trying to treat BSN as "arbitrary transforms on spawned entities" rather than "a specification of what to add" would eventually result in mismatched requirements, confusing implementations, and (perhaps ultimately) frustrated users.

That being said, there is nothing stopping people from creating a "remove component" bundle template

native mesa
#

I'm not sure that the meaning of @ within bsn templates is documented anywhere

#

is this intentional?

slender lion
split harness
slender lion
native mesa
native mesa
#

how would you feel about deduplicating the docs on the macro and redirecting people more towards the crate doc?

#

there's a lot of surface area here to keep in sync

split harness
#

The docs I've written have been in the crate-level spot. We should sync with @thick slate on the desired end state

#

Avoiding duplication is a good idea

native mesa
#

slight tangent, but been using bsn for a fun rustweek adjacent project. after intentionally not following development all that closely, i've been pretty happy with the docs and found it pretty easy to pick up.

a few little nits from that testing are: it's quite hard to conditionally add components, reactivity is still hard and i find myself constantly reaching for it now, we probably need to make the bsn/bsn_list and Scene/SceneList stuff a bit more clear, especially as it relates to inclusions {...} (the docs are all there, just a bit scattered).

thick slate
split harness
# native mesa slight tangent, but been using bsn for a fun rustweek adjacent project. after in...

Glad to hear it!

it's quite hard to conditionally add components,
Yup this definitely needs to be easier / lower boilerplate. We'll want first-class / lower boilerplate support for both conditionals and loops. I believe this is close to the "best" you can do in userspace atm:

let thing: Box<dyn Scene> =
   if condition {
      Box::new(bsn! {})
  } else {
      Box::new(())
  };
bsn! {
  {thing}
}

reactivity is still hard and i find myself constantly reaching for it now
Unsurprising. It would be nice to sort an official answer for this in the 0.20 cycle.

we probably need to make the bsn/bsn_list and Scene/SceneList stuff a bit more clear, especially as it relates to inclusions {...} (the docs are all there, just a bit scattered).
Very open to suggestions here!

native mesa
#

i'll try to get up a PR with some small docs nits

split harness
#

Option<T> syntax makes it harder to support patches I think

#

"conditional patches"

native mesa
split harness
#

The main reason I haven't done that yet (its pretty easy) is that we might want to reserve that syntax for reactivity

native mesa
#

cool, that makes sense to me. as long as it's being tracked im happy.

thick slate
split harness
rare whale
#

The approach I have been taking is to implement the reactive primitives at the component level, not deeply integrated into bsn syntax but embeddable within it. This avoids the need for BSN to commit to any particular reactive strategy, but is not as straightforwardly ergonomic.

#

For integrated syntax, you'd need to accommodate the fact that there are multiple flavors of conditional primitives depending on context: an "if" statement whose output is an entity or entity tree is different from an "if" whose output is a component. And that's not even including other conditional patterns such as switch, foreach, and so on.

split harness
thick slate
raw mesa
silk lava
raw mesa
#

I'm putting this discussion in a thread #1504173980532346910 message

south lagoon
#

under which example subfolder do I find the bsn examples? Thanks

somber rivet
#

Is there some way to force load a set of scenes in advance so that I can just safely use them syncronously through the rest of the project without thinking about it?

raw mesa
#

@split harness just to clarify, what's the behavior of asset handles in scenes? Does spawn_scene fail if any dependencies are unloaded? Or only if inherited scenes are unloaded?

split harness
#

The lingo for Scene::register_dependencies is "scene dependencies"

raw mesa
#

Cool that was what I thought, thanks!

split harness
#

Ultimately the plan is to support opting-in asset handles to be "scene dependencies"

#

on a case-by-case-basis

raw mesa
#

Right right. And then I think previously I've said we should make that opt out instead haha. I'm remembering now

raw mesa
#

Unless jackdaw has some scene loading thing

somber rivet
#

I should probably have said "is this in the plans" and it sounds like it is

#

Thanks!

raw mesa
# somber rivet I should probably have said "is this in the plans" and it sounds like it is

I don't think we're gonna have anything for "block until this loads", but we definitely want a loading screen API, so you can just say "here's a scene, stay in the loading state until it's loaded". Here's my proposal for one https://github.com/bevyengine/bevy/compare/main...andriyDev:bevy:loading-screen

But I'm waiting for assets-as-entities v0 to push it upstream.

GitHub

A refreshingly simple data-driven game engine built in Rust - Comparing bevyengine:main...andriyDev:loading-screen · bevyengine/bevy

somber rivet
#

Yeah I guess I'm thinking of something like, for projects that have a relatively small number of scenes (say, < 200) you can just force them to all load on start, or maybe a way to "stream" them, so you can load all the assets for the main menu, then display the main menu and while the player is messing around on that you can load your game assets in the background... something like that. I just genuinely do not want to have to reason about whether my fireball needs to be spawned asyncronously in game code, at least not while I'm prototyping

#

I guess in theory you prototype using the bsn! macro, not .bsn... idk

raw mesa
somber rivet
#

Great, I'm glad it's on peoples minds. I see the feedback people have about the async vs sync loading mental model introducing complexity, but that basically resolves it for me personally.

native mesa
raw mesa
#

Let's move further discussion of this to assets-dev #assets-dev message

#

I don't wanna jam up this working group anymore lol

silk lava
split harness
native mesa
split harness
silk lava
split harness
#

There are two implementation paths to consider:

  1. Expressing these dependencies via "bsn syntax", which becomes Scene impls that register the dependencies in Scene::register_dependencies. This would cause spawn_scene to fail in the same way as "scene asset inheritance"
  2. Expressing these dependencies by adding a "template data api", which allows Templates to register arbitrary data (ex: "scene asset dependencies"). This would be evaluated after the scene is fully resolved (aka it wouldn't need to directly block spawn_scene, but it might warn instead).
#

(2) would take this into account separately from inheritance dependencies as part of the "queued spawn system". Ex: it would resolve the scene, then wait for asset dependencies to load

silk lava
split harness
#

(2) is actually my current plan / the frontrunner in my mind, but its all tbd / we can discuss it

#

There is always the option to try to encode this statically (ex: a NoDependencies trait, which we constrain spawn_scene to). Main reason not to is compile times / complexity (as we need NoDependencies to align with the Scene::register_dependencies behavior)

#

We can't do the reverse / tie it to dependency registration directly, as !Dependencies isn't supported in stable rust

silk lava
#

Stupid question, but its not possible to evaluate scene asset dependencies after the immediate parts, I suppose 😛

raw mesa
#

IOW, no 😛

silk lava
#

A.k.A (2) from above, but also for Scene Assets that are inherited.

raw mesa
#

ahhhh got it

#

Yeah I would say no lol. That's also not really a desirable state, since now you're taking one action and having it applied "twice". That wouldn't be very obvious to a user that it's because the inheritance happens later

#

Like you'd be able to see a partially spawned scene which is... weird

silk lava
raw mesa
#

And then inheritance is just an ugly part we gotta live with lol

silk lava
#

This has been a good convo though, I will digress from my side as Im unsure if I have a valuable argument at this point. 🩵

raw mesa
#

I can understand the motivation behind your stance! I am just traumatized(or annoyed?) by users always showing up to the Bevy discord and needing to be told about SceneInstanceReady because the old scenes were so unintuitive

split harness
vapid forge
#

i like the new naming scheme

#

fyi: i won't be able to push commits to this for probably 18h

split harness
vapid forge
#

i'll mention it here as well: the merge was blocked by spuriously failing CI

raw mesa
#

Is there a way to add a pre-existing component to a bsn! ?

I'm trying to do something like:

let my_transform: Transform = compute_my_transform_somehow();
bsn!(
  {my_transform}
  SomeOtherComponent
)

But it looks like this isn't supported??

#

It seems this:

bsn!(
  {template_value(my_transform)}
  SomeOtherComponent
)

works?

split harness
#

bsn! { {my_transform} } expects something that impls Scene

#

And components do not impl Scene on their own

#

You can omit the {} around template_value

split harness
#

We could reverse it and make passing in scenes harder

#

Open to suggestions

cobalt stone
#

¯_(ツ)_/¯

#

Could you have {foo} turn into {foo.to_scene()} where ToScene gets implemented for Scene and Bundle?

raw mesa
vapid forge
vapid forge
#

WTF?
rust-analyzer resolves pub use bevy_scene_macros::bsn; in bevy_scene as the private module bsn and not the pub fn bsn proc-macro???

#

but only if i don't use a wildcard import...

#

(this is latest pre-release R-A btw)

#

so cursed

#

okay so its only an issue at that location, other places then using this re-exported bsn dont have this issue

raw mesa
vapid forge
raw mesa
vapid forge
#

it does to me, before going that route i'd made very sure we cant make component-returning expressions work in the current syntax

raw mesa
#

My reasoning is that we have one inheritance and N components, so components dominate and we should prioritize them

#

Also I think currently to apply a scene you need to put them in braces anyway? I might be wrong on that though

vapid forge
#

well, we also have N patches

lime veldt
#

I think alloc does that too with format

vapid forge
lime veldt
#

right

raw mesa
#

Anyway I've said my piece (peace?), so whatever we decide seems ok

#

Oh I guess one more thing is just that template_value is a very confusing solution here. Maybe we should have a specialized version like component()?

vapid forge
rare whale
vapid forge
#

I'm working on rewriting some bsn docs right now:

What do we call the collective kind of thing which can be part of an entity in bsn syntax? i'm talking about a single term for: scene inheritance, scene composition, templates (component patches), observers, etc.

The macro parsing calls it BsnEntry but entry/entries doesn't sound like a good term for user-facing documentation.

  • Patch(es) - doesn't really fit scenes
  • Templat(es) - doesn't really fit scenes
  • Component(s) - doesn't really fit scenes or observers
thick slate
#

Scene element?

vapid forge
vapid forge
# rare whale part?

hmm, i like how short it is, i might go with this for now since unlike element it might not be confused with html(-like) elements

silk lava
#

Isn't Patch the term? Scenes are Patches, they Patch an Single Entity.

vapid forge
silk lava
#

I dont think patching should be included in those 3.
You patch things on a single entity, using inheritence and composition.

vapid forge
#

well, whats Node { width: px(10.) } then?

silk lava
#

A composition of a Node Template.

vapid forge
#

thats not really how the existing docs, which are mostly copied from carts PR description, talk about it

#

it talks about composition purely in the context of scenes

silk lava
#

Never found cart to be the best at docs, to be honest. Alice covers that role pretty well though.
Composition in the context of scenes is correct when describing the semantics available to the patching-syntax.

vapid forge
#

(plus, have some slight factual errors and other issues)

silk lava
vapid forge
#

sure, but that to me just means it was too early to merge docs

#

anyways, i dont think "patch" works as a term to also encompass the inheritance syntax

#

i'm basically (kinda) writing a formal grammar

#

so
patch = scene_inheritance | scene_composition | component_patch
to me just.. doesn't fit

silk lava
vapid forge
#

-# I'm kinda torn between actually writing a grammar and just using grammar-like notation as part of the docs

silk lava
#

||I'll be honest, im still partially confused between Scene and Template|| 😅

vapid forge
silk lava
#

Right, Template -> Component and Scene -> Bundle. remember remember remember 😵‍💫

vapid forge
raw mesa
split harness
split harness
#

The things in scenes are "component templates"

crisp garden
split harness
#

Hehe I think docs are great and critical to building good software experiences. I just like writing the software more

#

Very open to discussing how to make the framing of components / templates / scenes "easier". Sadly each of these pieces are "functionally necessary", so it really comes down to naming and storytelling. I think the names for the concepts themselves are pretty close to as good as we can get. On the "storytelling" front, we could consider sweeping Template under the rug a bit as an implementation detail, and generally still talk about these things as "components"

topaz ginkgo
#

Hey @split harness sorry for the ping, but in jackdaw we’ve been working on the concept of prefabs now. I was wondering if you had any thoughts on how this might work in a bsn-based world? I believe in Flecs for instance prefabs are kind of baked into the ECS and first-class citizens there. Our implementation in jackdaw will effectively be Relationship based (via IsA) and an additional Prefab Component to be added as a marker, with individual field-level overrides supported via AST-based modification

thick slate
split harness
split harness
# topaz ginkgo Hey <@153249376947535872> sorry for the ping, but in jackdaw we’ve been working ...

Flecs does an "entity inheritance" thing, which spawns the prefab as an entity (which isn't an instance on its own), and then "instance" entities can be spawned with a relationship, which then "inherit" from that base entity tree (ex: shared components, shared children, etc).

Bevy doesn't currently support that, and I'm not yet convinced it is the right path forward. Doing inheritance like that makes mutations a lot more complicated, especially in a parallel context, and it introduces a fixed indirection overhead when reading components.

I'm not against exploring that avenue, but letting each instance be a "full" owned instance feels like the right starting point, from my prespective.

split harness
# topaz ginkgo Hey <@153249376947535872> sorry for the ping, but in jackdaw we’ve been working ...

This is an "ECS-feature research area". @tawdry owl has explored it here: https://github.com/bevyengine/bevy/pull/18767

GitHub

Objective
This PR adds an implementation of inherited components and entity prefabs. This is useful for creating dynamic entity templates and allows component sharing, which reduces memory pressure...

split harness
split harness
tawdry owl
split harness
#

(although actually I think that was in the context of insert operations)

tawdry owl
split harness
rare whale
#

@split harness @vapid forge I finished integrating the EntityTemplate into the feathers listbox widget and it's working great:

split harness
#

Love to hear it 🙂

rare whale
tawdry owl
rare whale
#

It was quite easy to use, here's a sample:

Children [
    // Inner part that scrolls
    (
        #inner
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            align_items: AlignItems::Stretch,
            justify_content: JustifyContent::Start,
            overflow: Overflow::scroll_y(),
        }
        ScrollArea
        Children [
            {props.rows}
        ]
    ),

    :FeathersScrollbar {
        @target: #inner,
        @orientation: {ControlOrientation::Vertical}
    }

The #inner entity id is used to tell the scrollbar which entity is being scrolled

#

The target field of FeathersScrollbarProps is an EntityTemplate.

vapid forge
#

Love to hear it, this is exactly the kind of usage i was imagineing for this!

Well, my own was a bit simpler: i initially had the desire for sth like this because i wanted a "close" button which despawns the correct entity: :closebtn(#thing_to_close)

karmic rock
#

This is also reflected in the get APIs: the regular get returns a const ref and works for owned and inherited components. Get_mut only works for owned components

karmic rock
karmic rock
#

One advantage of pulling it into the ECS is that you can optimize the storage for fast cloning of component values & hierarchies

vapid forge
#

I'm working on updating bsn/scene docs, and stumbled across this:

Scene Inheritance

There are two ways to build on an existing scene[...]
Both let you patch fields on top of the parent, and both merge children from parent and child (parent's children appear first). They differ in when the parent is resolved and what kinds of parents they support.

I'm really unsure how this was originally meant. I think part of the confusion for me is that its talking about both inheritance parent/children and hierarchy parent/children at the same time perhaps?

#

If anyone has any idea how to untangle this, i'm all ears. Issue is i don't know if i'm missing some implication here or if this is just confusingly worded

thick slate
#

And "what does pre-resolved mean".

vapid forge
#

pre-resolved: Scenes have a .resolve method, which turns them into a ResolvedScene. I think thats what pre-resolved would mean in this context

thick slate
#

This wasn't very clear from the PR description and things kept changing relative to the prior design docs here, so I ended up trying to reverse engineer the behavior from the tests

thick slate
#

The merging is about "how does this work if there's already matching children / components that could be overwritten"

#

Or at least, that was my understanding, which could very well be wrong

vapid forge
#

welp, more tests to write

thick slate
#

I would 100% just delete this particular paragraph, and try and answer the questions about the difference in behaviors and limitations of the two inheritance mechanisms directly

#

This was one of the most challenging and confusing bits for me: hence the table trying to compare and contrast them. I went through a ton of iteration on that, but I'm still not confident that it was right

#

And I should have just spent another couple of days writing tests lol

vapid forge
#

At this point i can't call this a docs PR anymore lol

#

the diff shows its about half docs changes half tests by line counts

thick slate
vapid forge
#

mhmm, including fun ones such as "lets just test every single primitive type with a few differnt literals each"

thick slate
#

Ooh, very nice

#

Macros are such a pain lol

vapid forge
#

dunno, this doesn't look nice to me

#

plus bonus "rust-analyzer doesn't understand the macro" error

#

yes, it compiles and tests fine

thick slate
#

Make sure we have tests for move closures in on observers? I remember idly thinking "oh hey I bet this is a nasty edge case, especially for macro parsing"

vapid forge
thick slate
#

Oh good! I did remember to add that lol

#

Thank you very much BTW; I know that the existing docs are both pretty rough

vapid forge
#

i did add one for normal function observers

thick slate
#

Maybe we should have one for bundling in global observers too?

#

That should just straight up work, even though it's a bit weird

vapid forge
#

also, one for this... thing

fn unit(is_boss: bool, level: u32) -> impl Scene {
  let scene: Box<dyn Scene> = if is_boss {
      Box::new(bsn! {
          Boss
          Children [ :unit(false, level - 1) #Grunt1, :unit(false, level - 1) #Grunt2]
      })
  } else {
      Box::new(bsn! { Grunt })
  };
  bsn! {
      Level(level)
      {scene}
  }
}
thick slate
#

The app.add_observer stuff is just sugar IIRC

vapid forge
#

ugh, but do we want to encourage that pattern?

thick slate
#

Yeah, valid 🤔 We do kind of bless that behavior if we add a test for it

vapid forge
#

I know for sure Talin isnt' aware of that option, otherwise feathers would have a few less plugins floating around

#

plus: what happens if you spawn twice?

thick slate
vapid forge
#

i'm pretty sure right now it would duplicate the global observer

thick slate
thick slate
vapid forge
#

i think thats something to carefully talk over, seeing how much of a pain SystemId templates was/is

thick slate
#

Yeah, agreed

#

I think we should skip for now, and then consider properly once systems-as-entities is being designed?

vapid forge
#

sure

#

i already feel like bsn suffers from too many ways to achieve the same goal

#

no need to add something like observers into the mix right now

thick slate
#

And it's extra hard because I think that much of the seeming duplication is required for obscure .bsn reasons, but we don't actually have that merged yet.

vapid forge
#

mhmm

#

i'll just say: i'm very glad to be on ADHD meds, i would not have been able to do this before

lime veldt
thick slate
# vapid forge i'll just say: i'm very glad to be on ADHD meds, i would not have been able to d...

Yeah, I had exactly the same sort of feeling about the release note revisions this cycle TBH. I found it way less exhausting to do them this cycle because they were just adjacent files in the normal repo and I could just bang them out one at a time.

Clearly delineating stuff like "adding links and images" as out of scope for that pass was super useful to, to prevent me from feeling paralyzed by dread about the tedious tasks that I needed to finish before I could go back to the fun bit of wordsmithing.

But also maybe I've just like... managed my imposter syndrome and anxiety better and was able to get into a groove?

vapid forge
#

uhm. I think there is no difference between :scene() and scene()

#

wrote a test which spawns 2 entities, one using inheritance first and composing the second scene func, and one using purely "composition". Both have children.

Archetypes? The same. Children names? The same. Assets<ScenePatch>? empty.

thick slate
#

Doesn't the : force it to be pre-resolved a single time then cached?

vapid forge
#

Where would it be cached?

#

that'd be Assets<ScenePatch>

thick slate
#

Ah, right 🤔

vapid forge
#

which is, i might note, empty after the spawns

thick slate
vapid forge
#

added another check: component values also end up the same (thought so, but good to check)

thick slate
thick slate
#

Yeah, just copy-paste the contents of fn scene in

vapid forge
#

oh you mean literally just inlining its bsn! contents?

thick slate
#

Yeah

#

Normally I would say that this is paranoid as heck but consider: macros

vapid forge
#

guess what i was doing just now

thick slate
#

So smart!

vapid forge
#
let scene_c = bsn! {
    unit_with_armor()
    Armor(10)
    Children [
        #Second
    ]
};
#

vs

let scene_b = bsn! {
    unit_with_armor()
    armor()
};
#

vs

let scene_a = bsn! {
    :unit_with_armor()
    armor()
};
#

all of them end up being, as far as i can tell, the exact same thing

#

well

#

the main difference is that if you inlinhe it you keep the same scope

#

but thats it

#

and that only matters to named entity references

#

i'm... going to have a word with cart when i get the chance about what the hecc the point is

thick slate
vapid forge
#

well

#

the original PR description said it would only be implemented for assets, but who knows whats changed since then

#

well, i do: :func("arg") wasn't originally planned to be allowed. only argument-less functions, at which point caching makes sense

#

Okay so i did the following:

  • inlined the macros and scene functions recursively
  • fixed all clippy lints (unneccessary type path)
  • copied the resulting blocks to their own files
  • removed all indentation
  • did a diff beteween them
#

therse one (1) difference: The left one has 2 SceneScopes nested where the right one has 1

#

otherwise theyre exactly the same

thick slate
vapid forge
#

the one where i manually inlined the scene entries (components etc) has a slight bit more difference, but negligible

#

yea no after cleaning up some more formatting differences, the logic is exactly the same between all 3

#

welp

#

this has put a... dampener on my docs writing till cart comes back

vapid forge
# vapid forge

@split harness can we like, talk about this? Could you ping me or even call for a second (just to alert me) if you see this and this timestamp hasn't passed <t:1779235200:R>

#

i mostly wanna ask a bunch of docs questions

thick slate
raw mesa
thick slate
wooden vine
#

If even Alice is humbled by this code, what are us mortal users supposed to do 😭

vapid forge
thick slate
split harness
split harness
vapid forge
split harness
vapid forge
#

the PR description says scene-functions without arguments and "scene.bsn" assets can be cached, both of which are clearly identified by their shape and do not need a prefix

#

1.1. Why should users care which option they use during the 0.19 cycle assuming caching for scene functions isn't implemented in time for 0.19?

#

1.2. Why was the syntax :scene named "inheritance" when all it seems to specify is that scene could be cached?

1.3 Are we sure we want to keep the : syntax around for scene functions and scene components? Would it perhaps make sense to only use it for :"scene.bsn" assets?

1.4 If we keep : around, does it make sense to allow scene functions with arguments? I can imagine it would be far more clear what the difference is if those just didn't work on a syntax level, and : always meant "caching" while non: scene functions need to be used for passing arguments. Then it truly becomes the sign for "cache this"

  1. What guidance does it make sense to give right now about when to use SceneComponents vs scene functions?
#
  1. Whats the correct terminology for a top-level scene item? In the #23413 text you talk about both "patch"/"patchable" and " in 'scene entry' position". Is a patch a kind of "scene entry"? is "scene entry" outdated terminology? I've started using "part" and "scene part(s)" insead, which terminology do you prefer?
#
  1. What are the usecases for template(|context |{ ... }) you imagined while writinhg it? I'm struggeling to come up with something to use as an example.
rare whale
split harness
# vapid forge 1. Whats the purpose of separate syntax for `:scene` and `scene` when caching, a...

Whats the purpose of separate syntax for :scene and scene when caching, as described in the PR, doesn't require it to know whats cacheable?

Any scene without inputs is "cacheable" from a functional perspective (as at the end of the day everything is a Scene). The purpose of the : syntax is to give authors control and express intent:

  • Inline Scene Functions: some_scene() is useful for one-off use cases where the author is just mashing functions (especially when functions are small). It is direct / immediate / incurs no extra "caching overhead". Because of this immediacy, it is also less restricted: you can do bsn!{ a() b() c() }, but you cannot do bsn! { :a :b :c }.
  • Inherited Scenes: :some_scene / :"some_scene.bsn" is useful when you are defining something "reusable" (as we just discussed, the fact that function-style inheritance is currently "uncached" is a product of an unfinished TODO list). It is best used when defining "resuable" scenes, especially when some_scene is a "big" / expensive scene to evaluate. Because we are pre-computing these scenes and reusing them, this means that we can only "inherit" from one scene at a time (as scenes "project" on to each other).
split harness
# vapid forge 1.1. Why should users care which option they use during the 0.19 cycle assuming ...

1.1. Why should users care which option they use during the 0.19 cycle assuming caching for scene functions isn't implemented in time for 0.19?
Because "inheritance" vs "inline scene functions" is essentially "just" a performance thing, we can still train people to think about their scenes in the terms above and frame them as such. When we land scene-function-caching those use cases will get a perf boost. By having people adopt :scene early, we also encourage them to adopt those constraints early (ex: can only inherit once).

Alternatively (and this was my initial plan), we could just land scene-function-caching before 0.19 (which I'm kind of still holding out hope for). The caching infrastructure is already there because of asset inheritance caching, it just needs some wiring up for the functions. Very low hanging fruit.

#

1.2. Why was the syntax :scene named "inheritance" when all it seems to specify is that scene could be cached?
It includes additional constraints (there can only be one), so its not "just" a cached vs uncached lever. I think "inheritance" is also largely how people will want to think about it. That being said, we can definitely discuss the framing here if you feel strongly.

#

There is also the :SceneComponent element to consider, which (when parameterized with "props") would not be cached. This is a bit of an elephant in the room when trying to frame : as "cached".

I do think that :SceneComponents without props should be cached by default

#

We can also theoretically cache Scene Components with props (and functions with args) provided the props / args are hash-able

#

The "challenge" here is that syntactically you can't drop the : on SceneComponent without "downgrading" it to a normal component patch (which is still valuable / has use cases)

#

1.3 Are we sure we want to keep the : syntax around for scene functions and scene components? Would it perhaps make sense to only use it for :"scene.bsn" assets?
Scene functions can be arbitrarily complex / expensive. I see no reason to limit it to just assets.

split harness
# vapid forge 1.2. Why was the syntax `:scene` named "inheritance" when all it seems to specif...

1.4 If we keep : around, does it make sense to allow scene functions with arguments? I can imagine it would be far more clear what the difference is if those just didn't work on a syntax level, and : always meant "caching" while non: scene functions need to be used for passing arguments. Then it truly becomes the sign for "cache this".
This is a good thought / it was my plan for when I got around to implementing function caching. Adopting that constraint earlier would have made sense ... its just that capability predates caching entirely 🙂

#

What guidance does it make sense to give right now about when to use SceneComponents vs scene functions?
Scene Components are for building cohesive "things", especially if they have "behaviors" that build on the scene logic (ex: a Player, a Button, etc.

Scene functions are best used for pieces of functionality that you layer on top of something else, "reusable" or "factored out" scene logic, or logic that isn't really a self-contained "thing" (ex: a widget "style", shared logic between a Human and an Elf Scene Component, etc).

#

Whats the correct terminology for a top-level scene item? In the #23413 text you talk about both "patch"/"patchable" and " in 'scene entry' position"
SceneEntry is only used in the context of the AST / it isn't a user-facing concept / should not be a part of the documentation. It largely refers to a snippet of BSN syntax that produces something that implements Scene, although that doesn't need to be the case (ex: we could theoretically add something that is semantically in SceneEntry position that does some form of validation, modifies other entries, defines variables, etc, but doesn't produce a Scene value).

Patches are indeed a type of "scene entry" (and a type of Scene ... for example TemplatePatch).

The tension here is that a "piece" of a scene still impls Scene. This is kind of the whole point: scenes are composable / nestable / embeddable things.

#

When referring to "scene parts", I think it is probably best to refer to them by their individual names, rather than trying to provide some additional name for a single "thing" that happens to implement Scene.

For example: Player { health: 10 }is a "component patch"

#

What are the usecases for template(|context |{ ... }) you imagined while writing it
Two primary use cases:

  1. Things that require World / entity spawn context, but don't implement FromTemplate or Template. This is most useful for partial BSN migrations and types that people don't control. It had much more usage earlier in the porting process.
  2. One-off template logic that the user doesn't want to implement a whole new type for, or that they want to be viewable / readable inline in their scene.
vapid forge
#

a term for "every thing that can be part of the whitespace separated list of thing added to an entity"

#

its easy to say from a technical perspective that it doesn't make sense to have a name for that, but people will expect a name and if there isn't one predefined everyone will call it something else, which is horrible for documentation and knowledge-sharing in the community

split harness
vapid forge
#

fair, it also matches what the macro uses so thats... something

vapid forge
# split harness > Whats the purpose of separate syntax for :scene and scene when caching, as des...

(inline scene functions and inheritance)
The problems i have with this, well, with explaining this, are

  • its very... loose. From a cynical perspective one could argue that even with all the things you've outlined, the : syntax is still largely meaningless and in a sense only serves as inline documentation about intent.
  • its using a very loaded word, inheritance, which comes with lots of implications, to mean "cacheable". This dilutes the meaning of the term IMO, and i wish i'd caught on to "inheritance" being "meaningless" way earlier. Let me explain it like this: I contributed a whole somewhat complex feature to BSN which touched the entire stack of how BSN gets converted to entities and components and still didn't understand that "inheritance" is actually a no-op untill i spent 20 minutes manually flattening 2 versions side by side to see that the diff shows nothing.

By using a term like "inheritance" we will make people assume theres something more to it, and spend time looking for that "something more". Thats a lot of wasted time and frustration we could avoid by... not using such a loaded term.

#

yes, "no-op" and "meaningless" are exaggerations and it does come with the "there can be only one" constraint, but compared to the breadth of baggage "inheritance" has in other related areas its basically nothing. Even more so as the meaning it does have is almost wholly different than the one it has in other places.

vapid forge
vapid forge
#

again, i dont want to actually get rid of it, but i want to point out how large the mismatch is between a term as loaded as inheritance and something as "light" as :

split harness
#

the : syntax is still largely meaningless and in a sense only serves as inline documentation about intent.
This is only true until caching is implented for functions. Which the first part of your analysis ignores

split harness
vapid forge
#

again: this is 99% about the term "Inheritance" and 1% about the perhaps not immediately that useful syntax

split harness
#

Happy to discuss the best way to improve terminology, syntax, and how they feed into each other

#

I personally consider the only major unresolved "tension" in the design to be :SceneComponent, which is not cached when parameterized / generally shouldn't be

#

In terms of syntax

#

"inheritance" vs "caching" for that piece of syntax is debate-able

#

But only after that tension is resolved

vapid forge
#

mhmm. In the PR there was talk about a @Coponent syntax, is that still relevant?

split harness
vapid forge
#

oof, another thing for which i'm pretty sure there are 0 tests

vapid forge
#

no wait, it'd be At

split harness
vapid forge
#

ah, is_template

#

what makes that necessary?

#

as in, why cant TemplatePatch work as a FromTemplatePatch?

#

wait wut, BsnEntry::TemplatePatch sets ty.to_patch_tokens is_scene_component to true?

split harness
# vapid forge what makes that necessary?

FromTemplate exists to say "I want to use this 'canonical template' for the given type". What if you want to define a custom "non-canonical" template that also produces that type?

#

This is admittedly, a largely theoretical thing. We haven't yet encountered a need upstream

vapid forge
#

Wouldn't that be a case for template(|context|{}) or similar?

split harness
vapid forge
split harness
#

Thats exactly what @MyTemplate {} syntax is for

#

Where MyTemplate impls Template

#

And produces some arbitrary output

rare whale
#

@split harness @vapid forge One problem I have with the current docs is the inconsistent use of the word "parent" (along with "child" and "inheritance") - we really have two different axes of parentage here, and the terminology is ambiguous. I would suggest picking a different word such as prototype, base, derived, or extension.

vapid forge
#

at least in the "component implements FromTemplate" case

rare whale
#

What we have now is closer to JavaScript's prototype inheritance than it is to C++

split harness
vapid forge
vapid forge
#

amazing sentence 10/10, completely comprehensible

#

(sorry alice)

split harness
vapid forge
rare whale
vapid forge
#

between the code generated when using inheritance and using only inline scene functions?

split harness
# vapid forge i suppose the confusion for me is: isn't that what components already do?

Normal "component patch syntax" relies on FromTemplate to determine what Template to use to produce that component.
Ex: Sprite implements FromTemplate, where FromTemplate::Template == SpriteTemplate. SpriteTemplate is a Template, which produces a Sprite.

So the question is: what if I define MyCustomSpriteTemplate, which impls Template, that also produces a Sprite? How would I express that (and patch it) in BSN?

split harness
#

Yes: the output is currently functionally identical. But that is because we haven't yet implemented function scene caching.

vapid forge
#

because theres nothing to compare, not on the behavioural level when spanwing a single entity

split harness
vapid forge
#

Even in its desired form, the only reason to use : is as a performance optimisation no?

split harness
vapid forge
#

which is not a constraint considering:

bsn!{
  :my_scene()
  my_scene()
}

is completely valid

split harness
#

When put together, it creates something that closely resembles a "class-inheritance"-like pattern (which also generally only supports one base class)

split harness
#

There can only be one "inherited scene"

vapid forge
white lichen
#

whats the point of an opt-in restriction when you can just side step it by default

vapid forge
#

i could also just do:

bsn! {
    :noop()
    my_scene()
    my_other_scene()
}
#

which still doesn't produce any difference in final result when spawning the scene

#

i hope you can understand why i'm not done with this topic. If i'm wondering "whats the point" theres sure to be quite a few more people wondering the same

split harness
#

I agree that they produce the same results. They just perform differently

white lichen
#

Can we just special case the first one to be : implicitly?

vapid forge
#

sure, but usually when considering "inheritance" therse not a way to achieve the same result completely without it

white lichen
#

that would eliminate the syntax and keep the perf and semantics

vapid forge
#

eliminating the syntax isn't really the goal

#

the goal, at least for me, is understanding how the hell i'm supposed to communicate this to people while using a name like inheritance without sounding insane

#

"yeah so inheritance is just a performance optimisation, it doesnt provide any functionality"

#

i beg of you, please, understand how strange that will sound to anyone else

karmic rock
split harness
#

my_scene being cached and my_scene() being uncached feels pretty arbitrary to me.
Also, we could theoretically support:

bsn! {
  "a.bsn"
  "b.bsn"
}

Which doesn't use cached scenes for the assets, but still depends on the assets being loaded

Having syntax for "caching" (or whatever we choose to call it) feels like a good thing

vapid forge
split harness
vapid forge
#

swap out the symbol to something new. maybe = or - or # or something to separate it so i can finally get on with writing docs

#

maybe even * would work

#

as a sort of 'unpacking'/'dereferencing'

split harness
vapid forge
#

mhmm

split harness
#

However :@SceneComponent doesn't feel very good to me

vapid forge
#

and maybe * makes sense for Template?

rare whale
vapid forge
split harness
#

Although maybe thats worth it, given that it plays "nicely" / symmetrically with the other forms of "scene inheritance":

my_scene
:my_scene
"my_scene.bsn"
:"my_scene.bsn"
@MyScene
:@MyScene
vapid forge
#

yeah, i dont think thats tooo bad

#

considering that caching a scene component is, i suspect, not gonna be a thing all too often

#

or hm

#

actually no it'd happen often enough

#

but yeah, the symmetry is probably worth it

split harness
vapid forge
#

the only consideration to me is if * works better:

my_scene
:my_scene
"my_scene.bsn"
:"my_scene.bsn"
*MyScene
:*MyScene
#

i think it does

split harness
#

Although a MyScene scene component with bsn! {:"my_scene.bsn" } is likely to be the most common

vapid forge
#

"component dereferences to scene" kiinda makes sense as well if you're generous (for the * instead of @ idea)

split harness
#

even if you're doing @MyScene {}

vapid forge
karmic rock
# rare whale Sorry, right - it's been so long. In JS, the *values* are inherited

one neat thing in flecs is that you get to do both with the same IsA inheritance relationship*:

struct Point2 {
  x : f32
  y : f32
}

// Point3 (type) entity inherits from Point2 entity
struct Point3 : Point2 {
  z : f32
}

prefab base {
  Point3: {10, 20}
}

// Instance entity inherits from base entity
instance : base {
  // ... inherits Point3 value
}
split harness
vapid forge
#

ahh

vapid forge
split harness