#Next Generation Scenes

1 messages Β· Page 9 of 1

split harness
#

I see where your head is at, and I think that is a valid way to frame things. I do still disagree though πŸ™‚
I think people should think about their "scenes" as analagous to "blueprints / schematics". A "scene" is not the final generated product, but the description of what that might be. We already have words for the final generated products (Entities, components, systems), and those are not meaningfully compatible with the "scene" functionality. Likewise ResolvedScene is enough of an implementation detail that I would not give it the Scene name. ScenePatch also isn't a canonical scene object and also isn't user-facing enough to merit the Scene name.

rare whale
#

I don't really care how the separator is spelled (you know my suggestion but I'm not married to it), but I think that using a separator of some kind is vastly better than defining a new kind of bracket.

split harness
#

If you ask somewhere "where is the player scene"? They would point to the player.bsn file, or the fn player() -> impl Scene function

#

The terminology lines up with how people will think about and use these things in practice

#

FWIW we have World::spawn_scene / Commands::spawn_scene now, which guarantees an immediate spawn, provided the scene has no dependencies (or the dependencies are already loaded)

rare whale
#

It would be more robust to use the non-immediate version. I'm not worried about the latency, other than I don't want a gap

split harness
#

Of course, that is not how Rusts match works

rare whale
split harness
#

But for some reactive switch, this is 100% possible

#

(maybe not currently, but theoretically doable in userspace)

rare whale
#

I mean, in practice the dependencies will almost always be loaded, but we can't guarantee that, and panicking 1% of the time is a deal breaker

split harness
rare whale
#

So instead, my idea is that the list of children contains a poison pill that nukes the old children

#

(mixing deadly metaphors is deadly)

split harness
#

But I'll note that pausing for an indefinite period of time to wait for a scene to load and resolve at the result of a button click doesn't feel good. I think we should be aiming to build a reactive system that is aware of anything that couldn't be spawned immediately / gives the developer the tools to specify a given context as "wait at the beginning for this dependency before spawning the root" and "wait for this dependency lazily whenever it is first encountered"

#

And I think in general we should be pushing people toward the former, in the context of scene inheritance

#

Ideally in such a way that lets us "pre-resolve" the entire scene

golden notch
rare whale
# split harness But I'll note that pausing for an indefinite period of time to wait for a scene ...

The easy thing to do, implementation-wise, is to allow the user to pre-declare the dependencies - basically a way to say "here are some handles, don't display them or do anything with them, but just hold on to them". This puts the extra burden on the developer to embed the handle pre-declarations, and would be onerous especially in the case of libraries that have dependencies (potentially breaking encapsulation). But I can't think of an alternative, because we don't know how far down the dependency tree we'd have to search otherwise.

split harness
#

Which is the space that Scene::register_dependencies intends to fill

#

If someone wants to guarantee that an Image asset is loaded before spawning their scene, they should be able to express that

rare whale
#

Here's a concrete use case: a modal dialog box containing a text entry field that uses a special font. When we open the dialog, we would like to ensure that the font is loaded before we begin the opening transition animation.

golden notch
# split harness If someone wants to guarantee that an `Image` asset is loaded before spawning th...

oh yeah i totally agree because that kind of model is also easier to reason about, but pedagogically there’s always a risk of presenting the simple
model upfront but then hitting a complexity cliff where for a real production app you need to learn how to do the asynchronous thing. i think/hope we can make the performant architecture friendly and approachable so that people only opt for the fully synchronous model when they have good reason to and build up good prod skills as they learn the engine

split harness
rare whale
#

