#Entity Inspector

1116 messages · Page 2 of 2 (latest)

silver sigil
#

Yeah that does seem reasonable. I think we're largely just debating order of operations

#

(3) vs (2)

shy wadi
#

Yeah I think a level 2 editor should support both.

#

It's clear that you need (3) as some components really want a fully custom UI.

silver sigil
#

I'm going to bow out of this to wrap up bsn stuff 🙂

shy wadi
#

I was thinking about loading custom editor UIs dynamically and honestly the thing that's probably easiest by far is just leaking the old component UI when you load in a new version.

#

Yeah, it's bad, but you can just restart the editor if you start running out of memory.

#

It's very hard, practically impossible, to entirely unload a DLL

chilly grail
#

My prototype for the "reflection property grid" used a trait called Inspectable which was a thin wrapper around PartialReflect and added some additional features. This could work with components, resources, assets, or proxies to same, even non-World types, so long as they provided a way to get a PartialReflect. What Inspectable added on top of PartialReflect was change notification, so that the editor knew when to update the visible display of the field when the data changed. It also added some flags for things like "this field is actually an entry in an array, and can be re-ordered relative to its siblings".

shy wadi
#

I guess you could just the custom editor UI be part of the BRP metadata. When the editor fetches the editor UI for some component, it might include the path to a DLL that contains the custom editor UI. Then the editor can just load that DLL without recompiling.

#

You'd have to be careful around dependencies (you must reload the DLL if any of its dependencies change, not just if the DLL itself changes) but it should be possible to make it work.

chilly grail
#

So, I would like someone to explain to me the whole BRP thing. My inspector prototype just used plain old ECS queries and reflection. (I gather than the egui-based inspector does something similar.) The BRP route seemed to me an artifact of the desire to run the game as a subprocess, a vision which I wasn't interested in.

#

I'm finding it difficult to follow the conversations in this topic because I don't understand the underlying assumptions.

silver sigil
#

It also lets web browsers inspect your running game

chilly grail
# silver sigil BRP enables you to inspect a Bevy app "anywhere" (ex: a brower, an app running s...

