Add support for returning all Component and values to query method in the Bevy Remote Protocol (#19857)

# 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::<serde_json::Value>()?;
    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 <jan@hohenheim.ch>
This commit is contained in:
Joe Buehler 2025-07-03 19:51:32 +01:00 committed by GitHub
parent ebf87f56ef
commit d16d216083
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 402 additions and 115 deletions

View File

@ -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]

View File

@ -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<String>,
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<String>),
}
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<Option<Value>>, world: &mut World) -> BrpResult {
let BrpQueryParams {
@ -715,44 +742,69 @@ pub fn process_remote_query_request(In(params): In<Option<Value>>, 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::<AppTypeRegistry>().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::<FilteredEntityRef>::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<Option<Value>>, 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::<AnyhowResult<Vec<(&str, &ReflectComponent)>>>()
.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::<AnyhowResult<Vec<(&str, &ReflectComponent)>>>()
.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<Option<Value>>, 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<Item = (TypeId, Option<ComponentId>)>,
) -> HashMap<String, Value> {
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::<ReflectComponent>() {
// 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<Option<Value>>, 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<Item = (&'a str, &'a ReflectComponent)>,
type_registry: &TypeRegistry,
) -> AnyhowResult<HashMap<String, Value>> {
let mut serialized_components_map = <HashMap<_, _>>::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,

View File

@ -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`
//!

View File

@ -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<String>,
}
/// 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::<serde_json::Value>()?;
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::<Transform>().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::<serde_json::Value>()?;
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::<ChildOf>().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::<serde_json::Value>()?;
info!("{res:#}");
Ok(())
}

View File

@ -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::{