#Entity Inspector
1116 messages · Page 2 of 2 (latest)
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.
I'm going to bow out of this to wrap up bsn stuff 🙂
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
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".
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.
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.
BRP enables you to inspect a Bevy app "anywhere" (ex: a brower, an app running somewhere on the internet, an app running on a game console or phone, etc). It defines a fully dynamic data-driven protocol. As long as you can speak it, you can interact with a bevy game
It also lets web browsers inspect your running game
This is cool https://github.com/doup/birp
Hosted here: https://doup.github.io/birp/
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 🙂
Not sure if this is what you mean, BRP isn't really an "extra crate" in my mind since it comes automatically when you say bevy = "0.18"
No, the "extra crate" I meant was egui
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!
I think the binding between editable type and editor should point in the opposite direction
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
Rangeannotation 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
Rangecan 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;
}
So you do an O(n) search through all inspectors every time you want to edit a type?
Performance is really not a concern, it only happens when the editor selection changes
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
It's not a simple type -> inspector mapping
The same type might have different inspectors based on annotation
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.
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.
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
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
With my proposal you don't have to recompile the editor at all, thanks to use of libloading
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.
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.
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
Clearly there's more to it than that. For example, if an plugin constructs a UI, it has to play nice with the other plugins who are also constructing UI. Something has to orchestrate where on the screen all these pieces go, how they respond to updates to the state, and other internal functions of the editor like selection state.
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.
I have a lot of reasons, but one of the main ones is that there isn't "one true editor". Adding annotations to structs that specify how to edit that struct assumes that there can only be one editor for that struct.
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
- 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 genericTypeRegistrydata so other editors can use the samelibloading-based infrastructure if they want to. - 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)
My editor isn't going to use libloading, or BRP. It's just a library that is conditionally compiled in. That's the quickest and easiest thing I can build.
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)
I agree with that. Those are what I was calling "editor hints"
Agreement, yay! 🙂
BTW, here's what I had working in Bevy two years ago: https://www.youtube.com/watch?v=3y1q4VpXJRk - unfortunately I have made no progress since that time (been busy waiting on bsn and building ui infrastructure)
Showing progress in editing game worlds in Panoply, my Bevy/Rust game engine.
BTW, a model for "editor hints" that I really like is json-schema
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.
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.
For what it's worth, there's an existing proposal that has some similarities and avoids recompiling the editor: https://github.com/bevyengine/bevy_editor_prototypes/pull/261/files. They're currently putting the game into a lib without splitting off the game_editor_ui stuff, but that seems easy to fit in.
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.
Yeah, I saw that proposal. I think that putting everything in a library is unnecessary. Just components with custom UI
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.
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...
Hi hi! Yeah, this is very ad hoc / permissive contributing right now 🙂
@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:
- We should try to use one of the BSN traits for the "thing that you apply to create a widget"
- How does this compare to
bevy-inspector-egui's design for the same problem? - Do we later want to add macro syntax to make this easier for quick customization?
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.
Oh also we should merge main into this work again, since we now have functioning text input 😄
That will unblock a lot
Mhm
I've been sniped by book writing in #documentation-dev and release prep 😅
thank you for this @civic fjord @mild jetty this is awesome work! i'm the owner of jackdaw btw which has an entity/component inspector so will have a look at these changes!
- yes very much so ! it does look like an impl or boxed Scene would be the best container for the 'Widget Builder' @civic fjord's been looking into that
- 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)
- 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
the 'hooking that up' is really just reusing the Drag widget's field path concept
I think it could be expanded to have &mut self callbacks for convenient editting
personally, i think Template is the way forward here
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 ?
to be honest I'm having trouble wrapping my brain around the exact distinction between all the BSN features. I just need to read through it over again
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 ?
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
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;
}
})
),
Very nice!
Oh man can’t wait to move to these in jackdaw
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).
Mostly have a vec3 widget because vec3 is such a commonly used data type in engines
but each number should be output individually as thats just overall more flexible and allows for non-linear associations, like colors could also use them for inputs, which would require diffrent settings on each, like oklch has diffrent ranges for l/c/h/a
Have a look at https://github.com/alice-i-cecile/feathers_inspector/blob/main/UPSTREAMING_STRATEGY.md
Yes please!
I'm a bit confused, for the items with targets outside of the feathers repo, are they meant to be PRed to the other repo?
Yes, generally this is intended to be incrementally upstreamed to Bevy itself
@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::Noneis 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
Switchnode.
- 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
taband is contained within the element with roletablist.- 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
- case
- Switch
- FeathersTabList
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
Do you think this option should be per tab?
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