Skip to content

Commit 383d525

Browse files
Add a remote source to the inspector entity tree (#26014)
# Objective Part of #23013. The inspector can only look at the world it runs in. To inspect a running game from a separate app, it needs to read another world over the Bevy Remote Protocol. # Solution Remote entities live in their own `World`, the `RemoteWorld`, spawned at their remote ids. This is @Zeophlite design and it replaces the proxy entities and id map from earlier revisions. The entity tree and details panel just read whichever world `world_to_inspect` hands them, so no local hook, observer or system ever sees remote data. - polls read `Name`, `ChildOf` and the label defining components, and a fuller poll with `has` runs when unseen entities show up and every 10 seconds, so internal entities like observers and systems stay hidden - only `Name` and `ChildOf` are inserted for now, a child whose parent isn't mirrored yet shows as a root until the parent appears - polls run off the main thread with one in flight at a time, and the tree only resyncs when something changed - the remote world is rebuilt on reconnect, on source change, on a failed connection, and when a remote id goes back a generation, which means the remote app restarted - selecting a remote entity shows only its name and hierarchy for now, component values come in the follow-up PR `World::spawn_at` only accepts an index's current generation and there's no public way to set one, so `spawn_at_remote_id` steps the generation up by spawning and despawning without freeing, and reserves every index up to the remote one so the allocator never hands those out. A small `bevy_ecs` follow-up adding a public way to move an unspawned index to a given generation, and to reserve an index in the allocator, would let us replace that function with a single call. # AI disclosure This is the first part of the remote inspection that already works in Jackdaw. I had AI rebuild it on top of what has merged since, review it for edge cases and run it end to end against the server example, then went through the changes myself. The biggest change from the original is that remote components are no longer inserted into the local world. After review, this moved to Zeophlite's separate world design, which AI reworked from his WIP branch, tested end to end against the server example, and I reviewed. # Showcase https://github.com/user-attachments/assets/396987b0-a9b6-4bbe-b28e-e1d79d92c959 --------- Co-authored-by: Daniel Skates <zeophlite@gmail.com>
1 parent e3f383c commit 383d525

13 files changed

Lines changed: 2413 additions & 37 deletions

File tree

‎Cargo.toml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4883,6 +4883,18 @@ description = "Inspects the app's own world with the bevy_inspector entity tree
48834883
category = "Dev tools"
48844884
wasm = true
48854885

4886+
[[example]]
4887+
name = "remote_inspector"
4888+
path = "examples/inspector/remote_inspector.rs"
4889+
doc-scrape-examples = true
4890+
required-features = ["bevy_inspector", "bevy_remote_client"]
4891+
4892+
[package.metadata.example.remote_inspector]
4893+
name = "Remote Inspector"
4894+
description = "Inspects a separate running app with the bevy_inspector panels over the Bevy Remote Protocol"
4895+
category = "Dev tools"
4896+
wasm = false
4897+
48864898
[[example]]
48874899
name = "2d_top_down_camera"
48884900
path = "examples/camera/2d_top_down_camera.rs"

‎_release-content/release-notes/inspection.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Entity inspection tools
33
authors: ["@jbuehler23", "@alice-i-cecile", "@Nilirad", "@Zeophlite"]
4-
pull_requests: [25818, 25822, 25823, 25824, 25826, 25837, 25845, 25866, 25883, 25884, 25885, 25923]
4+
pull_requests: [25818, 25822, 25823, 25824, 25826, 25837, 25845, 25866, 25883, 25884, 25885, 25923, 26014]
55
---
66

77
`bevy_dev_tools::inspection` is gaining a backend for inspecting worlds, entities, components, and resources.
@@ -18,3 +18,4 @@ This note will be completed once the rest of the series lands.
1818
- Added serde support and structured values to the inspection types (#25866)
1919
- Added `world.inspect*`, `world.summarize` and `registry.component_metadata` to the Bevy Remote Protocol (#25883)
2020
- Added the `bevy_inspector` crate with a local entity tree and an editable details panel (#25884, #25885, #25923)
21+
- Added a remote source to `bevy_inspector`, showing the entity tree of a separate running app over the Bevy Remote Protocol (#26014)

‎crates/bevy_inspector/Cargo.toml‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ bevy_ui = { path = "../bevy_ui", version = "0.20.0-dev" }
2626
bevy_ui_widgets = { path = "../bevy_ui_widgets", version = "0.20.0-dev" }
2727
bevy_utils = { path = "../bevy_utils", version = "0.20.0-dev" }
2828
bevy_window = { path = "../bevy_window", version = "0.20.0-dev" }
29+
bevy_remote = { path = "../bevy_remote", version = "0.20.0-dev", default-features = false, features = [
30+
"client",
31+
"http",
32+
], optional = true }
33+
bevy_tasks = { path = "../bevy_tasks", version = "0.20.0-dev", optional = true }
34+
35+
# other
36+
serde = { version = "1", optional = true }
37+
serde_json = { version = "1.0.140", optional = true }
2938

3039
[dev-dependencies]
3140
bevy_camera = { path = "../bevy_camera", version = "0.20.0-dev" }
@@ -36,6 +45,14 @@ bevy_math = { path = "../bevy_math", version = "0.20.0-dev" }
3645

3746
[features]
3847
default = []
48+
# Inspect a separate running app over the Bevy Remote Protocol
49+
remote = [
50+
"dep:bevy_remote",
51+
"dep:bevy_tasks",
52+
"dep:serde",
53+
"dep:serde_json",
54+
"bevy_ecs/serialize",
55+
]
3956

4057
[lints]
4158
workspace = true

‎crates/bevy_inspector/src/details_panel.rs‎

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,13 @@ struct FieldWidget {
205205

206206
/// A component of the inspected entity, with its fields flattened into rows.
207207
#[derive(Debug, Clone)]
208-
struct ComponentDetails {
209-
id: ComponentId,
208+
pub(crate) struct ComponentDetails {
209+
pub(crate) id: ComponentId,
210210
/// The name shown in the group header, a [`ShortName`] of the component type.
211-
name: String,
212-
type_path: String,
213-
memory: String,
214-
fields: Vec<FieldEntry>,
211+
pub(crate) name: String,
212+
pub(crate) type_path: String,
213+
pub(crate) memory: String,
214+
pub(crate) fields: Vec<FieldEntry>,
215215
}
216216

217217
/// The group spawned for one component, whether its fields were spawned, and the rows they were
@@ -835,8 +835,9 @@ pub fn sync_details_panel(world: &mut World) {
835835
return;
836836
};
837837

838-
let components = inspect_components(world, selection);
839-
let empty = empty_state(world, selection, &components);
838+
let inspected = crate::world_to_inspect(world);
839+
let components = inspect_components(inspected, selection);
840+
let empty = empty_state(inspected, selection, &components);
840841

841842
let index = world.resource::<DetailsIndex>();
842843
if selection_changed || index.body != Some(body) || index.empty != empty {
@@ -855,7 +856,11 @@ fn find_body(world: &mut World) -> Option<Entity> {
855856
.next()
856857
}
857858

858-
fn inspect_components(world: &World, selection: Option<Entity>) -> Vec<ComponentDetails> {
859+
/// The component groups of `selection` in the inspected `world`, sorted in display order.
860+
pub(crate) fn inspect_components(
861+
world: &World,
862+
selection: Option<Entity>,
863+
) -> Vec<ComponentDetails> {
859864
let Some(entity) = selection else {
860865
return Vec::new();
861866
};
@@ -916,6 +921,10 @@ fn inspect_components(world: &World, selection: Option<Entity>) -> Vec<Component
916921
}
917922
})
918923
.collect();
924+
#[cfg(feature = "remote")]
925+
if let Some(record) = world.component_id::<crate::remote::RemoteComponents>() {
926+
components.retain(|component| component.id != record);
927+
}
919928
components.sort_by(|left, right| (&left.name, left.id).cmp(&(&right.name, right.id)));
920929
components
921930
}
@@ -1684,7 +1693,7 @@ impl Walk {
16841693
}
16851694

16861695
/// The read-only caption form of a field value.
1687-
fn read_only(value: FieldValue) -> FieldValue {
1696+
pub(crate) fn read_only(value: FieldValue) -> FieldValue {
16881697
FieldValue::Label(match value {
16891698
FieldValue::Bool(value) => value.to_string(),
16901699
FieldValue::Number(NumericValue::F32(value)) => value.to_string(),

0 commit comments

Comments
 (0)