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:
parent
ebf87f56ef
commit
d16d216083
@ -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]
|
||||
|
@ -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,
|
||||
|
@ -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`
|
||||
//!
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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::{
|
||||
|
Loading…
Reference in New Issue
Block a user