(At this point in the conversation, were we at a big game studio, we'd get a bunch of clever young engineers suggesting the use of machine learning models to ingest the logs of asset loads and use that to automate the pre-loading of dependencies.)

split harness
silk lava
#

The ergonomics for the async route needs to be as close as possible to the sync route otherwise new users will likely choose the latter.
Such as with Events and Messages, Messages may be faster or better semantically, but they miss the ergonomics/feature-set of Events so most users default to Events.

fallow cloak
#

(no)

rare whale
# split harness But I'll note that pausing for an indefinite period of time to wait for a scene ...

The perception of delay is a fascinating topic, and depends a lot on context. A typical drummer can detect differences in beats of about 1/60th of a second, but that is true because they are listening to it as part of a pattern / groove and comparing the timing to nearby beats. Game players can see the difference between 60fps and 120fps, but that isn't an awareness of discrete events - the perception of jitter is as much spatial as it is temporal.

For something like a ui transition, where the event is happening disconnected from any other rhythm or sequence (other than the muscle movement of the user's fingers), I would guess that one could introduce 1 or 2 frames of delay before it starts to feel less snappy. Where things get bad is when you have cascading delays, a reaction triggers an asset load which triggers etc.

split harness
#

And unbounded X frames of delay is incredibly scary

queen oak
#

Seeing how nice and fast jackdaw is, is really quite incredible

#

the comparison to unity is striking

topaz ginkgo
#

i will say, I’m really looking forward to getting stuck in with BSN for jackdaw

dark crater
#

I think I must have missed it in the pr, but what syntax was decided on?

snow wedge
dark crater
split harness
#

Later, if we find a child / related syntax we like more, we can switch. But I think what we have currently is good enough for now

copper dragon
rare whale
# split harness I do agree that this is often true. But this is not necessarily one or two frame...

Thinking about this some more: I think that even if the ideal solution is to somehow be able to transitively collect dependencies (and I have no idea how you would do this, especially if you allow scenes in closures), I think that the "atomic children swap for asynchronously resolved scenes" is something we should support anyway. I think it's an important use case, and even if you choose not to go that route, other third-party solutions may want it.

#

@split harness Also, when we do the "real" documentation for BSN, I would prefer something more top-down instead of bottom-up. That is, start by teaching them how to spawn a scene that someone else has provided; then how to compose a scene using prebuilt components; and then, finally, how to define components of their own. The reason is that this keeps the narrative firmly grounded in concrete use cases instead of having to teach them a bunch of abstractions first.

split harness
split harness
#

But this stuff definitely needs book content

rare whale
split harness
#

I think in this context, doing this type of evil is worth it

#

Today (and yesterday) I worked on resolving comments. Still some left, but I'm pretty much down to documentation and tweaks.

golden notch
split harness
#

If specialization ever lands (unlikely imo) then we can remove it πŸ™‚

#

I remember using specialization in pre-release Bevy six years ago on nightly because surely it would land soon

golden notch
#

i will happily accept specialization never shipping in favor of real compile time reflection which feels like it has the potential to cover 90% of the most useful cases for specialization

#

maybe a bit uglier

split harness
#

Any easy links to what "compile time reflection as specialization" would look like

golden notch
# split harness Any easy links to what "compile time reflection as specialization" would look li...

the goals document talks about it a little

but think basically of it as being less open, instead of the compiler selecting the right specialized trait impl for a method call, the library author can just do a bunch of manual (imaginary reflection api):

fn foo<T>(t: T) {
    if const { T == SpecialFastType } {
        t.fast_impl();
    } else if const { T: SomeTrait } {
        t.slow_trait_impl();
    } else { const_panic!("we don't know how to use your thing") }
#

so it's more tedious for the library author and not open but still allows you to present nicer apis

split harness
#

Gotcha yeah that makes sense

frosty shell
#

with an example provided by our local bushrat

golden notch
split harness
#

Implementing GetTemplate for SceneRoot gives us nice usability wins while we wait for the gltf BSN loader:

fn arena() -> impl Scene {
    bsn! {
        Arena
        Children [
            SceneRoot("arena.glb#Scene0")
        ]
    }
}
#

Ultimately I'd like this to be

fn arena() -> impl Scene {
    bsn! {
        :"arena.glb#Scene0"
        Arena
    }
}
#

(once we have the gltf BSN loader)

steel oak
split harness
steel oak
#

SceneRoot for now. I really want the glTF loader though

#

Because then I'll be able to implement descendant overrides. I know you don't want them to be post-spawn but that's the easiest thing for me for now

#

We use ad-hoc descendant overrides of glTFs all over the place and I really want to get them onto a sensible system, even if not perfect

split harness
steel oak
#

It's just a template that lets you specify an XPath-alike and a subtemplate. The template's implementation can change over time

split harness
#

(which is often the same thing, but not always)

#

I'm adding Unpin specialization to the GetTemplate derive so we can start deriving GetTemplate on core Bevy types without losing Defaults

steel oak
#

Yeah, I saw that, nice work πŸ™‚

split harness
steel oak
#

I just mean what you write in the .bsn file. Right now I'm actually writing yet another ad-hoc system that watches for entities with magical names per artist request to work around the lack of overrides and I hate it

white lichen
#

i dont quite understand what that Unpin trick is doing

steel oak
#

I don't either

#

I mean, I know what it's intended to do, but I don't know how it works

#

I can't wait to start migrating my project to dynamic BSN. It's going to fix so many issues it's ridiculous

fallow cloak
#

you could use any trait there but auto traits are nice because they make it opt-out instead of opt-in

split harness
#

Alrighty theres some messiness to discuss. This messiness existed before "GetTemplate unpin specialization", but that does make the situation worse:

  • Option<T>, Vec<T>, HashSet<T>, etc all have auto-Template/GetTemplate implementations where Option<T>: Clone + Default. When T does not manually implement T: GetTemplate, we're already golden. Everything works as expected.
  • When we want Option<T: GetTemplate> for some manual T: GetTemplate impl (ex: Option<Handle<T>>, Option<Entity>), Option<T> does not behave as desired by default
  • We cannot impl<T: GetTemplate> GetTemplate for Option<T> because that conflicts with the blanket impl. And we really want that blanket impl because it means pretty much everything is compatible by default.
  • Prior to "GetTemplate unpin specialization", this would fall back to the auto impl: Option<T: Default + Clone>: GetTemplate. This would silently not support the inner template. Ex: for Option<Handle<Image>> the template would also be Option<Handle<Image>>, meaning the template would only accept handle values (ex: no Some("path.png") in bsn!, only Some(handle)). My plan for supporting inner templates was to have manual OptionTemplate / VecTemplate etc, with a #[template(OptionTemplate<T>)]. Obviously we would love for this all to be automatic, but I don't think it fits into the type system unless we give up the blanket impls,which would be quite bad from a UX perspective ... we'd be in a "serde" situation where every type throughout the Rust ecosystem needs Serialize, Deserialize impls.
#
  • With "GetTemplate unpin specialization", this gets weirder. Now when deriving GetTemplate for types that use this specialization (ex: Handle<T> has a manual impl, and I've been playing with adding it automatically to the GetTemplate derive), instead of silently getting Template == Option<Handle<Image>>, we now fail to compile with the inpenetrable error:
#[derive(GetTemplate)]
struct X {
    field: Option<Handle<Image>>,
}
error[E0277]: the size for values of type `[()]` cannot be known at compilation time
   --> examples/scene/bsn.rs:43:10
    |
 43 | #[derive(GetTemplate)]
    |          ^^^^^^^^^^^ doesn't have a size known at compile-time
    |
    = help: the trait `Sized` is not implemented for `[()]`
    = help: the following other types implement trait `bevy::prelude::Template`:
              EntityReference
              FnTemplate<F, O>
              HandleTemplate<T>
              OnTemplate<I, E, B, M>
              OptionTemplate<T>
              TemplateTuple<()>
              TemplateTuple<(T0, T1)>
              TemplateTuple<(T0, T1, T2)>
            and 11 others
    = note: required for `bevy::prelude::Handle<bevy::prelude::Image>` to implement `Unpin`

The fix is still:

#[derive(GetTemplate)]
struct X {
    #[template(OptionTemplate<Image>)]
    field: Option<Handle<Image>>,
} 

But we've gone from silently only supporting Option<Handle<Image>> in templates to failing with an error that makes no sense.

#

Related:
On the topic of improving the ergonomics of #[template(OptionTemplate<Image>)], if we add a second GetTemplate-style trait that doesn't have a blanket impl (for example: BuiltInTemplate), then we could implement that for things like Option<T: GetTemplate>. Then it would just look like this:

#[derive(GetTemplate)]
struct X {
    #[template(built_in)]
    field: Option<Handle<Image>>,
} 
#

(note that I haven't wired up the #[template(CustomTemplate)] feature yet ... I'll do that before we merge the PR)

#

Potential names for this concept: template(built_in), template(alternate), template(manual).
I think alternate is simultaneously the most accurate and the least helpful, from the perspective of users.

#

template(generic) is close, but notably GetTemplate derives (and manual impls) do support generics!

#

We could also just look for common types like Vec and Option and do this automatically

#

Although I really don't like using type paths for cases like this

#

Brittle and magic

steel oak
#

Oh man, I just started wondering whether my animation retargeter could just be a BSN template

#

That would simplify my work so much

copper dragon
#

Do we have any of the metadata syntax for .bsn designed?
Like versioning, asset bindings/paths, template vs patch, etc?

steel oak
copper dragon
#

Its gonna be really fun when someone manages to make a bsn/rust polyglot file

split harness
#

Nothing set in stone. Just an option

topaz ginkgo
#

How does bsn load the components that have been declared? Is it iterative, or can it be parallelised?

split harness
# topaz ginkgo How does bsn load the components that have been declared? Is it iterative, or ca...

The answer depends on what you mean by "load": Scenes are resolved to ResolvedScene, which is a spawnable/reusable scene description, and the ResolvedScene is spawned into the World as an entity. But in general parallelizing scene things is hard, due to the constraints at play.

When resolving scenes, the "scene patches" are applied in order, the order matters, and they can affect arbitrary levels of the hierarchy. Not a very parallelizable problem space. If we know that a given scene patch wont "reach in" to patch a descendant, then we could theoretically parallelize individual entities in the hierarchy.

Spawning anything in Bevy requires &mut World at the moment, which definitely impedes "parallel spawning". We're writing to shared data structures. Theoretically, if we could prepare all of the storage ahead of time (ex: know what Resources / Components / Archetypes / Tables are needed and how many entities in each storage, we could pre-allocate space and write to it in parallel. But the problem here is that scene spawn order is also a logical thing. Each spawn can have hooks/observers, which will benefit from having predictable / consistent execution orders relative to parents / children.

#

What we can definitely parallelize is resolving scenes that do not depend on each other

slate pewter
#
// old one
fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    // circular base
    commands.spawn((
        Mesh3d(meshes.add(Circle::new(4.0))),
        MeshMaterial3d(materials.add(Color::WHITE)),
        Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
    ));
}

// bsn one
fn base_scene(
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>
) -> impl Scene {
    let base_mesh = meshes.add(Circle::new(4.0));
    let base_mat = materials.add(Color::WHITE);

    bsn! {
        Mesh3d(base_mesh)
        MeshMaterial3d(base_mat)
        Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2))
    }
}```

error[E0277]: the size for values of type [()] cannot be known at compilation time
--> src\main.rs:51:5
|
51 | / bsn! {
52 | | Mesh3d(base_mesh)
53 | | MeshMaterial3d(base_mat)
54 | | Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2))
55 | | }
| |_____^ doesn't have a size known at compile-time
|
= help: the trait Sized is not implemented for [()]
= help: the following other types implement trait GetTemplate:
bevy::prelude::Entity
bevy::prelude::Handle<T>
= note: required for bevy::prelude::Handle<bevy::prelude::Mesh> to implement Unpin
note: required because it appears within the type bevy::prelude::Mesh3d
--> C:\Users\flfuac.cargo\git\checkouts\bevy-50d7e162b728c6c6\c35e428\crates\bevy_mesh\src\components.rs:98:12
|
98 | pub struct Mesh3d(pub Handle<Mesh>);
| ^^^^^^
= note: required for bevy::prelude::Mesh3d to implement GetTemplate

split harness
#

This is common enough of a thing to make me want to rethink these shennanegans

#

The "solution" is to derive GetTemplate on Mesh3d. But of course thats not something we can expect people to know

#

Real specialization would solve all of these problems 😭

thick slate
split harness
#

Just pushed:

  • GetTemplate derive now supports #[template(CustomTemplate)], enabling fields to define custom templates
  • OptionTemplate: enables support for things like Option<Handle<Image>> via #[template(OptionTemplate<HandleTemplate<Image>>)]
  • SceneRoot now derives GetTemplate, enabling bsn! { SceneRoot("level.gltf#Scene0") }
topaz ginkgo
#

@steel oak had made reference to an editor which edits the bsn - i was curious what your take is, as i'm obviously very interested in exploring this in jackdaw

steel oak
#

The BRP is for editing components.

#

You can wait for @cart to confirm but it's true πŸ™‚

topaz ginkgo
steel oak
#

I don't think anyone has worked on this; it's just too new right now

#

BSN itself is still undergoing flux

#

I'm obviously interested but I have a ton of things on my plate

topaz ginkgo
#

yea fair enough, i was thinking a happy medium just for now might be something to do with jackdaw modifying .bsn files, but then as i understand those won't be part of this PR

split harness
#

As it was quite long, here are some milestone markers

#1264881140007702558 message
#1264881140007702558 message

topaz ginkgo
steel oak
#

Actually rereading the conversation I think @split harness is less absolutist than I am about no ECS -> BSN writeback.

#

Though the disagreement might not have any practical effect.

#

I think part of my absolutism about no ECS -> BSN is the fact that I work on a the type of project where we use artist-defined prefabs and overrides everywhere. And it really is a totally different model. The #1 thing that's always at the top of mind for me is "how will an artist who works in Maya interact with this system"

split harness
#

Yeah the true takeaway from that conversation should be: start with BSN editing as the source of truth and then expand out into ECS->BSN writeback (perhaps partial writeback) as it makes sense.

steel oak
#

I agree

split harness
#

Just pushed:

  • Support for Handle values in HandleTemplate
  • GetTemplate derives for Mesh3d and MeshMaterial3d

@slate pewter this makes it possible to spawn the mesh scenes in 3d_scene, although it requires a bit more boilerplate than I like:

let base = meshes.add(Circle::new(4.0));
let material = materials.add(Color::WHITE);

commands.spawn_scene(bsn! {
    Mesh3d({base.clone()})
    MeshMaterial3d<StandardMaterial>({material.clone()})
    Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2))
});
steel oak
#

Editing meshes and animations in-editor isn't really interesting to me, for example (that doesn't mean I don't think that shouldn't be a supported workflow, mind you). Artists are going to do all that stuff in Maya. My interest is "how do I create a system that allows me, the programmer/tech artist, to cleanly integrate with these standalone glTF scenes that contain art assets only"

split harness
#

The clone() is required due to how the ownership works internally. I suspect we can figure out a way to remove that

#

The other sad piece of boilerplate isMeshMaterial3d<StandardMaterial>, as type inference breaks down in the context of handle.into()

steel oak
split harness
# split harness The `clone()` is required due to how the ownership works internally. I suspect w...

Actually maybe not resolvable. Not the first time we've discussed this. The issue here is that base is owned by the generated patch closure , which is repeatable. Meaning each invocation of the patch needs a new owned instance.

This is the relevant generated bsn!

    let base = meshes.add(Circle::new(4.0));
    <MeshMaterial3d<StandardMaterial> as bevy_scene2::PatchGetTemplate>::patch(
        move |value, _context| {
            value.0 = (material.clone()).into();
        },
    ),

(edit: missed a .clone())

split harness
#

Not to say users can't do a FBX -> BSN conversion and do direct edits on top. But I personally think that workflow is crazy. People ask for it / use it though in other engines

steel oak
#

Another thing is animation retargeting which is a whole 'nother kettle of fish I haven't even fully thought about

#

Well, we have a fully functional animation retargeting system (mostly OSS too as bevy_animation_controllers, albeit undocumented)

#

Some amount of it may be able to be converted into Templates which would be wonderful

#

But the needs are vast. For example: just yesterday I had an artist give me an animation that didn't match up to the rig because the bones were named things like deformation_grp/Hips in the rig and were prefixed with the character name like Ferris:deformation_grp/Ferris:Hips in the animation

#

Retargeting systems (ideally a Template of some kind?) need to be able to handle this kind of nonsense. e.g. a template like @RetargetAnimation { animation: "path/to/MyAnimation.gltf#Animation0", strip_prefix: "Ferris:" }

steel oak
steel oak
rare whale
#

@steel oak I am interested in editing of bsn, but possibly in a different way. As I mentioned, my game is a large open world that is a hybrid between manually created and procedurally generated. In my previous implementation, I leaned heavily on JavaScript prototype inheritance via "exemplars" and "aspects": for example, "alpha wolf" inherited properties from "wolf" which inherited from "NPC", and the system would, at runtime, compose together the various aspects that made up that character (the wolf skin, armature, animations, as well as the behaviors: territorial, combatant, canine combat skills). Originally these aspects were defined as JSON objects, but I think it may be possible to translate them into BSN files using BSN's inheritance feature. Since I don't want to spend eternity authoring BSN files in a text editor, I'll need to be able to edit these BSN aspects and compose them together in the game editor.

steel oak
steel oak
slate pewter
leaden dew
# split harness Sadly no dice here.

isn't this possible by changing the negative impl from [()]: Sized to an equally trivial condition using our own trait?

for example:

#[diagnostic::on_unimplemented(message = "Helpful error message here!")]
trait BlahSpecializationError {}
impl<T> Unpin for Blah<T> where for<'dummy> (): BlahSpecializationError {}
error[E0277]: Helpful error message here!
   --> src/spec_test.rs:16:6
    |
 16 |     <Option<Blah<i32>> as Something>::VALUE;
    |      ^^^^^^^^^^^^^^^^^ the trait `BlahSpecializationError` is not implemented for `()`
    |
<rest of the error message is similarly unhelpful as before>

this is also nice because we can provide a different error for each type being specialized to point people in the right direction

split harness
#

I’ll give it a whirl tomorrow

split harness
split harness
leaden dew
# split harness Presumably you didn't get this working?

I got it working but not on anything BSN related so I'm not sure what differences might throw it off

struct Blah<T> {
    data: T,
}
trait Something {
    const VALUE: i32 = 1;
}
//Blanket implementation.
impl<T: Unpin> Something for T {}
//Negative implementation
#[diagnostic::on_unimplemented(message = "Helpful error message here!")]
trait BlahSpecializationError {}
impl<T> Unpin for Blah<T> where for<'dummy> (): BlahSpecializationError {}
// Specialised implementation!
impl<T> Something for Blah<T> {}
fn blah() {
    <Option<Blah<i32>> as Something>::VALUE;
}

this produces the error message I posted above

#

another version of the same idea I saw online used PhantomPinned: Unpin which is indeed also an auto trait. I don't really understand the evil spell enough to tell whether that's materially relevant though

#

as far as i can tell the bound just needs to be a trivially false bound, which would normally be a compiler error but the for<'dummy> defers it or something

split harness
leaden dew
#

oogh...

split harness
#

Probably orphan rule-ey

#

And that explains why Sized works, as nobody can manually implement that

#

Building on this does probably introduce a degree of risk, as this does feel like a hole that could be closed at any moment.

#

That plus the arcane-ness of the errors makes me want to pursue other options

#

Sadly I don't think any option will be as satisfying as "supports both Default and GetTemplate"

leaden dew
#

e.g. this test in bevy_scene2 behaves as expected after the change in bevy_ecs

#[diagnostic::on_unimplemented(message = "This is my super helpful Handle specialization error")]
trait AssetSpecializationError {}
impl<T: Asset> Unpin for Handle<T> where for<'dummy> (): AssetSpecializationError {}
split harness
#

I just pushed a fix for autocomplete of named fields that are impartial / added in the middle of struct:

bsn! {
  Foo {
    bar: 1,
    a // typing here
    baz: 2,
  }
}
#

Its a case of "the current contents are not valid BSN, which fails parsing, which means Rust Analyzer never has a chance to resolve the final symbols"

The trick is to make the macro successfully parse and pass through the partial a field, despite not having the , separator

#

It already did this same type of thing for partial fields (that don't have : value), which is what allowed autocomplete to work in the other cases

tawdry owl
split harness
split harness
#

(constrained on AssetSpecializationError)

split harness
split harness
#

Perhaps a reason to adopt mandatory#() / #ChildName() syntax for children

tawdry owl
split harness
#

That would make it all get parsed as a single entity

#

Which is actually valid from an autocomplete perspective

#

But it would have potentially weird interactions with features like#Name

split harness
#

The middle one should be (Node Foo) (no comma), which is not ambiguous with the A(Node, Foo) tuple

#

However to know the difference, the parser would need to peek all the way in

#

And A(Node) is a truly ambiguous case

topaz ginkgo
#

apols for another basic question, but i assume nobody is looking at serializing bsn to a file atm right?

leaden dew
steel oak
#

Pretty printing should be straightforward, but I haven't implemented it yet

topaz ginkgo
steel oak
#

Please feel free to send PRs to my branch btw

topaz ginkgo
#

Ive actually cherry picked your commit onto my fork of carts branch

#

Might be best to just work directly from yours tbh

steel oak
#

Once cart's BSN PR lands I'll clean up and PR my branch

#

I'd really like to get dynamic BSN in soon because that unblocks so much

#

Probably not for 0.19, but early in the 0.20 cycle

fervent kindle
#

Not trying to be that impatient and demanding guy, but im curious, what is the ETA on the BSN pr?

split harness
split harness
#

PhantomData<T> is an especially annoying instance of a "cannot be unpinned" error

lime veldt
#

do you keep trying to make the pin specialization hack work?

#

it sounded like there is no way

tawdry owl
split harness
#

As soon as I wrap up everything else, I'll make a final call. But I'm leaning toward removing it in favor of something else

#

Which will likely mean that Handle will be harder to consume from templates (for as long as we keep the Default impl around)

split harness
tawdry owl
# split harness Its prevalence

If it's about the error message, constraining on a Sized supertrait with on_unimplemented was working when I tried it. Aside from that, Autoderef specialization can also probably be used instead of unpin hack, but that has its own quirks.

split harness
# dark crater how do you mean?

I think currently its fine, but if we (for example) disallowed duplicate #Name declarations for a single entity, this would break:

bsn! {
    Children [
        #Child1
        A
        ---
        // I want to add a #Child2 here
        #Child3
        B
    ]
}

This would parse as two children, where the second child has both #Child2 and #Child3

bsn! {
    Children [
        #Child1
        A
        ---
        #Child2 // I haven't typed the --- yet so this is read as a single entity
        #Child3
        B
    ]
}
split harness
#

Arg spent like 45 minutes doing a graph toposort to ensure scenes in resolve_scenes (fed by the LoadedWithDependencies events) are resolved in dependency order, only to remember after the fact that these events already fire in dependency order :/

#

Pretty dumb, especially given that I wrote that code

split harness
#

I just pushed changes that give "queued spawning" the better names (ex: spawn_scene / spawn_scene_list are now "queued" and spawn_scene_immediate / spawn_scene_list_immediate are now "immediate)

The rationale there is that queued spawning works by default / can be the thing people use for everything, so it makes sense to push people toward it with the better name.

I've also made queued spawning be "immediate" when there are no dependencies, meaning the changes can be observed right away. I think this makes it more viable as the "use for everything" method.

#

I resolved a couple of other comments. So everything is resolved except @white lichen's suggestion that we do the bevy_scene rename before merging (in a separate PR)

#

Kicking that off now

#

Still need to make a final call on Default specialization

#

But we're very close to being merge ready!

#

We could consider deferring Default specialization alternatives as a followup, given that this does work as-is

rare whale
split harness
#

So its pain they'd need to eat, but it is a logical progression (in that the specialization hack is extremely hairy)

#

Just pushed a merge with main

raw mesa
#

Users often are surprised when SceneRoot doesn't spawn the scene immediately, because the API we give them sounds like it should be immediate, when it's actually deferred

#

I was a big fan of the "queue" naming for that exact reason - it makes it very obvious this API is deferred and you shouldn't rely on it being ready immediately

silk lava
#

I dont like the change in behavior based on dependencies, an users code could work when expecting it to be immediate from the first iteration, but on the second iteration after changing the asset it becomes deferred and the user becomes confused, because now they need to use the deferred pipeline/path.

topaz ginkgo
#

hey @split harness @steel oak - ive been doing some preliminary testing of editing bsn in jackdaw, rather than the components themselves. one thing i can’t quite get my head around is that in order to actually see the changes to the bsn in the editor, i have to actually sync the bsn AST to the ECS anyway. this seems like an additional unnecessary hop - do you think im approaching this in the right way?

side note, i also have a preliminary .bsn file format which is a β€œsnapshot” of the world on a user save

white lichen
#

@split harness can we have the inline rust expressions generated by {} be auto-wrapped in a { } so that

bsn! {
    {
        #[cfg(feature = "pineapple")]
        bsn!{ Pineapple { size:  MASSIVE } }
        #[cfg(not(feature = "pineapple"))]
        ()
    }
}

can become

bsn! {
    {
        #[cfg(feature = "pineapple")]
        bsn!{ Pineapple { size:  MASSIVE } }
    }
}

since let x: () = {}; so the empty case wouldn't need a gated ()?

split harness
# raw mesa Personally I disagree with this. IMO users should prefer the immediate APIs by d...

Hmm yeah these are fair points. I do agree that the old naming and behavior was more predictable. I think the big thing that the new behavior gets us is that it insulates people from changes (ex: if a scene you call like bsn!{ scene() } didn't have a dependency, and then adds one, then suddenly your spawn starts to fail). I liked that there was one method we could recommend that just always works.

But the new behavior has its own (harder to understand) breaking quirks in that area (if you're relying on the immediate behavior). I'm reasonably convinced that the old approach was better

split harness
# topaz ginkgo hey <@153249376947535872> <@451476504879169546> - ive been doing some preliminar...

Yeah doing partial updates definitely requires building a mapping. My plan (and I believe @steel oak's plan as well) was to increase our "incrementality" over time. Start with a full wipeout and respawn, then scope it to entities, then perhaps scope it to individual components.

side note, i also have a preliminary .bsn file format which is a β€œsnapshot” of the world on a user save
Exciting πŸ™‚

#

The benefit of starting with the decoupling is that we get the full expressive power of scenes / templates. I think the jury is still out on whether or not the final BSN editor should have a principled 1:1 mapping between templates and components

#

(at the cost of some expressiveness)

#

You are welcome to explore both avenues

split harness
split harness
rare whale
split harness
rare whale
#

If Output had to be a component, then entities could only have a single effect

split harness
#

Ex: perhaps we add something that isn't a Template to ResolvedScene, which Scene can populate

rare whale
#

This would mean my entire approach would have to be abandoned

split harness
#

Ex: "effects"

split harness
rare whale
#

For the record, Output = () is the only thing I ever do

split harness
rare whale
#

I'm not doing anything more exotic with Output

rare whale
split harness
rare whale
#

If you squint hard enough

rare whale
# split harness Yeah doing partial updates definitely requires building a mapping. My plan (and ...

The approach I have been taking is to make the incrementality visible and explicit; that is, there are different strategies (wipeout and replace vs. mutate in place), so I present to the user an API where they choose, in a component-declarative, fine-grained fashion, which strategy they want. That's not to say that this is the best strategy, but it's one that is easily implementable as a separate layer on top of bsn

split harness
#

Which is not a major concern in the context of macros

#

When compared to functionality

rare whale
split harness
split harness
#

This is intended to work

#

Currently requires {{ }} to work

#

I've started doing the bevy_scene -> bevy_ecs_serialization rename. I think the big open question is what to do with Scene and Handle<Scene> which are both still used for glTF loading

#

(obviously they need to stick around, but what do we call them)

#

Scene is just Scene(World), and for the rest of bevy_ecs_serialization my plan was to just work with World directly

white lichen
split harness
#

But we can't reasonably do Handle<World> for this. And Handle<Scene> / SceneInstance are reasonably high traffic types

split harness
# white lichen Is it currently legible at all?

Legible-ish:

bsn! {
    Node {
        width: Val::Percent(100.0),
        height: Val::Percent(100.0),
    }
    Children [
        button("Ok"),
        button("Cancel"),
    ]
}

Becomes:

bevy_scene2::SceneScope((
    <Node as bevy_scene2::PatchFromTemplate>::patch(move |value, _context| {
        value.width = Val::Percent(100.0);
        value.height = Val::Percent(100.0);
    }),
    bevy_scene2::RelatedScenes::<
        <Children as bevy_ecs::relationship::RelationshipTarget>::Relationship,
        _,
    >::new((
        (bevy_scene2::EntityScene((button("Ok"),))),
        (bevy_scene2::EntityScene((button("Cancel"),))),
    )),
))
rare whale
split harness
rare whale
split harness
#

The migration would definitely feel better if we had a glTF loader for the new scene system

split harness
magic belfry
split harness
buoyant venture
# magic belfry

That screams generated code and not something a human would ever write

white lichen
#

whats with all the < as Patch> stuff

rare whale
#

Boring but straightforward

raw mesa
split harness
#

I'll note that the traits were designed to be a bit friendlier than that to author in person.
This generated code:

 <Node as bevy_scene2::PatchFromTemplate>::patch(move |value, _context| {
  value.width = Val::Percent(100.0);
  value.height = Val::Percent(100.0);
})

Can be written this way when PatchFromTemplate is in scope:

 Node::patch(move |value, _context| {
  value.width = Val::Percent(100.0);
  value.height = Val::Percent(100.0);
})
#

But we can't generate code that way, as it would require the developer to import PatchFromTemplate, and it could conflict with other patch functions in scope

split harness
thick slate
magic belfry
#

also have we done anything about requiring {} everywhere for trivial cases like Mesh3d({meshes.add(cube)}) and {Transform::from_translation(v).with_rotation(q)} and if not, is that planned for the mvp

thick slate
white lichen
split harness
raw mesa
split harness
split harness
#

That leaves SceneRoot

rare whale
split harness
rare whale
#

Something implying a subtree

split harness
white lichen
#

Prefab?

magic belfry
#

but the {} spam, constantly breaking autocomplete, and lack of formatting felt pretty bad

silk lava
#

SpawnAt maybe with a generic marker SpawnAt<WorldAsset>

magic belfry
#

the lack of formatting is my biggest pain point and a dealbreaker for me honestly

split harness
# white lichen Prefab?

Introduces a new (desirable) concept name for something we intend to remove shortly, and could be confusing given that we're introducing a new scene system while also introducing the new name

white lichen
#

DeprecatedSceneRoot

#

lol

split harness
white lichen
#

actually its not deprecated yet

magic belfry
verbal heron
white lichen
#

EarlyAccessDeprecatedExperimentalLegacySceneRoot

raw mesa
#

I mean the current Scene and SceneRoot still have a place

#

Even after bsn

split harness
#

Perhaps this is all pointing to an "experimental" BSN release, where we leave bevy_scene as-is for now

white lichen
#

if Scene is becoming WorldAsset cant SceneRoot just be WorldAssetRoot

white lichen
split harness
#

The bevy_scene rename starts the "push" to bevy_scene2, and I do think another release to get usability in order would help (glTF scene ports, formatter, reasonable asset instantiation story, etc)

split harness
split harness
#

bevy_scene2 crate + bevy_scene2_experimental feature

split harness
split harness
#

@white lichen given that I think the bevy_ecs_serialization rename vs experimental designation are coupled, I think it might make sense to merge it in its current form first, then make a call on the final packaging

white lichen
#

sure

#

i just would like to avoid a PR that moves bevy_scene to something else and also moves something else to bevy_scene

split harness
rare whale
split harness
#

Cool in that case I think there are just three issues to sort out before merging:

  • Still need to make a final call on Default specialization
  • Always surround {} blocks in {}
  • Initial release notes stub (I would very much prefer to flesh this out during the RC cycle)
rare whale
white lichen
#

i think the PR should get at least one approval from someone though

#

@thick slate do you feel comfy approving? i saw your reviews were probably the deepest

topaz ginkgo
# split harness You are welcome to explore both avenues

Great! I’ve been playing with Patch updates, because the concept of a delta just makes more sense to me - ie, when I update the Translation.x value in jackdaw, i would expect just that to update (and not a full Transform update)

split harness
topaz ginkgo
thick slate
#

@split harness bother me when you think it's ready

topaz ginkgo
#

sneak peak at the .bsn file format i'm working on with the jackdaw's Brush geometry

thick slate
topaz ginkgo
split harness
thick slate
split harness
#

@white lichen I attempted a quick toy bsn! macro_rules implementation just to see what the tradeoffs would be in practice. I got as far as "multiple struct-style" struct and enum patches, and lists of ONE related entity.

bsn_rules! (
    Foo::Bar { thing: 10, y: 10 }
    Node { width: Val::Px(10.0) }
    Children [
        Node { width: Val::Px(10. ) }
    ]
)

Source:

macro_rules! bsn_rules {
    () => {()};
    ($type_name:ident$(::$variant:ident)? { $($field:ident$(: $value:expr)?),* $(,)? } $($tokens:tt)*) => {
        (<$type_name as PatchFromTemplate>::patch(|value, _context| {
            if let PathResolveHelper::<<$type_name as FromTemplate>::Template>$(::$variant)? { $($field,)*  ..} = value {
                $(*$field$(= $value)?;)*
            }
        }),
        bsn_rules!($($tokens)*))
    };
    ($type_name:ident$(::$variant:ident)? [ $($child:tt)* ] $($tokens:tt)*) => {
        bevy::scene2::RelatedScenes::<
            <$type_name as bevy::ecs::relationship::RelationshipTarget>::Relationship,
            _
        >::new((
            bevy::scene2::EntityScene(bsn_rules!($($child)*))
        ))
    };
}
#

Things start to break down:

  1. When trying to have multiple children, as $($child::tt)* is required to capture all of a single child's tokens. Trying to wrap that in a comma-separated list doesn't work, as commas are also tokens.
#
  1. Tuple-style inputs, which requiring defining identifiers for the matched tuple indices. I believe this is possible token munchers.
#

My takeaways:

  1. Autocomplete isn't magically fixed. It requires solving the same problems we solve in the the proc macro.
  2. It does not help the formatting situation. Rustfmt still breaks on BSN syntax because it isn't valid rust.
#

Plus theres the whole "#Name in value position" almost certainly being impossible

#

If anyone wants to explore further they can. But I'm convinced that a macro_rules! port isn't going to solve any of the hard problems for us, and it creates new hard problems to deal with

#

I do like how concise it is though!

#

And I think the "use irrefutable let patterns to share code between struct patches and enum patches" might help simplify things. I believe it also enables us to not need to rely on naming conventions to extract "enum-ness"

#

as struct vs enum will use the same code.

#

I was planning on exploring "destructuring structs in patches" anyway, as I believe that would solve the "suggesting functions when typing fields" problem

split harness
#

And as evidenced by this not being a problem in the bsn_rules! impl, when irrefutable let is used

rare whale
split harness
#

(but I'm dropping it now to wrap things up)

dark crater
dark crater
topaz ginkgo
#

so i've got a very basic implementation of writing the .bsn to a file, and then loading that as a scene in jackdaw - there are still some issues with selection highlights and we've lost some of the Brush Groups on save (but these are just serialization issues)

#

i don't think the grammar supports spaces @steel oak : if i write something like #Brush Group, it just parses it as #Brush (which is actually a different entity type in jackdaw)

white lichen
topaz ginkgo
#

much better! we can no save/load .bsn and compose scenes as well! i need to work on bringing down the filesize next, so will implement the import-type aliasing πŸ™‚

steel oak
split harness
split harness
steel oak
topaz ginkgo
#

I changed the grammar to basically parse until end of line

steel oak
split harness
#

We could probably support #”name” too

steel oak
#

you name a Unicode code point, some artist somewhere has put it in a Maya file πŸ˜†

#

and then when you ask "how did that get there" it's like "idk"

split harness
#

Rather than pretend the file format supports arbitrary expressions

white lichen
topaz ginkgo
topaz ginkgo
#

Do you have reservations with something like #Brush and #Brush Group - or do you prefer #Brush and #{Brush Group}

#

My preference is just simply #Brush and #Brush Group for Name

steel oak
#

I defer to @split harness

#

keep in mind that we want it to be compatible with the macro parsing in rust

#

and I'm not sure that rust macro parsing lets you match on newlines like that

white lichen
#

does the space have to be there?

steel oak
#

otherwise I wouldn't have a problem with it

steel oak
topaz ginkgo
white lichen
#

Name("Brush Group")

#

why do we have a special cased way to do it that isnt general and then special case the special case lol

steel oak
white lichen
#

just use the general thing lol

steel oak
#

it's a neat feature

#

but that means .bsn syntax has to be a rust token tree

#

that's how I explain BSN to unity devs anyway, "it's a readable scene format so you can edit merge conflicts, and also you can just copy and paste it into your rust files to spawn the objects if you don't want to go to the trouble of making a scene file"

topaz ginkgo
steel oak
#

yeah

topaz ginkgo
#

I think we should be consistent here though, so #”Brush” and #”Brush Group” should always be resolved

steel oak
#

if you say "you can take your scene files and copy and paste them into your code and it just works" they get it

topaz ginkgo
#

This makes sense why we want the editor to modify BSN directly

steel oak
#

yeah, it's like, in Unity the "manual" way to construct GameObjects is with Instantiate(), AddComponent<Foo>(), etc. which is different from the way they're specified in the editor. Bevy just says, what if they were the same

#

I mean, you can construct them "manually" in Bevy too, but it does have the inline bsn! { ... } feature which I think is cool

topaz ginkgo
#

FYI i have a working implementation of modifying the in-memory BSN in jackdaw, rather than Component-based. I’ll commit and push my branch shortly (there are other changes/refactors as well, so it’s kind of polluted)

#

But hopefully once carts bsn PR lands, and your dynamic bsn PR i can just pin jackdaw to mainline bevy

topaz ginkgo
topaz ginkgo
#

also @split harness has there been discussions about shared assets in bsn? sorry if i'm out of the loop on this. JSN currently supports this, and am happy to port to BSN. we have a separate "assets" section in JSN where all the shared assets of entities have access to

topaz ginkgo
#

ok apologies for the spam, i'll shut up - but just to be clear, i have the following 3 changes in flight to get jackdaw fully onto bsn:

  • Finish off DynamicBsnLoader impl for List and Tuple support on @steel oak's dynamic-bsn branch
  • World -> bsn scene writer (DynamicBsnWriter) - bsn-scene-writer branched off dynamic-bsn
  • Asset catalog support (BsnAssetCatalogLoader) - bsn-asset-catalog branch off dynamic-bsn

i've done it this way so i can have individual PRs raised as well - this should then get us in a good state in jackdaw to be fully reading/writing BSN.

i should highlight locally the world -> bsn serializer, and asset catalog is encompassed within jackdaw while i test, but i'm extracting these out to be upstreamed

split harness
#

I personally don't like #{Brush Group} either, as we'll want that syntax to pass in a #{name? variable

#

So actually neither of those are options πŸ™‚

#

I think it either needs to be #"Brush Group" or #{"Brush Group"}

topaz ginkgo
lime veldt
#

next up #"\"foo\""

split harness
#

Name("Brush Group") is "just" the component value / does not have access to the #Name functionality

topaz ginkgo
split harness
topaz ginkgo
#

i wanted to pick your brain on an "asset catalogue" @split harness - kind of similar to unreal's .uasset. in JD currently we have a shared assets file (in JSN) that allows other scenes to reference without instantiating each time (so a load asset once, and reuse). I've made a similar implementation for .bsn in my bevy fork but requires some changes to the LoadContext in bevy_asset such that we will automatically generate an assets.bsn which looks like this:

Children [
  #ground06
  StandardMaterial { perceptual_roughness: 0.8 },
  #rock058
  StandardMaterial { metallic: 0.1 }
]

which we can then access by reference in any scene.bsn:

Brush { faces: [ BrushFaceData { material: "assets.bsn#ground06" } ] }

so in essence, the changes i've made are scene loads bsn -> asset_server loads the assets.bsn -> our DynamicBsnLoader parses it and registers it as a sub-asset via reflect -> and the Handle resolves automatically through the labeled assets system.

curious if you think this would be a good start, as this will almost immediately allow jackdaw to save/load textures and materials with bsn?

slender lion
split harness
# tawdry owl If it's about the error message, constraining on a `Sized` supertrait with `on_u...

I tried the supertrait approach:

#[diagnostic::on_unimplemented(message = "Helpful message here", label = "label", note = "note")]
pub trait SpecializeTemplate: Sized {}

impl<T: Sized> SpecializeTemplate for T {}
impl<T: Asset> Unpin for Handle<T> where for<'dummy> [()]: SpecializeTemplate {}

(and in the FromTemplate derive)

This does still work from a "specialization hack" perspective. But I'm not getting improved diagnostics:

#[derive(Component, FromTemplate)]
struct Foo {
    image: Option<Handle<Image>>,
}
#

"Helpful message here" is never printed anywhere, and the same arcane the size for values of type [()] cannot be known at compilation time message is printed

tawdry owl
split harness
# topaz ginkgo i wanted to pick your brain on an "asset catalogue" <@153249376947535872> - kind...

Is StandardMaterial a component here? Because if not, and it does a runtime "is this an asset type" check, I think this is too hacky (and cannot be supported in the bsn! macro).

My current plan for this category of thing is:

  1. Land Assets as Entities
  2. Add the concept of a "shared entity" to BSN, which is spawned once and shared between instances
  3. Add the ability to retrieve handles to shared entities (this probably directly intersects with both (1) and (2).
  4. Profit
fallow cloak
# split harness "Helpful message here" is never printed anywhere, and the same arcane `the size ...

Do you specifically need the Sized supertrait bound, or to use [()]? I think you could put any dummy struct/trait there as long as the dummy struct doesn't impl the trait

pub struct Dummy;

#[diagnostic::on_unimplemented(message = "Helpful message here", label = "label", note = "note")]
pub trait SpecializeTemplate {}

impl<T: Asset> Unpin for Handle<T> where for<'dummy> Dummy: SpecializeTemplate {}
split harness
#

(2) is also an important piece for things like "observers shared between instances", once "observers as relationships" lands

split harness
#

We're back to "maybe the UX here is viable"

split harness
#

Leaving only "this is a built on a hack that the rust team might close at any time"

thick slate
split harness
#

But honestly, if it gets us through a single release cycle we still come out on top. Because by then we'll be able to push more aggressively toward BSN and perhaps have an "always strong handles" story in place

#

I'm leaning toward doing it

tawdry owl
queen oak
topaz ginkgo
split harness
leaden dew
#

if the supertrait works then problem solved anyway!

split harness
#

I'll try it one more time just in case πŸ™‚

topaz ginkgo
split harness
#

If you're entering that world / have questions sync up with @raw mesa

raw mesa
#

I'm unsure how nicely something like "spawning assets as bsn" will work tbh. We'd need like a Template that internally stores a Scene, and then we need to provide some way to tell the asset system "give me a handle for this asset entity, I promise this is the only handle". Quite gross

#

But maybe I'm thinking too small haha

split harness
#

/ after I review assets-as-entities v0 so I can take that into account

raw mesa
split harness
#

(dont have time now sadly)

raw mesa
#

Yeah bsn takes priority for sure haha

split harness
#

@thick slate I've resolved the outstanding issues / have hopefully just fixed CI. Ready for your final approval!

thick slate
split harness
#

I do have plans to more fully specify the BSN featureset in the Scene docs, but I'd like to do that during the RC cycle

split harness
#

Was hoping I'd get the cargo_features.md updated before you noticed πŸ™‚

split harness
#

Lol I was still lazily spawning scenes in Update instaed of SpawnScene

#

@thick slate ok now should be greeeeen green

#

🀞

thick slate
#

@split harness approved with notes on follow-ups. Feel free to merge

#

Do uh let us know when you're going to do the big renames

#

So then fix-ups don't get clobbered and we can get them through quickly

white lichen
#

(pr doesnt look approved to me :p)

topaz ginkgo
split harness
#

referencing "inline" assets defined in BSN is definitely intended to look like that, from the consumption side

split harness
#

@rare whale have you done any feathers BSN porting? I'll pick that up now if not

split harness
split harness
summer cove
#

I am a very different person from who I used to be when the first ideas for BSN were brought up

dapper sky
#

Such a huge milestone!

lime veldt
split harness
lime veldt
#

because impl Bundle -> impl Scene would cause much trouble at calling sites?

#

when migrating

forest shuttle
#

About formatting support within bsn, is that something that could have its own goal?

elfin surge
lime veldt
#

neat, where do you see that?

elfin surge
#

just adding up the sizes before and after the pr

#

annoying to do, but I was curious

near warren
dapper sky
#

That's the intention, and also pcwalton has been working on the bsn asset format for his own needs and idk how much that'll be able to be rolled up into a 3rd party crate while we wait.

urban galleon
#

I have been spending 2 months migrating away from a framework and hopefully will be done end of next week.

#

So if I have to do a bsn migration upfront that would be a big blow for me.

rare whale
#

There will be some migration work if you are keeping the old widget functions, the names have changed and you may also need to ensure you have the right default font.

somber rivet
summer cove
#

LMAO

rare whale
topaz ginkgo
copper dragon
steel oak
#

I don't have that much time for Bevy over the next few days but I plan to clean up and submit the dynamic BSN (a.k.a., BSN assets, .bsn) PR shortly after that

#

This will be preliminary, not a complete implementation of .bsn as specced, but should be a compatible subset.

#

I think it's best to implement it piecemeal as that simplifies review, etc

rigid adder
#

Do we still need the default helper now that we have BSN?
I remember the main motivation was to improve the ergonomics of the struct update syntax, which was heavily used. But with BSN, users no longer need to type it at all.
It always felt awkward to me since RA suggests Default::default() when you type ...
Even in the BSN PR, I saw Cart using ..Default::default() in benchmarks.

slender lion
magic belfry
#

personally I'm not going to use BSN until the DX isn't as awful (i.e. formatter, more robust autocomplete, less {} spam for just specifying components or values)

#

so I'd definitely wait

slender lion
#

the lack of autoformatting and autocomplete is definitely going to be rough in the near term

#

plus the internal APIs haven't "switched over" to bsn yet either, so "component-based" is still going to be used quite a bit

magic belfry
#

I did try migrating some examples in Avian and gave up :p it was just a pain and made things uglier since I don't even benefit from things like inheritance there

#

except maybe for some UI things, which I don't have that much of

slender lion
#

I'm committed to living with that pain personally (makes sense for pedagogy/videos/etc), but wouldn't expect anyone else to make that choice

magic belfry
#

but not having to pass around the asset server etc. will be nice

coarse patrol
somber rivet
#

I have gotten married and had a child since BSN started πŸ™ƒ

steel oak
#

We need dynamic BSN to migrate off glXF, but once we do I plan to yeet glXF

steel oak
#

so we'll be an early heavy dogfooder of BSN

magic belfry
#

yeah I'm talking about the macro, not dynamic BSN

steel oak
#

I plan on using the macro too, but we'll see

#

I'm all in on BSN

dapper sky
#

I have complex UI that needs to be made maintainable, I'm moving onto it the moment the rc drops πŸ˜…

magic belfry
#

definitely very happy to see your work on dynamic BSN :)

viscid cradle
#

bsn merging feels unreal at this point

magic belfry
#

has been cool following along and seeing the progress there

viscid cradle
#

kinda like the editor

dapper sky
steel oak
#

Once dynamic BSN lands we may go from "no editor" to "there is an editor" surprisingly fast

dapper sky
#

yeah it's relatively easy to design build an editor once the thing that's getting edited has been tightly defined.

#

~ relatively easy ~

steel oak
#

indeed jackdaw has been making tons of progress

white lichen
#

Joe is a beast

viscid cradle
#

accelerationism in bevy

#

like the ai bros say, we are reaching the bsn singularity soon

#

or watever they say

steel oak
#

it will take some time for people's workflows to catch up

#

and there will need to be a lot of experimentation in the brave new BSN world

dapper sky
tribal yacht
#

the france

#

yes quite

white lichen
#

2025 is a bit outdated

dapper sky
dapper sky
tribal yacht
#

I couldn't tell πŸ˜‰

steel oak
#

for example, I have no idea how artists and designers are going to start using the editor in practice at my studio

#

we'll have to figure it out!

white lichen
#

a mwriter at mheart

tribal yacht
#

I only write fiction, seems much easier

viscid cradle
tribal yacht
#

that's just a lie, there's a difference

viscid cradle
#

aren't all fictions lie ?

dapper sky
#

if I had fewer responsibilities I'd be making editors for many silly little things like a macromedia flash clone.

tribal yacht
topaz ginkgo
#

i will say, leaning into bsn has been a great side quest for jackdaw πŸ™‚

#

i can even serialize physics components via avian into .bsn files pretty seamlessly @magic belfry

urban galleon
wooden vine
#

Question:
I'm trying to make a UI constructor thing for a list-like widget.
I need to take a list of N user widgets and create a structure like so:

A
 -child-> Header
      -child-> Button
 -child-> Container
      -children-> the N widgets
and
A
 -subwidget-> the N widgets
#

subwidget is another relationship

#

Bundles afaik cannot express this, how could this be done in BSN?

#

I can't seem to figure it out

native mesa
wooden vine
#

oh! I didn't have that on my channel list

#

I moved there.

queen oak
#

should this working group be closed and maybe a new one opened focusing specifically on the serialized format?
going off of how working groups we're originally supposed to work they were supposed to be explicitly scoped, and we do have a scenes-dev channel

cobalt stone
#

Agree @split harness ^

buoyant venture
rigid adder
#

Do you think hot reloading should use the serialized format, or is it something separate and better be handled by subsecond?

magic belfry
#

probably at least BSN assets and fixing the worst DX problems (formatter pleasepleaseplease)

#

but stuff like reactivity is also listed in the PR but should be its own thing imo

native mesa
#

i don't think we can close this yet

#

there's no actual syntax docs in repo that i can find

#

there's still remaining work to make this something that you can just hand to users.

#

i think we can keep this open, but scoped as "fixing and finishing the current impl" rather than "extending it"

split harness
buoyant venture
topaz ginkgo
rare whale
slender lion
thick slate
thick slate
tribal yacht
#

A winter wonderland would honestly be a good theme

thick slate
thick slate
#

Currently sorting out some nasty merge conflicts πŸ˜…

#

Pushing shortly

rare whale
#

@thick slate OK, so the issue is that CustomCursor is used by both CursorIcon and EntityCursor, both of which are enums that have defaults. In other words, it's an enum inside of an enum variant, where the outer enum has a default but the inner one does not:

pub enum CursorIcon {
    #[cfg(feature = "custom_cursor")]
    /// Custom cursor image.
    Custom(CustomCursor),
    /// System provided cursor icon.
    System(SystemCursorIcon),
}
#

Adding a default to CustomCursor would be a hack, since you'd either have to provide a fake image or a fake URL.

thick slate
#

Putting this down for now; I don't understand this well enough to try a more involved fix

rare whale
thick slate
rare whale
thick slate
#

This fixes the compilation errors, but I have low confidence that this is the right fix

rare whale
thick slate
rare whale
thick slate
#

+40/-3 lol

#

A little more manageable!

rare whale
#

Maybe add a TODO to that effect

thick slate
rare whale
# thick slate I'm not sure what you mean here. What do you think the correct behavior should b...

First, I'm not sure that it makes sense to ever use CursorIcon in bsn, unless you are using bsn to spawn your game's window entity, which seems odd. For individual entities, EntityCursor is the right component to use, and it already has a default - the default system cursor. It makes no sense to insert an EntityCursor with a default custom cursor. But bsn insists on having every enum variant be defaultable, which is stricter than Rust's normal rules for defaulting.

#

That is, when I impl Default on an enum, only the default variant needs to be default-constructable - not every variant. But bsn is apparently more strict than this.

thick slate
rare whale
thick slate
#

But I don't want to be too presumptous with @split harness's branch while he's off skiiing πŸ™‚ The earlier fixes were very uncontroversial (thanks reviewers!)

#

(if this was my branch I'd be full πŸš€)

steel oak
#

Working on rebasing my dynamic BSN branch. What did GetTemplate turn into?

steel oak
thick slate
topaz ginkgo
#

I will also have a couple follow ups that I’ve implemented off this branch such as asset cataloging and scene serialization as well

tawdry owl
steel oak
#

and even if it were, you wouldn't want to pull in so many rust compiler guts into your game

#

Maybe it is. I've just never heard of anyone using syn for anything other than parsing Rust code

tawdry owl
steel oak
#

But also like, what's the point? The macro produces Rust code, but that's not what you want in the dynamic setting

tawdry owl
#

The main reason to not use syn for parsing is because it's limited to valid rust tokens, but this is not a problem in this case

tawdry owl
steel oak
#

You could have a BSN AST shared between the two but I assume that @split harness didn't write it that way for a reason

#

Maybe that was not a good assumption, in any case cart should weigh in

#

(no rush of course)

steel oak
#

Because I didn't love having to write a lexer in nom

#

Ok yeah, I think you can use syn as a lexer

#

Looking at it closer, the only reason we couldn't use BsnEntry is that BsnEntry is a singly linked data structure with no way to refer to intermediate nodes, which is bad for editors which want to maintain bidirectional links between BSN AST nodes and the objects. We would probably need to ECS-ify it if we want to share data structures.

tawdry owl
steel oak
#

I'm going to wait until cart weighs in on the AST question before doing more work on dynamic BSN

split harness
# queen oak should this working group be closed and maybe a new one opened focusing specific...

We haven't finished the serialized asset format, which was a major part of the formation criteria and is a key piece of the puzzle. I think there is still value in keeping the working group open for a bit longer. I don't think we're ready to close out the Goal yet either, for that reason. I think the formatter, new features, UX improvements, etc would not justify keeping the Working Group / Goal open.

  • @cobalt stone @magic belfry
#

But also we should do whatever serves us best.

#

If anyone feels a particular kind of way let me know

topaz ginkgo
split harness
split harness
topaz ginkgo
split harness
rare whale
lime veldt
#

default url is 127.0.0.1

split harness
# steel oak You could have a BSN AST shared between the two but I assume that <@153249376947...

Haha we've talked about this before. You asked essentially the same question and I pushed you in the direction of a separate parser. My rationale was that they have different featuresets (ex: bsn! supports arbitrary Rust code in {} and in some value positions, .bsn uses versioned imports driven by reflection data, etc) . Sharing implementations introduces new error pathways (ex: accidentally pretending to support something). Our error messages will also need to be different for each of these contexts.

On top of that, bsn! is built to be a "permissive" parser to support autocomplete, where .bsn should be stricter in the interest of ensuring everything is well formed.

@tawdry owl

#

The previous chat: #1264881140007702558 message

#

@steel oak's point about not shipping syn in binaries is also extremely valid

topaz ginkgo
split harness
#

And we could almost certainly make ours faster

tawdry owl
rare whale
split harness
tawdry owl
#

It also means that dynamic bsn isn't strictly a subset of macro bsn, which I thought was the goal, although maybe I'm mistaken here?

split harness
# tawdry owl It also means that dynamic bsn isn't strictly a subset of macro bsn, which I tho...

I think full / strict copy-paste-ability should be a nongoal for the following reasons:

  1. .bsn files will use versioned imports, in the style of something like use bevy{0.19}::prelude::*. bsn! cannot / should not support this as versioning is nonsensical in that context. Supporting it and just dropping the version number seems wrong to me.
  2. .bsn will use the reflected type paths, which serve as a canonical name for the type. bsn! is not constrained by this, and based on the current import / crate context, the paths might not line up.

That being said, for .bsn files using short type names with versioned imports, I think it is useful / cool to support copy-pasting everything but the imports

steel oak
#

OK, cool, so I don't have to rewrite anything πŸ™‚

dusk aurora
#

I think with both of these features (errors, fmt) I would start using bsn personally, so it would be cool if we could get this out in some shape or form

magic belfry
#

would love getting these in 0.19 as experimental (or otherwise) to make bsn! actually usable and give people a better first impression

#

as it stands, I wouldn't use BSN or recommend others to use it much, but with these I'd be way more comfortable using it

dusk aurora
#

I also added tests to test formatted diffs, but that part is honestly not super great imo yet.

It could be much more granular/atomic tests but it catches most things and is close to the example so I stuck to it.
I also played around with more bsn mock data and more tests but ultimately decided to go with more of an integration test route first.

split harness
dusk aurora
#

definitely an area that will need reviewing

#

I can add instructions for vscode and I'm hoping enough people use it that we can find issues like that in time.

magic belfry
#

I feel like it's also important to get it in peoples' hands to really test it

dusk aurora
#

This did happen to me earlier already so I made sure to double check for this

buoyant venture
dusk aurora
#

Yea I wrote some notes about that

#

I do think so but it should be its own crate

#

like bevy_lint

buoyant venture
#

Ah, right, I should have read the PR description first πŸ˜…

dusk aurora
#

otherwise it force-breaks editor plugins of users

#

or at least if you ship it as a standalone bevy_fmt (cargo fmt + bsn fmt)

#

so that coupling should not be forced

steel oak
dusk aurora
#

Yea to be fair I was like hard focussing on these 2 things for 4 days now

#

and I was quite deep already

#

thematically

buoyant venture
steel oak
#

We are the #2 OSS game engine on github after all (iirc)

dusk aurora
#

Also the design itself did work out rather well in the end

#

the only thing is syn + comments

#

rn

#

that I can't get done (it is doable but yea...)

buoyant venture
rare whale
steel oak
#

One nice thing about using nom and lalrpop for the asset format as opposed to having a hand-coded parser is that it means we have a declarative official definition of the BSN grammar right there in the code

#

So if we wanted to build a spec it'd be easy to

#

And if you want to write tooling targeting BSN (which my studio very much does) then you can just open the LALRPOP file and the grammar is right there

split harness
amber pebble
#

But I guess for the editor integration there is also value in doing the version detection directly in the format binary and have it also be usable across versions

thick slate
#

Started on crate docs today; should have a PR up tomorrow

cobalt stone
#

for BSN?

native mesa
#

Don’t forget to add syntax docs for the DSL!

#

Actually we might want to consider writing a spec at some point

#

No one likes a language thats defined by a single canonical implementation… rust side

thick slate
#

Crate-level docs only for now

split harness
#

Taking a look at the Feathers port stuff yall did while I was away

tawdry owl
#

To add a bit to the tooling conversation, another bit of tooling that would make bsn nicer to use is an LSP that would suggest only components in the bsn! macro (and dynamic bsn), preferring required components first. This would improve discoverability of required components and make it on par with what we had with bundles.

magic belfry
#

a lot of required components are often primarily internal components that are not meant to be added or edited by users manually, so it's not really the best heuristic for that imo

#

ex: any "computed" component (InheritedVisibility, ViewVisibility, ComputedNode, etc. in Bevy, ComputedMass, ComputedAngularInertia, ComputedCenterOfMass in Avian)

#

or various ID components or required marker components

tawdry owl
#

Yeah, computed and marker components should probably be their own category, at least docs and completion-wise

#

Which should also probably help world->bsn serialization? Computed can be ignored when writing back, although I'm not sure if the complexity of tagging component types would be worth it.

thick slate
lime veldt
#

finally I get to learn what this is all about instead of pretending

#

kept my fresh eyes sealed for a proper review

thick slate
thick slate
rare whale
#

@split harness @thick slate Now that I have read the docs, I think that FromTemplate is misnamed - it inverts the normal meaning of Rust's From / Into pattern. I suggest one of the following:

  • IntoTemplate
  • UseTemplate or UsesTemplate
  • HasTemplate
  • TemplatedBy
thick slate
#

But no strong feelings here from me

rare whale
split harness
# rare whale <@153249376947535872> <@159873981174906880> Now that I have read the docs, I thi...

FromTemplate is "correct" in my mind.

The From trait is From<Y> for X
If you could derive that , it would be #[derive(From<Y>)] struct X;, which can be read as "we can create X from Y"
Translating to templates, this would be #[derive(From<SpriteTemplate>)] struct Sprite , which can be read as, "we can create Sprite from a SpriteTemplate"
We express this as the FromTemplate trait, which when implemented for Sprite reads as "we can create Sprite from a Template", where the Template in question is defined by FromTemplate::Template, which is SpriteTemplate.

rare whale
steel oak
#

Here's a fun little puzzle: The AssetPath -> HandleTemplate is currently driven by a From impl. We need that to be reflectable in asset BSN, and there is no ReflectFrom. ReflectFrom is a little weird too because it has a type parameter, unlike other Reflectable traits. You can't just #[reflect(From)] because the derive macro wouldn't know from what. And even specifying a single type to convert from is insufficient for the case of HandleTemplate because it's a blanket impl<I: Into<AssetPath<'static>>, T: Asset> From<I> for HandleTemplate<T> .
Anyone have any ideas as to how to solve this?
One possibility would be to have something like type_registration.register_conversion<TFrom, TTo>() where TTo: From<TFrom> and register all the transitive conversions between asset paths and handle templates manually.

split harness
split harness
#

I don't think theres a good way to try to bring the whole Rust type/trait conversion behavior / path into Bevy Reflect. I'd prefer to take the dumb/easy path, at the cost of some additional manual wiring

#

Once we fill out the initial conversions for core types, I don't think the difference will be noticeable in the vast majority of cases

split harness
#

Happy to discuss though. This won't be the last time we see this pattern

thick slate
split harness
rare whale
thick slate
split harness
#

I've resolved feedback on the Feathers Port / fixed up CI

#

Triggered a merge

#

(working late tonight because I spent some time playing slay the spire 2 with my brother during work hours 😈 )

split harness
#

@dusk aurora wow the improved error handling is also great. bravo

dusk aurora
#

just for additional context

#

or just when something else unrecoverable happens

silver bramble
#

Is this type of implementation currently not supported in bsn? I saw that there is a specialization trick, but not sure how it works or if it could be applied here.

If not, thats fine, I know bsn is still in the early phase, just wanted to know if I missed something.

#[derive(Clone, VariantDefaults)]
pub enum Primitive {
    Sphere {
        radius: f32,
        color: Color,
    },
    Circle {
        radius: f32,
        color: Color,
    }
}


impl Template for Primitive {
    type Output = (Mesh3d, MeshMaterial3d<StandardMaterial>);

    fn build_template(&self, context: &mut TemplateContext) -> Result<Self::Output> {
        let mut meshes = context.resource_mut::<Assets<Mesh>>();
        let mut materials = context.resource_mut::<Assets<StandardMaterial>>();


        let mesh = match self {
            Primitive::Sphere { radius, .. } => {meshes.add(Sphere::new(radius))}
            Primitive::Circle { radius, .. } => {meshes.add(Circle::new(radius))}
        };

        let color = match self {
            Primitive::Sphere { color, .. } => {color}
            Primitive::Circle { color, .. } => {color}
        };

        let material = materials.add(StandardMaterial {
            base_color: *color,
            ..default()
        });


        Ok((Mesh3d(mesh), MeshMaterial3d(material)))
    }

    fn clone_template(&self) -> Self {
        match self {
            Primitive::Sphere { radius, color } => {
                Primitive::Sphere {radius: radius.clone(), color: color.clone()}
            }
            Primitive::Circle { radius, color } => {
                Primitive::Circle {radius: radius.clone(), color: color.clone()}
            }
        }
    }
}
rare whale
#

@split harness How do I convert an EntityReference to an Entity? I need to do this:

Scrollbar {
    orientation: ControlOrientation::Vertical,
    target: #inner,
    min_thumb_length: 6.0,
}

Where target is an Entity.

#

Similarly, I can't pass a name as a parameter to a template:

(
    scrollbar(#inner, ControlOrientation::Vertical)
    Node {
        position_type: PositionType::Absolute,
        right: px(0),
        top: px(0),
        bottom: px(0),
        width: px(6),
    }
),

This gives me an error, "expected one of ! or [, found inner"

split harness
split harness
split harness
#

But that is essentially the same case as field: #Name, which currently supports #Name syntax but not arbitrary inline Rust expressions (just values). We want to support both in both cases, and solving one should solve the other

#

I'm also of course very interested in "#Name in closures", which will require a completely different strategy (templates that take entity references and return closures with the resolved entity)

round totem
#

Can attribute or derive macros be expressed inside a bsn! invocation? (I'm guessing not)

#

(Just wanting to make sure)

rare whale
#

@split harness I'm calling parent.queue_spawn_related_scenes::<Children>(bsn_list!(...etc...) and nothing is showing up, what could be wrong?

split harness
round totem
#

I'm moreso trying to think of a syntax for bsn files that denote metadata

split harness
round totem
#

Pcwalton's pull has a simple bsn file in it with none of that Metadata and it got me wondering how we could do that

split harness
split harness
#

(which aren't loaded)

rare whale
#

You can repro by running the feathers example and clicking on the menu button

round totem
#

Im moreso thinking of a way to let the parser know what "version" of bsn syntax its using. Sort of like how you specify in Cargo.toml the Rust version the rustc compiler should parse

#

Or how CMakeLists.txt specifies its version syntax (but hopefully less gross)

split harness
round totem
#

It doesn't need to be fleshed out but I think we'd be better off at least reserving a syntax for meta specifications in bsn files

#

Since bsn is rust-ish inspired I wonder if we could reserve something like #![] for setting bsn meta attributes

split harness
#

Although thats admittedly a bit of a hack

#

My problem with reserving syntax is that it requires doing design for something that we haven't built yet / committed to build.

round totem
#

#![bevy_version(0.19)]

split harness
rare whale
#

@split harness I also want to talk a bit about the menubutton API. The main idea here is that when the user clicks the button, a signal is sent to the app to spawn the menu items (because we don't want to have a bunch of hidden menu items hanging around when the menu popup is not visible). This could be done either as a callback, or as an observer. Neither is ideal:

  • Callback
    • This would look like: menu(|parent| { parent.queue_spawn_related... })
    • Not possible to use this in asset bsn
  • Observer
    • This would listen for a MenuOpen event, and would be declared using the normal on syntax
    • I can't pass an EntityCommands in an Event, so the observer has to have a lot more access to the internals of the menu system - it means that the observer has to do additional housekeeping and boilerplate
round totem
#

(And whatever goes in the bevy_version() attribute would be any legal semver string)

white lichen
split harness
#

Other than the syntax

#

Not opposed to that syntax

white lichen
#

we had pretty much this exact sequence of messages a year or two ago lol

#

you propose {}, i suggest @, you agree

round totem
#

Other custom syntaxes could be enabled with #![] like in nightly rust

split harness
split harness
white lichen
#

lol

#

almost exactly 2 years ago

round totem
#

I like the #![] idea because it'll allow users to experiment with adding custom stuff to the syntax and enable them like in nightly rust

#

Like nightly experimental features in Rust

#

#![bsn_feature(foo)]

#

If/when bsn's syntax gets expanded and experimented on in the future this could be a boon

white lichen
#

i dont see why this should be combined with bevy versioning

round totem
#

And then do #![] for toggling experimental syntax that isn't part of bevy core yet

split harness
white lichen
#

i dont see why feature gating experimental stuff is related to versioning

#

i was talking about the #![bsn_feature(foo)] proposal

#

i dont see how thats related to bevy versioning

round totem
#

Versioning and experimental features are both meta attributes to the parser

split harness
#

Its relevant because [email protected]::prelude::* is about bringing in Bevy 0.19 types, which could be done in the context of any BSN format version

#

And defining the BSN syntax format shouldn't necessarily involve importing bevy types

white lichen
#

i would think bsn format versions are more like "Is Children shorthand supported" type things

split harness
#

Yup

white lichen
#

and the specific bevy version types involved are not really bsn related

split harness
#

Yup sounds like we're on the same page

white lichen
#

cool

split harness
#

So something like #![bsn_version(1.2)] is "necessary" in that context

white lichen
#

right, but thats got no bearing on which bevy types are available per se

split harness
#

However I kind of like the idea that [email protected]::prelude::* can imply a given bsn version, when none is specified, in the interest of terseness

white lichen
#

maybe we want to call them bsn editions, not versions? like rust editions

#

not sure if thats semantically closer

#

they're language changes, not crate changes if that makes sense

split harness
white lichen
#

like, in this analogy bevy is the stdlib, and adding stuff to is like stablizing stuff. adding if let matches is a language change and wont be accepted by previous editions

split harness
white lichen
#

hrmm yeah

#

maybe its not a worthwhile analogy

split harness
#

I'm personally tabling this conversation for later πŸ™‚

#

Feel free to come up with thoughts / specs

white lichen
#

~~i'll suggest @ again in 2 years when it comes up again ~~

split harness
#

(respond there plz)

split harness
split harness
split harness
#

@rare whale hehe removing Visibility::Hidden seems to do the trick πŸ™‚

split harness
# rare whale <@153249376947535872> I also want to talk a bit about the menubutton API. The ma...

Rather than spawn the menu items lazily when clicked, why not just make this structural / hidden by default? Ex:

(
    menu()
    Children [
        (
            :menu_button(MenuButtonProps::default())
            Children [
                (Text("Menu") ThemedText)
            ]
        ),
        (
            :menu_popup
            Children [(
                :menu_item
                on(|_: On<Activate>| {
                    info!("Menu item clicked!");
                })
                Children [
                    (Text("MenuItem") ThemedText)
                ]
            )]
        )
    ]
)

That allows the menu items to have their dependencies propagated up to the root if they have them. It also lets things like entity references work across boundaries, and improves legibility in general

#

It also ensures all of the items are "ready to go", which might improve responsiveness

thick slate
#

@split harness do you want to try and land the formatter for 0.19? If so, I'll review @dusk aurora's https://github.com/bevyengine/bevy/pull/23586 tomorrow. Otherwise, plenty of misc release prep stuff for me to do.

GitHub

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

dusk aurora
#

not sure if it can be merged tomorrow though

thick slate
split harness
dusk aurora
#

yea you can totally create the binary locally

split harness
#

I'm working on the bevy_scene rename atm

thick slate
thick slate
#

I think we're good now that the macro code quality PR is in

split harness
rare whale
rare whale
# split harness Rather than spawn the menu items lazily when clicked, why not just make this str...

The reason I don't like this is because (a) some menus are quite large (e.g. the font selector in inkscape has a menu item for every font), and (b) an editor might have a large number of menu buttons. Also (c) some menu items might incur an overhead even when invisible, for example if their label is dynamically computed. Because of this, I prefer menus, dialogs, and other dynamic content to not exist rather than be hidden.

split harness
# rare whale The reason I don't like this is because (a) some menus are quite large (e.g. the...

Riiight I remember this conversation. From my perspective, the cost:benefit plays out in favor of "always spawned menus" in the average case, which is a small number of menu items that are just some text and (perhaps) some icons.

Pros of laziness:

  • very expensive menus that have expenses when they are invisible don't incur overhead
  • we get some "dynamicness" for free, as we can automatically re-compute a new menu each time it is shown
  • we don't wait to prepare the dependencies of menus when spawning the rest of the scene. for expensive menus with many asset dependencies (icons, fonts, inherited scenes), time to first render is faster.

Cons of laziness:

  • we don't get "free" preloads of assets which are defined as "required scene dependencies" (ex: inherited scenes, fonts, image icons, etc). Everything is loaded lazily on click, meaning skipped frames and/or pop-in. For "big" assets,/scene dependencies this could be many frames.
  • we need to re-compute on every click, which is more expensive than just hiding the root of the menu.
  • in most cases, we don't do any rendering or processing of hidden nodes, so the overhead should be extremely minimal. The "don't do work for hidden items" case is generally very easy to optimize.
  • developers can't directly define their menus inside the rest of the BSN scene. They need to define a closure that is passed as an argument, which then calls a command, which then defines a macro with the scene contents.
  • developers can't use Entity references (ex: #Name syntax) to connect menu items to other items in the scene
split harness
rare whale
#

I agree that for small use cases, being able to define the items inline is very convenient

split harness
#

I think for most things, hiding but skipping expensive work is the move

#

Ex: we don't really want to be recreating state each time we click between tabs

#

Much better to flip a switch that says "you aren't focused right now" and make it the responsibility of the UI internals (and custom logic) to ensure it doesn't do work in that case

#

The thing with "lazy menu icon loading" vs "immediate / blocking menu icon loading (aka part of a loading screen)" is that we already plan to support that distinction via HandleTemplate

#

If we do "always spawned menus", the developer will have both options

#

If we do "lazy menus", the developer is forced to be lazy, no matter what the configuration of the HandleTemplate is

split harness
#

But I will assert that this is generally preferable to someone clicking a menu and icons popping in randomly (or not at all)

#

(because the load kicks off right when the click happens)

exotic pecan
#

Not all of it, mind you

#

(I’m not using BSN yet, so, mostly focusing on the points related to asset usage here)

rare whale
#

@split harness I'm still having trouble figuring out how to make a clean API for scrollbars in BSN. The basic structure is that there's a Scrollbar component in the headless widgets that includes an entity id for the region being scrolled - that is, an entity whose ScrollPosition is kept in sync with the state of the scrollbar. Then feathers wraps this in a scrollbar scene function which adds a thumb child and a bunch of styling components.

Earlier you suggested implementing FromTemplate for Scrollbar but that doesn't really help because users aren't calling that directly; they are calling the feathers wrapper. I mean, I could remove Scrollbar from the wrapper and have the users call it directly, but that's a really ugly API - hi, here's a function which outputs all the components needed for scrollbars except for the actual Scrollbar component.

So we need to solve two problems: first, the user needs to be able to pass in the entity id to the wrapper; and second, inside the wrapper, we need to be able to take the passed in parameter and put it in the component. It's unclear (to me) whether that parameter needs to be an Entity or an EntityReference - that is, does it get resolved by the caller or the callee?

Note that if we had struct template support, we could work around the first since we could make scrollbar a struct instead of a function. That doesn't help with the second problem though.

#

An additional problem is that I can't actually figure out how to implement Template for Scrollbar, there are only 3 examples of impls in the code and they are quite different from my use case.

silver bramble
# split harness Yup this should work, as written, with `bsn!{ @Primitive::Sphere { radius: 10 } ...

The problem I have is that if I implement default I get:

error[E0119]: conflicting implementations of trait `Template` for type `Primitive`
   --> src/main.rs:100:1
    |
100 | impl Template for Primitive {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: conflicting implementation in crate `bevy_ecs`:
            - impl<T> Template for T
              where T: Clone, T: std::default::Default, T: Unpin;

But if I don't implement it I get:

error[E0277]: the trait bound `Primitive: std::default::Default` is not satisfied
  --> src/main.rs:67:18
   |
67 |       let sphere = bsn! {
   |  __________________^
68 | |         #base
69 | |         @Primitive::Circle {
70 | |             radius: 1.0,
...  |
73 | |     };
   | |_____^ the trait `std::default::Default` is not implemented for `Primitive`

So not sure how I work around that.

dapper sky
#

I assume we don't have @ImageNode { image: "image.png" } yet? πŸ˜…

#

as an alternative to asset server everywhere

topaz ginkgo
split harness
#

Its trying to do FromTemplate on the enum variants, which is very wrong in the context of raw templates

split harness
#

But 0.19 should launch with handle templates working everywhere

#

The bevy_scene rename is such a slog. So many "scene" things :/

split harness
dapper sky
#

that and a desire for some kind of bsn_for_each :')

pseudo fern
#

Bsn gets so fun when you start doing map().collect()

rare whale
#

@split harness Another question about APIs: how should I design widgets with slots? Let's take as example the text input widget, which looks like this:

  • feathers_text_input (the frame entity)
    • left_adornments (icons such as a search magnifying glass, etc.)
    • the actual TextInput
    • right_adornments (more icons such as x to clear)
      Where both "left adornments" and "right_adornments" are bsn_list parameters
split harness
# rare whale <@153249376947535872> Another question about APIs: how should I design widgets w...

I can't get in the weeds here right now, but for things that are "init only" (ex: things that are very unlikely to need adding / removing after init), parameterizing seems fine (ex: accept a left_adornments: impl SceneList parameter, or if we land struct-style, struct FeathersTextInput<L: SceneList> { left_adornments: L }.

For things that are likely to be dynamic (ex: lists, collections, etc), until we land reactivity I think we should embrace direct BSN / structural APIs as much as possible, as abstracting over that structure will ultimately make it harder for developers to change it dynamically.

split harness
rare whale
dapper sky
split harness
topaz ginkgo
#

https://github.com/bevyengine/bevy/issues/23637 - i raised an issue with some of the pieces i added into Jackdaw in order to get us into the world of editing BSN. i'd like to open a few PR's (hopefully small and easy to review!) with a final PR that encompasses an API for an editor to use

GitHub

Summary Based on experimentation in Jackdaw (a 3D editor I created before BSN), I've found several pieces of BSN infra needed for editor tooling that would benefit the ecosystem. @cart's PR...

rare whale
rare whale
#

@split harness @crude pawn Also, we have a problem that TextFont needs to implement FromTemplate (it's currently on team Clone+Default). InheritableFont does; but that's a feathers thing, and we need for TextFont to be usable for non-feathers people.

topaz ginkgo
split harness
split harness
split harness
split harness
topaz ginkgo
split harness
split harness
thick slate
split harness
thick slate
rare whale
#

@split harness Back on the topic of "slots", I'm still struggling to come up with a workaround. Let's take MenuButton as an example. The behavior we want is:

  • Menu button requires a label, which can be either text or an icon
  • The dropdown chevron is automatically placed next to the label
    Right now the way I am doing it is an ugly hack: You call the :menu_button scene function, and then you immediately follow it with Children [ (your label) ]. Since children are appended, this means that the label and the chevron are in the wrong order, so I fix that by setting the flex direction to row_reverse.

This creates a problem if we ever want to change the hierarchical structure of the button - right now it assumes that (a) the label is a direct child of the button, which might not be the case for a future implementation, and (b) the label is always appended as the last child. If we decided that we needed an extra layer for layout purposes, there's never going tobe a Grandchildren [] relation in bsn.

#

I experimented instead with adding a label parameter to MenuButtonProps, but couldn't get that to compile.

#

Now, I also want to give users the option to replace the chevron with a custom icon (the bevy editor designs make extensive use of custom drop-down icons).

#

I wanted to add an optional icon field to MenuButtonProps, which would let users override the icon, but this creates another problem: if we declare the type of icon as Option<I> where I is a generic param bound by SceneList, it means that in the majority of cases, where you want to pass None, Rust can't infer the type of I.

#

Another approach would be to make the menu_button function take positional parameters (this lets me use impl SceneList) but then I lose the ability to have defaults, it means now you have to explicitly pass in None if you don't want a custom chevron.

split harness
split harness
#

Its an abominable hack that doesn't hold up in the context of repeated templates (ex: spawning multiple times or inheritance). But I did make this work:

/// set up a simple 3D scene
fn setup(mut commands: Commands) {
    commands.spawn_scene_list(bsn_list![
        // circular base
        (
            Mesh3d(asset_value(Circle::new(4.0)))
            MeshMaterial3d<StandardMaterial>(asset_value(Color::WHITE))
            Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2))
        ),
        // cube
        (
            Mesh3d(asset_value(Cuboid::new(1.0, 1.0, 1.0)))
            MeshMaterial3d<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
            Transform::from_xyz(0.0, 0.5, 0.0)
        ),
        // light
        (
            PointLight {
                shadow_maps_enabled: true,
            }
            Transform::from_xyz(4.0, 8.0, 4.0)
        ),
        // camera
        (
            Camera3d
            template_value(Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y))
        ),
    ]);
}
#

(a port of 3d_scene)

#

The "hack" being adding this variant: HandleTemplate::Value(Arc<Mutex<Option<T>>>)

#

Swapping the Option with an enum AssetOrHandle<A: Asset> { Asset(A), Handle(Handle<A>) } would make it work in the context of repeated templates, but thats still quite the hack

cobalt stone
#

The other thing I want is something like PartialDefault

#

Where some fields have sensible defaults, and other fields require you to fill out in the BSN

thick slate
split harness
#

Im not sure we can encode "maybe implied default maybe partial default, based on the type" in the Rust type system

#

I mean we can from a "encoding the variant" perspective. But I don't think we could then use it to drive the necessary behaivors

#

I think in the majority of cases, we should just make things work by default

#

Required fields don't feel particularly necessary in this case.

split harness
split harness
#

And is notably the behavior difference between Foo { ..Default::default() } and Foo { .. } in that proposal

#

Aka they also require different syntax

cobalt stone
rare whale
split harness
split harness
#

Definitely want those

cobalt stone
split harness
#

I feel like our own ParitalDefault impl would be dirty / make an already quite complicated typing situation in BSN even more complicated

#

I'm not sure the cost:benefit is there relative to just making our types default-friendly

#

/ default template friendly in the cases where the actual component field is not default-able

split harness
#

Although I think we'd only need to eat that check for required fields

split harness
#

Ex: label a field with #[template(required)], which uses the RequiredTemplate<T> wrapper (which is like an Option but returns a template error when it isn't set)

#

The issue there is that it doesn't give us compile-time errors, so its just a standard way to have an error at runtime

#

And in general, i think it is better for that type of thing to be handled on a case-by-case basis, for more descriptive messages / the ability to define fallback behaviors

cobalt stone
#

that seems fair to me

#

We'd maybe want #[template(default(foo))] then as well

#

In order to not rely on Default

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

Or in your case, more likely some_widget(Some(Box::new(bsn!{ Node })))

split harness
thick slate
rare whale
#

@split harness Working on trying out your latest changes. It works, but one thing I quickly ran into was that Box<dyn Scene> can't derive Default, so I have to manually implement it; also, this means that the default allocates memory:

impl Default for MenuButtonProps {
    fn default() -> Self {
        Self {
            label: Box::new(bsn_list!()),
            corners: Default::default(),
        }
    }
}

Also, assuming we can get to a place of comfort here, I'd like to revise the APIs for button, checkbox, and the other label-bearing widgets to use similar patterns - passing in the label as a parameter rather than man-handling the widget's children.

raw mesa
#

In fact I'm using that assumption in my migration of BSN to assets as entities, so if that's not true... That's a problem

#

(I need to hokey-pokey a SceneListPatch in order to spawn it, but doing that fully would mean removing the AssetData<SceneListPatch> component, using it, then inserting it back onto the entity. So instead I just make a fake dummy instance for SceneListPatch and replace the data during the hokey instead)

#

Actually I guess even if it does allocate, it's still better than the two archetype moves

rare whale
split harness
#

Making it play nice with Into would likely (eventually) yield fruit in the context of defining this in field-value-position in BSN (which has implicit into)

#

The big downside is that its another "special" type people need to think about + learn, rather than just using the natural tools Rust gives us (Box / traits)

rare whale
#

@split harness One thing I hope to get out of the review for the menu button is feedback on the DX; I expect that there will be opinions about what the API should look like. For example, menu items look like this now:

(
    :menu_button(MenuButtonProps {
        label: Box::new(bsn_list!(
            (Text("Menu") ThemedText),
        )),
        ..default()
    })
),
#

For the moment, the "optional chevron" feature is handled by having a different scene function menu_button_without_arrow. I thought about instead having a boolean in the menu button props to indicate whether to include the standard arrow, but I realized I wasn't sure what was the canonical way to do conditional logic (non-reactive) in bsn blocks.

#

To support the editor, we're going to need variants of menu_button, for example some dropdown buttons in the mocks are smaller and have the "tool button" form factor.

#

We might also need to implement a combo box, which combines the functionality of text input and dropdown menu

#

And then there's autocomplete, which in my experience is one of the more difficult widgets to implement.

split harness
# rare whale <@153249376947535872> One thing I hope to get out of the review for the menu but...

I think we should consider treating something like a "label slot" as a single bsn entity / entry rather than a bsn_list:

  1. It slims things down slightly
  2. It would allow us to resolve the label to a single Entity and perhaps expose that as a property on the MenuButton component for easier / direct access to that logical piece.

Cases that really require multiple entities can create their own nesting. I think bsn_list is best used for "collection" abstractions.

#

Ex: (2) would allow let node = nodes.get(menu_button.label)?;

rare whale
split harness
#

Or it could be a more generic button.content

#

I dont have strong opinions here

split harness
steel oak
#

At some point I'd like some feedback on my .bsn asset PR to know if it's on the right track

#

There's still some stuff it's blocked on like ReflectConvert but other than that a lot of it is just docs & polish, but I don't want to spend time polishing something if there are concerns about the approach

rare whale
#

@split harness So, it looks like there's no way to have a conditional child without boxing being involved at some level.

lime veldt
#

Because it would return different types at the branching? Can't do bsn something alike the SpawnIter<core::option::IntoIter> thing one can do at bundle level?

rare whale
#

Even this doesn't compile:

Children [
    {props.label},
    {
        if props.arrow {
            Box::new(bsn_list!(
                Node {
                    flex_grow: 1.0,
                },
                :icon(icons::CHEVRON_DOWN),

            ))
        } else {
            Box::new(bsn_list!())
        }
    }
]
lime veldt
#

might need casting so it is Box<dyn Foo> instead of Box<Bar1> and Box<Bar2> at each branch

rare whale
#

Maybe a custom template function? I need to look at the docs again.

lime veldt
#

it is weird it does not work like you did it but it should work that way

rare whale
#

That being said, I bet you could write a ConditionalScene impl of the trait