From d16d216083afdf3860c076caa5f4bb6890b55fc9 Mon Sep 17 00:00:00 2001 From: Joe Buehler Date: Thu, 3 Jul 2025 19:51:32 +0100 Subject: [PATCH] Add support for returning all `Component` and values to query method in the Bevy Remote Protocol (#19857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective We should have an API with filtering to allow BRP clients to retrieve all relevant data from the world state. Currently working on adding examples - but reviews are appreciated! Still semi-WIP while I get my head around bevy’s reflection and implementation :) ## Solution This change adds support to query all entities in the world, and returns all of their Reflected Components with corresponding values. For custom `Components` it's important to still implement `Reflect` so that this endpoint returns these. This will be useful for the `bevy_entity_inspector` so that we can easily get the current world state. We have modified the existing query API so that clients can now pass in an empty `components[]` on the JSON request. ## Testing Updated example to showcase how to use the new endpoint to get all data: ```rust /// Create a query_all request to send to the remote Bevy app. /// This request will return all entities in the app, their components, and their /// component values. fn run_query_all_components_and_entities(url: String) -> Result<(), anyhow::Error> { let query_all_req = BrpRequest { jsonrpc: String::from("2.0"), method: String::from(BRP_QUERY_METHOD), id: Some(serde_json::to_value(1)?), params: None, }; println!("query_all req: {:#?}", query_all_req); let query_all_res = ureq::post(&url) .send_json(query_all_req)? .body_mut() .read_json::()?; println!("{query_all_res:#}"); Ok(()) } ``` --- ## Showcase In the `client.rs` example, we can clearly see (assuming the `server.rs` is running) a query hit for all entities and components: ```text query_all req: BrpRequest { jsonrpc: "2.0", method: "bevy/query", id: Some( Number(1), ), params: Some( Object { "data": Object { "components": Array [], "has": Array [], "option": Array [], }, "filter": Object { "with": Array [], "without": Array [], }, "strict": Bool(false), }, ), } ``` And in the massive response: ```text ..... { "components": { "bevy_window::monitor::Monitor": { "name": "\\\\.\\DISPLAY1", "physical_height": 1080, "physical_position": [ -1920, 0 ], "physical_width": 1920, "refresh_rate_millihertz": 240000, "scale_factor": 1.25, "video_modes": [ { "bit_depth": 32, "physical_size": [ 1920, 1080 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1680, 1050 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1600, 900 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1440, 900 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1400, 1050 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1366, 768 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1360, 768 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1280, 1024 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1280, 960 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1280, 800 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1280, 768 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1280, 720 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1280, 600 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1152, 864 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 1024, 768 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 800, 600 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 640, 480 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 640, 400 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 512, 384 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 400, 300 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 320, 240 ], "refresh_rate_millihertz": 240000 }, { "bit_depth": 32, "physical_size": [ 320, 200 ], "refresh_rate_millihertz": 240000 } ] } }, "entity": 4294967267 }, .... ``` What's also really cool about this and `bevy_reflect` is that we also get custom components returned as well (see below for `"server::Cube": 1.0` as the custom reflected struct specified in `server.rs`: ```text { "components": { "bevy_render::primitives::Aabb": { "center": [ 0.0, 0.0, 0.0 ], "half_extents": [ 0.5, 0.5, 0.5 ] }, "bevy_render::view::visibility::InheritedVisibility": true, "bevy_render::view::visibility::ViewVisibility": true, "bevy_render::view::visibility::Visibility": "Inherited", "bevy_transform::components::global_transform::GlobalTransform": [ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 2.4572744369506836, 0.0 ], "bevy_transform::components::transform::Transform": { "rotation": [ 0.0, 0.0, 0.0, 1.0 ], "scale": [ 1.0, 1.0, 1.0 ], "translation": [ 0.0, 2.4572744369506836, 0.0 ] }, "bevy_transform::components::transform::TransformTreeChanged": null, "server::Cube": 1.0 }, ``` --------- Co-authored-by: Jan Hohenheim --- crates/bevy_remote/Cargo.toml | 1 + crates/bevy_remote/src/builtin_methods.rs | 232 +++++++++++++++------- crates/bevy_remote/src/lib.rs | 135 +++++++++++++ examples/remote/client.rs | 145 +++++++++----- examples/remote/server.rs | 4 + 5 files changed, 402 insertions(+), 115 deletions(-) diff --git a/crates/bevy_remote/Cargo.toml b/crates/bevy_remote/Cargo.toml index 7fc8d2b4de..e7a40c65ba 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -38,6 +38,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1.0.140" http-body-util = "0.1" async-channel = "2" +bevy_log = { version = "0.17.0-dev", path = "../bevy_log" } # dependencies that will not compile on wasm [target.'cfg(not(target_family = "wasm"))'.dependencies] diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 615fdecc14..e847da08ed 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -14,6 +14,7 @@ use bevy_ecs::{ system::{In, Local}, world::{EntityRef, EntityWorldMut, FilteredEntityRef, World}, }; +use bevy_log::warn_once; use bevy_platform::collections::HashMap; use bevy_reflect::{ serde::{ReflectSerializer, TypedReflectDeserializer}, @@ -313,7 +314,7 @@ pub struct BrpQuery { /// /// [full path]: bevy_reflect::TypePath::type_path #[serde(default)] - pub option: Vec, + pub option: ComponentSelector, /// The [full path] of the type name of each component that is to be checked /// for presence. @@ -707,6 +708,32 @@ fn reflect_component( Ok(serialized_object) } +/// A selector for components in a query. +/// +/// This can either be a list of component paths or an "all" selector that +/// indicates that all components should be selected. +/// The "all" selector is useful when you want to retrieve all components +/// present on an entity without specifying each one individually. +/// The paths in the `Paths` variant must be the [full type paths]: e.g. +/// `bevy_transform::components::transform::Transform`, not just +/// `Transform`. +/// +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ComponentSelector { + /// An "all" selector that indicates all components should be selected. + All, + /// A list of component paths to select as optional components. + #[serde(untagged)] + Paths(Vec), +} + +impl Default for ComponentSelector { + fn default() -> Self { + Self::Paths(Vec::default()) + } +} + /// Handles a `bevy/query` request coming from a client. pub fn process_remote_query_request(In(params): In>, world: &mut World) -> BrpResult { let BrpQueryParams { @@ -715,44 +742,69 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W option, has, }, - filter: BrpQueryFilter { without, with }, + filter, strict, - } = parse_some(params)?; + } = match params { + Some(params) => parse_some(Some(params))?, + None => BrpQueryParams { + data: BrpQuery { + components: Vec::new(), + option: ComponentSelector::default(), + has: Vec::new(), + }, + filter: BrpQueryFilter::default(), + strict: false, + }, + }; let app_type_registry = world.resource::().clone(); let type_registry = app_type_registry.read(); - let (components, unregistered_in_components) = - get_component_ids(&type_registry, world, components, strict) + // Required components: must be present + let (required, unregistered_in_required) = + get_component_ids(&type_registry, world, components.clone(), strict) .map_err(BrpError::component_error)?; - let (option, _) = get_component_ids(&type_registry, world, option, strict) - .map_err(BrpError::component_error)?; - let (has, unregistered_in_has) = + + // Optional components: Option<&T> or all reflectable if "all" + let (optional, _) = match &option { + ComponentSelector::Paths(paths) => { + get_component_ids(&type_registry, world, paths.clone(), strict) + .map_err(BrpError::component_error)? + } + ComponentSelector::All => (Vec::new(), Vec::new()), + }; + + // Has components: presence check + let (has_ids, unregistered_in_has) = get_component_ids(&type_registry, world, has, strict).map_err(BrpError::component_error)?; - let (without, _) = get_component_ids(&type_registry, world, without, strict) - .map_err(BrpError::component_error)?; - let (with, unregistered_in_with) = get_component_ids(&type_registry, world, with, strict) + + // Filters + let (without, _) = get_component_ids(&type_registry, world, filter.without.clone(), strict) .map_err(BrpError::component_error)?; + let (with, unregistered_in_with) = + get_component_ids(&type_registry, world, filter.with.clone(), strict) + .map_err(BrpError::component_error)?; // When "strict" is false: // - Unregistered components in "option" and "without" are ignored. // - Unregistered components in "has" are considered absent from the entity. // - Unregistered components in "components" and "with" result in an empty // response since they specify hard requirements. - if !unregistered_in_components.is_empty() || !unregistered_in_with.is_empty() { + // If strict, fail if any required or with components are unregistered + if !unregistered_in_required.is_empty() || !unregistered_in_with.is_empty() { return serde_json::to_value(BrpQueryResponse::default()).map_err(BrpError::internal); } let mut query = QueryBuilder::::new(world); - for (_, component) in &components { + for (_, component) in &required { query.ref_id(*component); } - for (_, option) in &option { + for (_, option) in &optional { query.optional(|query| { query.ref_id(*option); }); } - for (_, has) in &has { + for (_, has) in &has_ids { query.optional(|query| { query.ref_id(*has); }); @@ -764,35 +816,67 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W query.with_id(with); } - // At this point, we can safely unify `components` and `option`, since we only retrieved - // entities that actually have all the `components` already. - // - // We also will just collect the `ReflectComponent` values from the type registry all - // at once so that we can reuse them between components. - let paths_and_reflect_components: Vec<(&str, &ReflectComponent)> = components - .into_iter() - .chain(option) - .map(|(type_id, _)| reflect_component_from_id(type_id, &type_registry)) - .collect::>>() - .map_err(BrpError::component_error)?; - - // ... and the analogous construction for `has`: - let has_paths_and_reflect_components: Vec<(&str, &ReflectComponent)> = has - .into_iter() - .map(|(type_id, _)| reflect_component_from_id(type_id, &type_registry)) + // Prepare has reflect info + let has_paths_and_reflect_components: Vec<(&str, &ReflectComponent)> = has_ids + .iter() + .map(|(type_id, _)| reflect_component_from_id(*type_id, &type_registry)) .collect::>>() .map_err(BrpError::component_error)?; let mut response = BrpQueryResponse::default(); let mut query = query.build(); + for row in query.iter(world) { - // The map of component values: - let components_map = build_components_map( - row.clone(), - paths_and_reflect_components.iter().copied(), + let entity_id = row.id(); + let entity_ref = world.get_entity(entity_id).expect("Entity should exist"); + + // Required components + let mut components_map = serialize_components( + entity_ref, &type_registry, - ) - .map_err(BrpError::component_error)?; + required + .iter() + .map(|(type_id, component_id)| (*type_id, Some(*component_id))), + ); + + // Optional components + match &option { + ComponentSelector::All => { + // Add all reflectable components present on the entity (as Option<&T>) + let all_optionals = + entity_ref + .archetype() + .components() + .filter_map(|component_id| { + let info = world.components().get_info(component_id)?; + let type_id = info.type_id()?; + // Skip required components (already included) + if required.iter().any(|(_, cid)| cid == &component_id) { + return None; + } + Some((type_id, Some(component_id))) + }); + components_map.extend(serialize_components( + entity_ref, + &type_registry, + all_optionals, + )); + } + ComponentSelector::Paths(_) => { + // Add only the requested optional components (as Option<&T>) + let optionals = optional.iter().filter(|(_, component_id)| { + // Skip required components (already included) + !required.iter().any(|(_, cid)| cid == component_id) + }); + components_map.extend(serialize_components( + entity_ref, + &type_registry, + optionals + .clone() + .map(|(type_id, component_id)| (*type_id, Some(*component_id))), + )); + } + } // The map of boolean-valued component presences: let has_map = build_has_map( @@ -800,16 +884,56 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W has_paths_and_reflect_components.iter().copied(), &unregistered_in_has, ); - response.push(BrpQueryRow { + + let query_row = BrpQueryRow { entity: row.id(), components: components_map, has: has_map, - }); + }; + + response.push(query_row); } serde_json::to_value(response).map_err(BrpError::internal) } +/// Serializes the specified components for an entity. +/// The iterator yields ([`TypeId`], Option<[`ComponentId`]>). +fn serialize_components( + entity_ref: EntityRef, + type_registry: &TypeRegistry, + components: impl Iterator)>, +) -> HashMap { + let mut components_map = HashMap::new(); + for (type_id, component_id_opt) in components { + let Some(type_registration) = type_registry.get(type_id) else { + continue; + }; + if let Some(reflect_component) = type_registration.data::() { + // If a component_id is provided, check if the entity has it + if let Some(component_id) = component_id_opt { + if !entity_ref.contains_id(component_id) { + continue; + } + } + if let Some(reflected) = reflect_component.reflect(entity_ref) { + let reflect_serializer = + ReflectSerializer::new(reflected.as_partial_reflect(), type_registry); + if let Ok(Value::Object(obj)) = serde_json::to_value(&reflect_serializer) { + components_map.extend(obj); + } else { + warn_once!( + "Failed to serialize component `{}` for entity {:?}", + type_registration.type_info().type_path(), + entity_ref.id() + ); + } + } + } + } + components_map +} + /// Handles a `bevy/spawn` request coming from a client. pub fn process_remote_spawn_request(In(params): In>, world: &mut World) -> BrpResult { let BrpSpawnParams { components } = parse_some(params)?; @@ -1328,36 +1452,6 @@ fn get_component_ids( Ok((component_ids, unregistered_components)) } -/// Given an entity (`entity_ref`) and a list of reflected component information -/// (`paths_and_reflect_components`), return a map which associates each component to -/// its serialized value from the entity. -/// -/// This is intended to be used on an entity which has already been filtered; components -/// where the value is not present on an entity are simply skipped. -fn build_components_map<'a>( - entity_ref: FilteredEntityRef, - paths_and_reflect_components: impl Iterator, - type_registry: &TypeRegistry, -) -> AnyhowResult> { - let mut serialized_components_map = >::default(); - - for (type_path, reflect_component) in paths_and_reflect_components { - let Some(reflected) = reflect_component.reflect(entity_ref.clone()) else { - continue; - }; - - let reflect_serializer = - ReflectSerializer::new(reflected.as_partial_reflect(), type_registry); - let Value::Object(serialized_object) = serde_json::to_value(&reflect_serializer)? else { - return Err(anyhow!("Component `{}` could not be serialized", type_path)); - }; - - serialized_components_map.extend(serialized_object.into_iter()); - } - - Ok(serialized_components_map) -} - /// Given an entity (`entity_ref`), /// a list of reflected component information (`paths_and_reflect_components`) /// and a list of unregistered components, diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 348be8089d..25c1835faf 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -136,6 +136,7 @@ //! - `components` (optional): An array of [fully-qualified type names] of components to fetch, //! see _below_ example for a query to list all the type names in **your** project. //! - `option` (optional): An array of fully-qualified type names of components to fetch optionally. +//! to fetch all reflectable components, you can pass in the string `"all"`. //! - `has` (optional): An array of fully-qualified type names of components whose presence will be //! reported as boolean values. //! - `filter` (optional): @@ -153,7 +154,141 @@ //! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the //! entity has that component. If `has` was empty or omitted, this key will be omitted in the response. //! +//! ### Example +//! To use the query API and retrieve Transform data for all entities that have a Transform +//! use this query: //! +//! ```json +//! { +//! "jsonrpc": "2.0", +//! "method": "bevy/query", +//! "id": 0, +//! "params": { +//! "data": { +//! "components": ["bevy_transform::components::transform::Transform"] +//! "option": [], +//! "has": [] +//! }, +//! "filter": { +//! "with": [], +//! "without": [] +//! }, +//! "strict": false +//! } +//! } +//! ``` +//! +//! +//! To query all entities and all of their Reflectable components (and retrieve their values), you can pass in "all" for the option field: +//! ```json +//! { +//! "jsonrpc": "2.0", +//! "method": "bevy/query", +//! "id": 0, +//! "params": { +//! "data": { +//! "components": [] +//! "option": "all", +//! "has": [] +//! }, +//! "filter": { +//! "with": [], +//! "without": [] +//! }, +//! "strict": false +//! } +//! } +//! ``` +//! +//! This should return you something like the below (in a larger list): +//! ```json +//! { +//! "components": { +//! "bevy_core_pipeline::core_3d::camera_3d::Camera3d": { +//! "depth_load_op": { +//! "Clear": 0.0 +//! }, +//! "depth_texture_usages": 16, +//! "screen_space_specular_transmission_quality": "Medium", +//! "screen_space_specular_transmission_steps": 1 +//! }, +//! "bevy_core_pipeline::tonemapping::DebandDither": "Enabled", +//! "bevy_core_pipeline::tonemapping::Tonemapping": "TonyMcMapface", +//! "bevy_pbr::cluster::ClusterConfig": { +//! "FixedZ": { +//! "dynamic_resizing": true, +//! "total": 4096, +//! "z_config": { +//! "far_z_mode": "MaxClusterableObjectRange", +//! "first_slice_depth": 5.0 +//! }, +//! "z_slices": 24 +//! } +//! }, +//! "bevy_render::camera::camera::Camera": { +//! "clear_color": "Default", +//! "is_active": true, +//! "msaa_writeback": true, +//! "order": 0, +//! "sub_camera_view": null, +//! "target": { +//! "Window": "Primary" +//! }, +//! "viewport": null +//! }, +//! "bevy_render::camera::projection::Projection": { +//! "Perspective": { +//! "aspect_ratio": 1.7777777910232544, +//! "far": 1000.0, +//! "fov": 0.7853981852531433, +//! "near": 0.10000000149011612 +//! } +//! }, +//! "bevy_render::primitives::Frustum": {}, +//! "bevy_render::sync_world::RenderEntity": 4294967291, +//! "bevy_render::sync_world::SyncToRenderWorld": {}, +//! "bevy_render::view::Msaa": "Sample4", +//! "bevy_render::view::visibility::InheritedVisibility": true, +//! "bevy_render::view::visibility::ViewVisibility": false, +//! "bevy_render::view::visibility::Visibility": "Inherited", +//! "bevy_render::view::visibility::VisibleEntities": {}, +//! "bevy_transform::components::global_transform::GlobalTransform": [ +//! 0.9635179042816162, +//! -3.725290298461914e-9, +//! 0.26764383912086487, +//! 0.11616238951683044, +//! 0.9009039402008056, +//! -0.4181846082210541, +//! -0.24112138152122495, +//! 0.4340185225009918, +//! 0.8680371046066284, +//! -2.5, +//! 4.5, +//! 9.0 +//! ], +//! "bevy_transform::components::transform::Transform": { +//! "rotation": [ +//! -0.22055435180664065, +//! -0.13167093694210052, +//! -0.03006339818239212, +//! 0.9659786224365234 +//! ], +//! "scale": [ +//! 1.0, +//! 1.0, +//! 1.0 +//! ], +//! "translation": [ +//! -2.5, +//! 4.5, +//! 9.0 +//! ] +//! }, +//! "bevy_transform::components::transform::TransformTreeChanged": null +//! }, +//! "entity": 4294967261 +//!}, +//! ``` //! //! ### `bevy/spawn` //! diff --git a/examples/remote/client.rs b/examples/remote/client.rs index c4ededd163..a139863199 100644 --- a/examples/remote/client.rs +++ b/examples/remote/client.rs @@ -1,60 +1,59 @@ //! A simple command line client that allows issuing queries to a remote Bevy //! app via the BRP. +//! This example requires the `bevy_remote` feature to be enabled. +//! You can run it with the following command: +//! ```text +//! cargo run --example client --features="bevy_remote" +//! ``` +//! This example assumes that the `server` example is running on the same machine. + +use std::any::type_name; use anyhow::Result as AnyhowResult; -use argh::FromArgs; -use bevy::remote::{ - builtin_methods::{BrpQuery, BrpQueryFilter, BrpQueryParams, BRP_QUERY_METHOD}, - http::DEFAULT_ADDR, - http::DEFAULT_PORT, - BrpRequest, +use bevy::{ + ecs::hierarchy::ChildOf, + prelude::info, + remote::{ + builtin_methods::{ + BrpQuery, BrpQueryFilter, BrpQueryParams, ComponentSelector, BRP_QUERY_METHOD, + }, + http::{DEFAULT_ADDR, DEFAULT_PORT}, + BrpRequest, + }, + transform::components::Transform, }; -/// Struct containing the command-line arguments that can be passed to this example. -/// -/// The components are passed by their full type names positionally, while `host` -/// and `port` are optional arguments which should correspond to those used on -/// the server. -/// -/// When running this example in conjunction with the `server` example, the `host` -/// and `port` can be left as their defaults. -/// -/// For example, to connect to port 1337 on the default IP address and query for entities -/// with `Transform` components: -/// ```text -/// cargo run --example client -- --port 1337 bevy_transform::components::transform::Transform -/// ``` -#[derive(FromArgs)] -struct Args { - /// the host IP address to connect to - #[argh(option, default = "DEFAULT_ADDR.to_string()")] - host: String, - /// the port to connect to - #[argh(option, default = "DEFAULT_PORT")] - port: u16, - /// the full type names of the components to query for - #[argh(positional, greedy)] - components: Vec, -} - /// The application entry point. fn main() -> AnyhowResult<()> { - // Parse the arguments. - let args: Args = argh::from_env(); - // Create the URL. We're going to need it to issue the HTTP request. - let host_part = format!("{}:{}", args.host, args.port); + let host_part = format!("{DEFAULT_ADDR}:{DEFAULT_PORT}"); let url = format!("http://{host_part}/"); + // Creates a request to get all Transform components from the remote Bevy app. + // This request will return all entities that have a Transform component. + run_transform_only_query(&url)?; - let req = BrpRequest { + // Create a query that only returns root entities - ie, entities that do not + // have a parent. + run_query_root_entities(&url)?; + + // Create a query all request to send to the remote Bevy app. + // This request will return all entities in the app, their components, and their + // component values. + run_query_all_components_and_entities(&url)?; + + Ok(()) +} + +fn run_query_all_components_and_entities(url: &str) -> Result<(), anyhow::Error> { + let query_all_req = BrpRequest { jsonrpc: String::from("2.0"), method: String::from(BRP_QUERY_METHOD), id: Some(serde_json::to_value(1)?), params: Some( serde_json::to_value(BrpQueryParams { data: BrpQuery { - components: args.components, - option: Vec::default(), + components: Vec::default(), + option: ComponentSelector::All, has: Vec::default(), }, strict: false, @@ -63,13 +62,67 @@ fn main() -> AnyhowResult<()> { .expect("Unable to convert query parameters to a valid JSON value"), ), }; - - let res = ureq::post(&url) - .send_json(req)? + info!("query_all req: {query_all_req:#?}"); + let query_all_res = ureq::post(url) + .send_json(query_all_req)? .body_mut() .read_json::()?; - - println!("{res:#}"); - + info!("{query_all_res:#}"); + Ok(()) +} + +fn run_transform_only_query(url: &str) -> Result<(), anyhow::Error> { + let get_transform_request = BrpRequest { + jsonrpc: String::from("2.0"), + method: String::from(BRP_QUERY_METHOD), + id: Some(serde_json::to_value(1)?), + params: Some( + serde_json::to_value(BrpQueryParams { + data: BrpQuery { + components: vec![type_name::().to_string()], + ..Default::default() + }, + strict: false, + filter: BrpQueryFilter::default(), + }) + .expect("Unable to convert query parameters to a valid JSON value"), + ), + }; + info!("transform request: {get_transform_request:#?}"); + let res = ureq::post(url) + .send_json(get_transform_request)? + .body_mut() + .read_json::()?; + info!("{res:#}"); + Ok(()) +} + +fn run_query_root_entities(url: &str) -> Result<(), anyhow::Error> { + let get_transform_request = BrpRequest { + jsonrpc: String::from("2.0"), + method: String::from(BRP_QUERY_METHOD), + id: Some(serde_json::to_value(1)?), + params: Some( + serde_json::to_value(BrpQueryParams { + data: BrpQuery { + components: Vec::default(), + option: ComponentSelector::All, + has: Vec::default(), + }, + strict: false, + filter: BrpQueryFilter { + without: vec![type_name::().to_string()], + with: Vec::default(), + }, + }) + .expect("Unable to convert query parameters to a valid JSON value"), + ), + }; + info!("transform request: {get_transform_request:#?}"); + let res = ureq::post(url) + .send_json(get_transform_request)? + .body_mut() + .read_json::()?; + info!("{res:#}"); Ok(()) } diff --git a/examples/remote/server.rs b/examples/remote/server.rs index e90f5ea44e..057565e2e8 100644 --- a/examples/remote/server.rs +++ b/examples/remote/server.rs @@ -1,4 +1,8 @@ //! A Bevy app that you can connect to with the BRP and edit. +//! Run this example with the `remote` feature enabled: +//! ```bash +//! cargo run --example server --features="bevy_remote" +//! ``` use bevy::math::ops::cos; use bevy::{