I guess what I mean is that many months ago, when I lobbied for working on the world inspector as a kind of dress rehearsal for the bevy editor, I was envisioning something much less ambitious - basically something equivalent to the egui world inspector, but without the need to install an extra crate (most of the time when you really need to debug a problem with an inspector, you want it right now, it won't be something you planned for in advance).

#

As far as cool goes, I am well known to be a grump 🙂

shy wadi
chilly grail
shy wadi
#

Strawperson proposal.
In a library that your game links to (call it my_game):

#[derive(Template)]
#[template(editor(crate = "my_game_editor_ui"))]
pub struct MyTemplate {
    pub foo: String,
    pub bar: Handle<Mesh>,
}

The template(editor(...)) metadata gets stuffed in the TypeRegistry somewhere and is exposed via the BRP.

In another crate, called my_game_editor_ui, that you don't ship with your game but does go in your game's Cargo workspace:

export_editor_ui! {
    my_game::MyTemplate => my_template_editor,
    // ... include other types here ...
}

fn my_template_editor(world: &mut World, editor_context: &mut EditorContext) {
    // ...draw the UI here...
    // `editor_context` includes the entity of the BSN AST node currently being edited
}

Internally, export_editor_ui! turns into a function with a signature like #[no_mangle] pub fn bevy_register_editor_ui(world: &mut World) { ... add all the editors to the world TypeRegistry ... }.

When the editor slurps info from your game via the BRP, it learns about the DLL that contains all your custom editor UI (because you specified the location to it in #[template(editor(...)), and loads it with libloading, passing a reference to the world to the autogenerated bevy_register_editor_ui function. After that, all the editor UI is in the editor's AppTypeRegistry ready to be used. At no point did the editor ever need to be recompiled, and, because the editor UI is in a separate crate, it'll be stripped from your game when you release it. Nice!

chilly grail
#

Here's how I set it up in my prototype:

#
  • There's a registry of "field editor handlers", which is just a list in priority order in a resource
  • For each field being edited, we look for a handler who recognizes that field. The first one wins.
  • The handler can use any information that is exposed via PartialReflect - including type, name, and annotations - to decide whether it wants to handle that field.
  • The handler then either constructs a UI for editing that field, or passes to the next handler.
  • The lowest priority is a "vanilla types" handler that knows about the basic types: int, bool, string, and so on. The handler for f32, for example, will generate a different UI for fields that have a Range annotation than fields that don't.
  • Other handlers understand more complex types: for example color fields generate a color picker, asset handles generate an asset picker and so on.
  • In the case of asset handles, the annotation puts a constraint on the type of asset picked - so if it's meant to be a handle to a texture asset, you don't end up picking a reference to a character
  • Annotations are for the most part editor-agnostic - so for example Range can work with a slider, a numeric text input, a knob, etc. This means that the same data can be edited using a variety of different editors - egui, web-based, or native bevy_ui.
  • In addition to field editor handlers, there's also component handlers, which do the same job but at the component level, so you have custom editors for an entire component rather than just a field.
  • This includes not just tabular property grids but gizmos
  • For highly specialized data types, the data type definition and the custom editor will often live in the same github repo, although they may be separate crates
#
/// Trait that defines a factory for creating inspectors. Multiple factories can be registered,
/// and the first one that returns true will be used to create the inspector.
pub trait InspectorFactory: Sync + Send {
    /// Examine the reflect data and decide what kind of widget to create to edit the
    /// data. Can return false if the data is not in a supported format.
    fn spawn_inspector(
        &self,
        world: &mut World,
        parent: Entity,
        inspectable: Arc<Inspectable>,
    ) -> bool;
}
shy wadi
chilly grail
shy wadi
#

I guess, but I don't really see the advantage of that over storing a type -> inspector-for-that-type map, like bevy_inspector_egui does

chilly grail
#

The same type might have different inspectors based on annotation

shy wadi
#

Well, then you could have a two-level type -> subkey (including annotation, etc.) -> inspector map. I really don't like the "all inspectors ever in one giant list" approach, it just seems fundamentally inefficient to me.

In any case, the details of how the type to be inspected is associated with the inspector isn't the biggest concern for me. More important is establishing a workflow that allows for custom editors without having to recompile the editor whenever your components change and without requiring you to contort your app.

chilly grail
# shy wadi Well, then you could have a two-level type -> subkey (including annotation, etc....

The N in O(N) isn't that large because a single handler can be responsible for many types, so N isn't that large. The "vanilla" handler has a match statement for all the built-in primitive types.

As for the rest: if I have to recompile my game anyway, I don't have a problem recompiling the editor. Again, it depends on what mode I am working in. If I am in content-creation mode, neither the game nor the editor are going to be changing much. If I am working on adding engine features, then both the game and the editor are going to be constantly recompiled anyway.

shy wadi
#

Well, we clearly have some disagreement, so I'll just say that not having to recompile the editor is a very important constraint for me and leave it at that

chilly grail
# shy wadi Well, we clearly have some disagreement, so I'll just say that not having to rec...

Well, clearly you have to recompile the editor when you add new editing features. But do you need to recompile the editor when you change the game? It depends. For most components, we can use the standard property grid, which will go field-by-field using reflection, and try to build a sensible user experience for each field based on that. However, for some data types (e.g. quaternions) this produces an editing experience that's not great, at which point you really have no choice but to extend the editor to handle those data types.

#

My experience with editors spans two extremes:

  • working on a very large team where you had a lot of engineers delivering new editing features to the team daily
  • working as a solo developer on an open-world game, where I was constantly hacking the editor to improve the rate at which I could create content
shy wadi
#

TBH I don't understand the pushback to not having to recompile. Seems like a strict improvement over having to recompile the editor?

#

With libloading you can extend the editor arbitrarily and you don't have to recompile.

chilly grail
# shy wadi TBH I don't understand the pushback to not having to recompile. Seems like a str...

I'm just dubious that this can be made to work.

  • The challenge around plugin/DLL architectures is that as the editor's capabilities increase, there's a continual need to extend the plugin API
  • There's also just the general increase in complexity
  • A lot of useful editing features require gizmos which are embedded within the 3d scene itself - they are interwoven with the game's code. Examples are overlays that show navigation meshes. In a game with mutable terrain, nav meshes are not assets, they are generated at runtime, so the only way to query them is to ask the game; and you want to be able to see these in the editor as you are placing obstacles
  • Similarly, achieving maximum productivity in the editor is a game of constantly improving UI for specialized data types. For example, my game has a lot of mini-scripts that are like spreadsheet formulas; these are stored as strings but have a dedicated field editor
  • I'm not just interested in editing scenes; the "reflection property editor" widget could just as easily be used for editing any kind of reflectable struct, whether that be a material definition, an animation blend graph node, a character goal node, and so on.
shy wadi
#

I don't see the issue. The plugin API is basically "give me a &mut World and let me munge it however I want"

#

So anything you can do in a Plugin you can also do in a DLL

chilly grail
shy wadi
#

OK, and whatever that something is, it exists in the World somewhere, and can be modified by plugins that modify the World.

#

I really don't get this pushback. It's like I'm offering what is in my mind a clearly better solution in terms of DevX (not having to recompile is strictly better than having to recompile!), and because nobody realized that it could be done at first, it's getting sniped at

#

Clearly my solution can be made to work as it's essentially what Blender plugins do.

chilly grail
#

Look, I will be writing my own editor, separate from the bevy scene editor, because my needs are different from what the bevy scene editor does

shy wadi
#
  1. Different editors can offer their own macros/whatever for editor UIs. bevy_inspector_egui's type-specific inspectors will live alongside the Bevy editor's UI, for example. So my proposal isn't incompatible with having multiple editors. In fact, it benefits other editors, as it's based on generic TypeRegistry data so other editors can use the same libloading-based infrastructure if they want to.
  2. Crippling the Bevy editor by requiring that it be recompiled all the time so that it's on the same footing as potential third-party editors strikes me as unjustifiable (even if that dichotomy were real, which as I argued in (1) it isn't)
chilly grail
#

I think this discussion is getting off track, as we are getting into vague generalities about plugin architectures. My specific issue is that annotations on data types should be purely semantic - that is, they describe what the data is and what it means, not how it should be edited. It's up to the individual editors to decide how to edit a field based on that information. The data shouldn't know anything about how it is edited.

#

Range is a good example: it says that the valid range for this field is between min and max. It doesn't say anything about how that is achieved.

#

This also has the advantage that editors can edit data structure from other crates and libraries that they have no control over (they can't add annotations to a repo they don't own)

shy wadi
chilly grail
chilly grail
#

TBH, most of the time you only need editor hints on primitive / std types

#

Most bespoke types or newtypes are already narrowly defined in terms of what they mean

#

In my various editor projects, the most commonly annotated type is String. Because often a string field isn't just an arbitrary string, but has some meaning - like a token or FSM state. This means that your editor wants to be a picker popup rather than just accepting arbitrary typed text.

#

Of course, one can avoid this by wrapping every string like that in a newtype, but in practice type authors don't always bother to do this.

#

Example: a string which holds a font name should, when displayed in the editor, show a dropdown list of available fonts

#

However, if you wrap the string in a FontName(String) then the editor knows that it's a font name, without needing the extra reflection annotation.

rugged iris
#

Sorry for the delay, I didn't benchmark it, but the examples run with no perceptible difference. More complex use cases might reveal some bottlenecks though.

north knoll
#

Also just an observation from Unreal, which uses a similar "your game-specific editor stuff is a separate crate" approach - it works well for big stuff, but it's annoying for lots of small things. So they also have the (C++ equivalent of) #[cfg(feature = "editor")], which allows smaller stuff to embedded in the game crate for convenience but still compiled out.

shy wadi
civic fjord
#

Hi everyone, @mild jetty and I (mostly @mild jetty) started working on the inspector. We really want to see the editor come to life and guessed the best way to make some progress would be here. As a bonus, we get university credits for 'innovation' i.e. writing features. That's why we went for this instead of contributing to the upsteaming process.

We made a PR, more of a design proposal for a solution to allow more complex component displays in the component list. It aims to be reusable and extensible. You can see in the screenshot an exemple of the Vec3 with colored numbers.

https://github.com/alice-i-cecile/feathers_inspector/pull/42

The feathers inspector don't have a clear contributing process like the main bevy repo, I hope we did well. We'd be glad to have some feedback on our design proposal or directions on how to continue the feature process.

GitHub

Here is our design proposal for a solution to allow more complex component displays in the component list.
It implements a WidgetRegistry to associate a Reflect or PartialReflect type with a Bundle...

hasty vessel
#

@shell ingot @brisk garden @rugged iris I'd love your opinions here too

#

@chilly grail, this looks to be the "customizable widget displays per type" that you keep asking about

#

@civic fjord @mild jetty I'm really excited about this! Thanks for the contributions! Three initial thoughts:

  1. We should try to use one of the BSN traits for the "thing that you apply to create a widget"
  2. How does this compare to bevy-inspector-egui's design for the same problem?
  3. Do we later want to add macro syntax to make this easier for quick customization?
shell ingot
# hasty vessel <@208689993126772736> <@229684273098129408> <@695216885490384908> I'd love your ...

The figma has a lot of my work on this concept.
Really its not that difficult besides making and defining the rules for when to use certain widgets.
Visually I'm not too worried because it can all be adjusted later.

The main thing I'd worry about is modifying the data correctly and hooking that up, my previous attempts at this concept didn't get very far since reflection is just difficult conceptually for me.

I don't have the time to give this proper review atm but the cursory look I gave seems conceptually the right direction.

hasty vessel
#

Oh also we should merge main into this work again, since we now have functioning text input 😄

#

That will unblock a lot

shell ingot
#

Mhm

hasty vessel
#

I've been sniped by book writing in #documentation-dev and release prep 😅

brisk garden
mild jetty
#
  1. our approach was to keep the linearizing process of the extract... function, but egui's process seems to favor recursing widgets within each other. We're thinking about adding this behavior in a way that doesn't become cumbersome.
    This could either be statically with the ReflectWidget trait or dynamically with the WidgetRegistry. This could then be derived if needed, but the present reflect recursion is likely enough. Using a derive would mean there's only one type-erasure for each component, but that's not really important

an other difference with egui is that it requires a &mut self for editting. This is convenient for implementing behavior but doesn't work well for a two-step process (read all first, then insert widgets)

#
  1. I would think that feathers would handle any needed customization with headless widgets

one thing that could be considered is a macro that wraps a given field in a type with a different associated widget, or state that should be persisted for a widget across instantiations
(common example could be a XYZ euler switch, we'd want it to retain the selected mode after rebuilding the widget)

a design choice for how to represent components that are read-only is perhaps also relevant to the question of macros, but I haven't dug into it enough to have an idea

#

egui uses an Any to carry a config, it's an interesting concept but I don't like how they've but the fallible any cast as the implementer's responsibility and not sure how its supposed to work with nested widgets

#

the &InspectorConfig in the proposal was in part to try to be as minimalist in implementing a POC for this api, but i'm personnally in favor of removing it and relying on feathers or features of bsn, or using Bundle overrides like in feathers if really necessary

mild jetty
brisk garden
mild jetty
#

looking into the Scene thing. Scenes are meant to be repeatable, which is valid, but is there a similar concept for single-use scenes ?
is the overhead of cloning stored state enough to justify a sort of SceneOnce ? idk maybe it's optimized away if the scene is dropped after the invokation ?

mild jetty
#

i'm seeing impl Scene in the return types of the examples, that's what looks relevant to me
whether the body uses template(||{}) is just up to the actual widget, not the interface right ?

brisk garden
#

Template is what composes a Scene AFAIK

#

When you write impl Scene as a return type, you're saying "I return something that can be spawned." that's exactly what WidgetBuilder is trying to be from what i can see

#

The template(|| { ... }) closure inside a bsn! block is just how you construct a component that needs world access i believe (happy to be corrected on this). whoever writes the widget might use it internally (ie, to resolve a theme font handle), but the interface is fn widget(&self, path: &FieldPath) -> impl Scene

#

we are already doing this in my bsn branch of jackdaw once we moved to the AST-first model - the scene description is data, not really side effects

chilly grail
#

The feathers demo can now edit a Vec3, which should be useful for the inspector:

#
(
    :number_input(NumberInputProps {
        sigil_color: tokens::TEXT_INPUT_X_AXIS,
        label_text: Some("X"),
    })
    template_value(DemoVec3Field::X)
    Node {
        flex_grow: 1.0,
    }
    BorderColor::all(palette::X_AXIS)
    on(
        |value_change: On<ValueChange<f32>>,
        mut states: ResMut<DemoWidgetStates>| {
        if value_change.is_final {
            states.vec3_prop.x = value_change.value;
        }
    })
),
brisk garden
hasty vessel
#

Lovely

#

Very very useful

chilly grail
#

I'm wondering whether feathers should export a vec3_input widget, or whether we should simply let people assemble their own vec3 inputs out of individual number_input widgets (which is not that difficult).

shell ingot
candid mountain
#

Is this still being worked on?

#

I'm thinking of tasking on some tasks if so

velvet haven
candid mountain
hasty vessel
chilly grail
#

@hasty vessel @rugged iris I hadn't had a chance to look at the tabs.rs implementation before now, and I have a few nits:

  • The components should have a required component for the accessibility role - there are best practices for tab groups that should be followed
  • I'd like to keep the "tab bar" widget separate from the "tab deck":
    • Tab bar is just the row of tabs in the header, without the switching panes below it - this basically acts like a radio group
    • Tab deck is the whole thing, which contains both tab bar and tab panes
  • The reason for this is that I think it's important for the user to have the option of how they want to hide the non-active panels. It should not assume that Display::None is the only available option, for several reasons:
    • Some tab panes might be very expensive to keep around when hidden, and ought to be despawned when not visible, or at least the option should be available
    • Despawning when not visible is trivial in a reactive framework like bevy_reactor - it's just a Switch node.
  • Another reason to keep them separate is that we may want to put the tab bar in a panel header
#
  • The element that serves as the container for the set of tabs has role tablist.
  • Each element that serves as a tab has role tab and is contained within the element with role tablist.
  • Each element that contains the content panel for a tab has role tabpanel.
#

So in my own code, I'd have something like:

  • FeathersTabDeck
    • FeathersTabList
      • FeathersTab
      • FeathersTab
      • FeathersTab
    • FeathersTabPanelList
      • Switch
        • case
          • FeathersTabPanel
        • case
          • FeathersTabPanel
        • case
          • FeathersTabPanel
#

As an example of what I mean by "expensive", imagine a tab panel that contains a large scrolling list or tree view that is populated by a query, and kept up to date. The ECS system that populates the list only cares about what components and observers are attached to the scrolling list entity, and probably won't know or care that some parent entity happens to have Display::None. This means it's going to be doing the work of continually refreshing that list, even when the list isn't visible.

#

It gets worse, though: imagine that the tab panels contains a list of thumbnails which are rendered using off-screen render buffers - so keeping that tab around means keeping a bunch of extra cameras around

candid mountain
#

I imagine there are some tabs where hiding is preferable and some where despawning is

#

Also would there be room for a bespoke non-active panel strategy per panel implementation

#

For instance, disabling the refresh of the list or disabling the camera for the examples provided