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"
|
serde_json = "1.0.140"
|
||||||
http-body-util = "0.1"
|
http-body-util = "0.1"
|
||||||
async-channel = "2"
|
async-channel = "2"
|
||||||
|
bevy_log = { version = "0.17.0-dev", path = "../bevy_log" }
|
||||||
|
|
||||||
# dependencies that will not compile on wasm
|
# dependencies that will not compile on wasm
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use bevy_ecs::{
|
|||||||
system::{In, Local},
|
system::{In, Local},
|
||||||
world::{EntityRef, EntityWorldMut, FilteredEntityRef, World},
|
world::{EntityRef, EntityWorldMut, FilteredEntityRef, World},
|
||||||
};
|
};
|
||||||
|
use bevy_log::warn_once;
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::{
|
use bevy_reflect::{
|
||||||
serde::{ReflectSerializer, TypedReflectDeserializer},
|
serde::{ReflectSerializer, TypedReflectDeserializer},
|
||||||
@ -313,7 +314,7 @@ pub struct BrpQuery {
|
|||||||
///
|
///
|
||||||
/// [full path]: bevy_reflect::TypePath::type_path
|
/// [full path]: bevy_reflect::TypePath::type_path
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub option: Vec<String>,
|
pub option: ComponentSelector,
|
||||||
|
|
||||||
/// The [full path] of the type name of each component that is to be checked
|
/// The [full path] of the type name of each component that is to be checked
|
||||||
/// for presence.
|
/// for presence.
|
||||||
@ -707,6 +708,32 @@ fn reflect_component(
|
|||||||
Ok(serialized_object)
|
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.
|
/// Handles a `bevy/query` request coming from a client.
|
||||||
pub fn process_remote_query_request(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
|
pub fn process_remote_query_request(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
|
||||||
let BrpQueryParams {
|
let BrpQueryParams {
|
||||||
@ -715,23 +742,47 @@ pub fn process_remote_query_request(In(params): In<Option<Value>>, world: &mut W
|
|||||||
option,
|
option,
|
||||||
has,
|
has,
|
||||||
},
|
},
|
||||||
filter: BrpQueryFilter { without, with },
|
filter,
|
||||||
strict,
|
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 app_type_registry = world.resource::<AppTypeRegistry>().clone();
|
||||||
let type_registry = app_type_registry.read();
|
let type_registry = app_type_registry.read();
|
||||||
|
|
||||||
let (components, unregistered_in_components) =
|
// Required components: must be present
|
||||||
get_component_ids(&type_registry, world, components, strict)
|
let (required, unregistered_in_required) =
|
||||||
|
get_component_ids(&type_registry, world, components.clone(), strict)
|
||||||
.map_err(BrpError::component_error)?;
|
.map_err(BrpError::component_error)?;
|
||||||
let (option, _) = get_component_ids(&type_registry, world, option, strict)
|
|
||||||
.map_err(BrpError::component_error)?;
|
// Optional components: Option<&T> or all reflectable if "all"
|
||||||
let (has, unregistered_in_has) =
|
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)?;
|
get_component_ids(&type_registry, world, has, strict).map_err(BrpError::component_error)?;
|
||||||
let (without, _) = get_component_ids(&type_registry, world, without, strict)
|
|
||||||
|
// Filters
|
||||||
|
let (without, _) = get_component_ids(&type_registry, world, filter.without.clone(), strict)
|
||||||
.map_err(BrpError::component_error)?;
|
.map_err(BrpError::component_error)?;
|
||||||
let (with, unregistered_in_with) = get_component_ids(&type_registry, world, with, strict)
|
let (with, unregistered_in_with) =
|
||||||
|
get_component_ids(&type_registry, world, filter.with.clone(), strict)
|
||||||
.map_err(BrpError::component_error)?;
|
.map_err(BrpError::component_error)?;
|
||||||
|
|
||||||
// When "strict" is false:
|
// When "strict" is false:
|
||||||
@ -739,20 +790,21 @@ pub fn process_remote_query_request(In(params): In<Option<Value>>, world: &mut W
|
|||||||
// - Unregistered components in "has" are considered absent from the entity.
|
// - Unregistered components in "has" are considered absent from the entity.
|
||||||
// - Unregistered components in "components" and "with" result in an empty
|
// - Unregistered components in "components" and "with" result in an empty
|
||||||
// response since they specify hard requirements.
|
// 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);
|
return serde_json::to_value(BrpQueryResponse::default()).map_err(BrpError::internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut query = QueryBuilder::<FilteredEntityRef>::new(world);
|
let mut query = QueryBuilder::<FilteredEntityRef>::new(world);
|
||||||
for (_, component) in &components {
|
for (_, component) in &required {
|
||||||
query.ref_id(*component);
|
query.ref_id(*component);
|
||||||
}
|
}
|
||||||
for (_, option) in &option {
|
for (_, option) in &optional {
|
||||||
query.optional(|query| {
|
query.optional(|query| {
|
||||||
query.ref_id(*option);
|
query.ref_id(*option);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (_, has) in &has {
|
for (_, has) in &has_ids {
|
||||||
query.optional(|query| {
|
query.optional(|query| {
|
||||||
query.ref_id(*has);
|
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);
|
query.with_id(with);
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, we can safely unify `components` and `option`, since we only retrieved
|
// Prepare has reflect info
|
||||||
// entities that actually have all the `components` already.
|
let has_paths_and_reflect_components: Vec<(&str, &ReflectComponent)> = has_ids
|
||||||
//
|
.iter()
|
||||||
// We also will just collect the `ReflectComponent` values from the type registry all
|
.map(|(type_id, _)| reflect_component_from_id(*type_id, &type_registry))
|
||||||
// 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))
|
|
||||||
.collect::<AnyhowResult<Vec<(&str, &ReflectComponent)>>>()
|
.collect::<AnyhowResult<Vec<(&str, &ReflectComponent)>>>()
|
||||||
.map_err(BrpError::component_error)?;
|
.map_err(BrpError::component_error)?;
|
||||||
|
|
||||||
let mut response = BrpQueryResponse::default();
|
let mut response = BrpQueryResponse::default();
|
||||||
let mut query = query.build();
|
let mut query = query.build();
|
||||||
|
|
||||||
for row in query.iter(world) {
|
for row in query.iter(world) {
|
||||||
// The map of component values:
|
let entity_id = row.id();
|
||||||
let components_map = build_components_map(
|
let entity_ref = world.get_entity(entity_id).expect("Entity should exist");
|
||||||
row.clone(),
|
|
||||||
paths_and_reflect_components.iter().copied(),
|
// Required components
|
||||||
|
let mut components_map = serialize_components(
|
||||||
|
entity_ref,
|
||||||
&type_registry,
|
&type_registry,
|
||||||
)
|
required
|
||||||
.map_err(BrpError::component_error)?;
|
.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:
|
// The map of boolean-valued component presences:
|
||||||
let has_map = build_has_map(
|
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(),
|
has_paths_and_reflect_components.iter().copied(),
|
||||||
&unregistered_in_has,
|
&unregistered_in_has,
|
||||||
);
|
);
|
||||||
response.push(BrpQueryRow {
|
|
||||||
|
let query_row = BrpQueryRow {
|
||||||
entity: row.id(),
|
entity: row.id(),
|
||||||
components: components_map,
|
components: components_map,
|
||||||
has: has_map,
|
has: has_map,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
response.push(query_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_json::to_value(response).map_err(BrpError::internal)
|
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.
|
/// Handles a `bevy/spawn` request coming from a client.
|
||||||
pub fn process_remote_spawn_request(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
|
pub fn process_remote_spawn_request(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
|
||||||
let BrpSpawnParams { components } = parse_some(params)?;
|
let BrpSpawnParams { components } = parse_some(params)?;
|
||||||
@ -1328,36 +1452,6 @@ fn get_component_ids(
|
|||||||
Ok((component_ids, unregistered_components))
|
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`),
|
/// Given an entity (`entity_ref`),
|
||||||
/// a list of reflected component information (`paths_and_reflect_components`)
|
/// a list of reflected component information (`paths_and_reflect_components`)
|
||||||
/// and a list of unregistered components,
|
/// and a list of unregistered components,
|
||||||
|
|||||||
@ -136,6 +136,7 @@
|
|||||||
//! - `components` (optional): An array of [fully-qualified type names] of components to fetch,
|
//! - `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.
|
//! 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.
|
//! - `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
|
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
|
||||||
//! reported as boolean values.
|
//! reported as boolean values.
|
||||||
//! - `filter` (optional):
|
//! - `filter` (optional):
|
||||||
@ -153,7 +154,141 @@
|
|||||||
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
|
//! - `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.
|
//! 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`
|
//! ### `bevy/spawn`
|
||||||
//!
|
//!
|
||||||
|
|||||||
@ -1,60 +1,59 @@
|
|||||||
//! A simple command line client that allows issuing queries to a remote Bevy
|
//! A simple command line client that allows issuing queries to a remote Bevy
|
||||||
//! app via the BRP.
|
//! 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 anyhow::Result as AnyhowResult;
|
||||||
use argh::FromArgs;
|
use bevy::{
|
||||||
use bevy::remote::{
|
ecs::hierarchy::ChildOf,
|
||||||
builtin_methods::{BrpQuery, BrpQueryFilter, BrpQueryParams, BRP_QUERY_METHOD},
|
prelude::info,
|
||||||
http::DEFAULT_ADDR,
|
remote::{
|
||||||
http::DEFAULT_PORT,
|
builtin_methods::{
|
||||||
|
BrpQuery, BrpQueryFilter, BrpQueryParams, ComponentSelector, BRP_QUERY_METHOD,
|
||||||
|
},
|
||||||
|
http::{DEFAULT_ADDR, DEFAULT_PORT},
|
||||||
BrpRequest,
|
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.
|
/// The application entry point.
|
||||||
fn main() -> AnyhowResult<()> {
|
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.
|
// 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}/");
|
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"),
|
jsonrpc: String::from("2.0"),
|
||||||
method: String::from(BRP_QUERY_METHOD),
|
method: String::from(BRP_QUERY_METHOD),
|
||||||
id: Some(serde_json::to_value(1)?),
|
id: Some(serde_json::to_value(1)?),
|
||||||
params: Some(
|
params: Some(
|
||||||
serde_json::to_value(BrpQueryParams {
|
serde_json::to_value(BrpQueryParams {
|
||||||
data: BrpQuery {
|
data: BrpQuery {
|
||||||
components: args.components,
|
components: Vec::default(),
|
||||||
option: Vec::default(),
|
option: ComponentSelector::All,
|
||||||
has: Vec::default(),
|
has: Vec::default(),
|
||||||
},
|
},
|
||||||
strict: false,
|
strict: false,
|
||||||
@ -63,13 +62,67 @@ fn main() -> AnyhowResult<()> {
|
|||||||
.expect("Unable to convert query parameters to a valid JSON value"),
|
.expect("Unable to convert query parameters to a valid JSON value"),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
info!("query_all req: {query_all_req:#?}");
|
||||||
let res = ureq::post(&url)
|
let query_all_res = ureq::post(url)
|
||||||
.send_json(req)?
|
.send_json(query_all_req)?
|
||||||
.body_mut()
|
.body_mut()
|
||||||
.read_json::<serde_json::Value>()?;
|
.read_json::<serde_json::Value>()?;
|
||||||
|
info!("{query_all_res:#}");
|
||||||
println!("{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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
//! A Bevy app that you can connect to with the BRP and edit.
|
//! 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::math::ops::cos;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user