use core::any::TypeId;
use anyhow::{anyhow, Result as AnyhowResult};
use bevy_ecs::{
component::ComponentId,
entity::Entity,
hierarchy::ChildOf,
lifecycle::RemovedComponentEntity,
message::MessageCursor,
query::QueryBuilder,
reflect::{AppTypeRegistry, ReflectComponent, ReflectEvent, ReflectResource},
system::{In, Local},
world::{EntityRef, EntityWorldMut, FilteredEntityRef, Mut, World},
};
use bevy_log::warn_once;
use bevy_platform::collections::HashMap;
use bevy_reflect::{
serde::{ReflectSerializer, TypedReflectDeserializer},
DynamicStruct, GetPath, PartialReflect, TypeRegistration, TypeRegistry,
};
use serde::{de::DeserializeSeed as _, de::IntoDeserializer, Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::{
error_codes,
schemas::{
json_schema::{export_type, JsonSchemaBevyType},
open_rpc::OpenRpcDocument,
},
BrpError, BrpResult,
};
#[cfg(all(feature = "http", not(target_family = "wasm")))]
use {crate::schemas::open_rpc::ServerObject, bevy_utils::default};
pub const BRP_GET_COMPONENTS_METHOD: &str = "world.get_components";
pub const BRP_QUERY_METHOD: &str = "world.query";
pub const BRP_SPAWN_ENTITY_METHOD: &str = "world.spawn_entity";
pub const BRP_INSERT_COMPONENTS_METHOD: &str = "world.insert_components";
pub const BRP_REMOVE_COMPONENTS_METHOD: &str = "world.remove_components";
pub const BRP_DESPAWN_COMPONENTS_METHOD: &str = "world.despawn_entity";
pub const BRP_REPARENT_ENTITIES_METHOD: &str = "world.reparent_entities";
pub const BRP_LIST_COMPONENTS_METHOD: &str = "world.list_components";
pub const BRP_MUTATE_COMPONENTS_METHOD: &str = "world.mutate_components";
pub const BRP_GET_COMPONENTS_AND_WATCH_METHOD: &str = "world.get_components+watch";
pub const BRP_LIST_COMPONENTS_AND_WATCH_METHOD: &str = "world.list_components+watch";
pub const BRP_GET_RESOURCE_METHOD: &str = "world.get_resources";
pub const BRP_INSERT_RESOURCE_METHOD: &str = "world.insert_resources";
pub const BRP_REMOVE_RESOURCE_METHOD: &str = "world.remove_resources";
pub const BRP_MUTATE_RESOURCE_METHOD: &str = "world.mutate_resources";
pub const BRP_LIST_RESOURCES_METHOD: &str = "world.list_resources";
pub const BRP_TRIGGER_EVENT_METHOD: &str = "world.trigger_event";
pub const BRP_REGISTRY_SCHEMA_METHOD: &str = "registry.schema";
pub const RPC_DISCOVER_METHOD: &str = "rpc.discover";
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpGetComponentsParams {
pub entity: Entity,
pub components: Vec<String>,
#[serde(default)]
pub strict: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpGetResourcesParams {
pub resource: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpQueryParams {
pub data: BrpQuery,
#[serde(default)]
pub filter: BrpQueryFilter,
#[serde(default)]
pub strict: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpSpawnEntityParams {
pub components: HashMap<String, Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpDespawnEntityParams {
pub entity: Entity,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpRemoveComponentsParams {
pub entity: Entity,
pub components: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpRemoveResourcesParams {
pub resource: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpInsertComponentsParams {
pub entity: Entity,
pub components: HashMap<String, Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpInsertResourcesParams {
pub resource: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpReparentEntitiesParams {
pub entities: Vec<Entity>,
#[serde(default)]
pub parent: Option<Entity>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpListComponentsParams {
pub entity: Entity,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpMutateComponentsParams {
pub entity: Entity,
pub component: String,
pub path: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpMutateResourcesParams {
pub resource: String,
pub path: String,
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
struct BrpTriggerEventParams {
pub event: String,
pub value: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
pub struct BrpQuery {
#[serde(default)]
pub components: Vec<String>,
#[serde(default)]
pub option: ComponentSelector,
#[serde(default)]
pub has: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
pub struct BrpQueryFilter {
#[serde(default)]
pub without: Vec<String>,
#[serde(default)]
pub with: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
pub struct BrpJsonSchemaQueryFilter {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub without_crates: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub with_crates: Vec<String>,
#[serde(default)]
pub type_limit: JsonSchemaTypeLimit,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
pub struct JsonSchemaTypeLimit {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub without: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub with: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpSpawnEntityResponse {
pub entity: Entity,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum BrpGetComponentsResponse {
Lenient {
components: HashMap<String, Value>,
errors: HashMap<String, Value>,
},
Strict(HashMap<String, Value>),
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpGetResourcesResponse {
pub value: Value,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum BrpGetComponentsWatchingResponse {
Lenient {
components: HashMap<String, Value>,
removed: Vec<String>,
errors: HashMap<String, Value>,
},
Strict {
components: HashMap<String, Value>,
removed: Vec<String>,
},
}
pub type BrpListComponentsResponse = Vec<String>;
pub type BrpListResourcesResponse = Vec<String>;
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpListComponentsWatchingResponse {
added: Vec<String>,
removed: Vec<String>,
}
pub type BrpQueryResponse = Vec<BrpQueryRow>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpQueryRow {
pub entity: Entity,
pub components: HashMap<String, Value>,
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
pub has: HashMap<String, Value>,
}
pub fn parse<T: for<'de> Deserialize<'de>>(value: Value) -> Result<T, BrpError> {
serde_json::from_value(value).map_err(|err| BrpError {
code: error_codes::INVALID_PARAMS,
message: err.to_string(),
data: None,
})
}
pub fn parse_some<T: for<'de> Deserialize<'de>>(value: Option<Value>) -> Result<T, BrpError> {
match value {
Some(value) => parse(value),
None => Err(BrpError {
code: error_codes::INVALID_PARAMS,
message: String::from("Params not provided"),
data: None,
}),
}
}
pub fn process_remote_get_components_request(
In(params): In<Option<Value>>,
world: &World,
) -> BrpResult {
let BrpGetComponentsParams {
entity,
components,
strict,
} = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>();
let type_registry = app_type_registry.read();
let entity_ref = get_entity(world, entity)?;
let response =
reflect_components_to_response(components, strict, entity, entity_ref, &type_registry)?;
serde_json::to_value(response).map_err(BrpError::internal)
}
pub fn process_remote_get_resources_request(
In(params): In<Option<Value>>,
world: &World,
) -> BrpResult {
let BrpGetResourcesParams {
resource: resource_path,
} = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>();
let type_registry = app_type_registry.read();
let reflect_resource =
get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?;
let Ok(reflected) = reflect_resource.reflect(world) else {
return Err(BrpError::resource_not_present(&resource_path));
};
let reflect_serializer = ReflectSerializer::new(reflected.as_partial_reflect(), &type_registry);
let Value::Object(serialized_object) =
serde_json::to_value(&reflect_serializer).map_err(BrpError::resource_error)?
else {
return Err(BrpError {
code: error_codes::RESOURCE_ERROR,
message: format!("Resource `{resource_path}` could not be serialized"),
data: None,
});
};
let value = serialized_object.into_values().next().ok_or_else(|| {
BrpError::internal(anyhow!("Unexpected format of serialized resource value"))
})?;
let response = BrpGetResourcesResponse { value };
serde_json::to_value(response).map_err(BrpError::internal)
}
pub fn process_remote_get_components_watching_request(
In(params): In<Option<Value>>,
world: &World,
mut removal_cursors: Local<HashMap<ComponentId, MessageCursor<RemovedComponentEntity>>>,
) -> BrpResult<Option<Value>> {
let BrpGetComponentsParams {
entity,
components,
strict,
} = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>();
let type_registry = app_type_registry.read();
let entity_ref = get_entity(world, entity)?;
let mut changed = Vec::new();
let mut removed = Vec::new();
let mut errors = <HashMap<_, _>>::default();
'component_loop: for component_path in components {
let Ok(type_registration) =
get_component_type_registration(&type_registry, &component_path)
else {
let err =
BrpError::component_error(format!("Unknown component type: `{component_path}`"));
if strict {
return Err(err);
}
errors.insert(
component_path,
serde_json::to_value(err).map_err(BrpError::internal)?,
);
continue;
};
let Some(component_id) = world.components().get_valid_id(type_registration.type_id())
else {
let err = BrpError::component_error(format!("Unknown component: `{component_path}`"));
if strict {
return Err(err);
}
errors.insert(
component_path,
serde_json::to_value(err).map_err(BrpError::internal)?,
);
continue;
};
if let Some(ticks) = entity_ref.get_change_ticks_by_id(component_id)
&& ticks.is_changed(world.last_change_tick(), world.read_change_tick())
{
changed.push(component_path);
continue;
};
let Some(events) = world.removed_components().get(component_id) else {
continue;
};
let cursor = removal_cursors
.entry(component_id)
.or_insert_with(|| events.get_cursor());
for event in cursor.read(events) {
if Entity::from(event.clone()) == entity {
removed.push(component_path);
continue 'component_loop;
}
}
}
if changed.is_empty() && removed.is_empty() {
return Ok(None);
}
let response =
reflect_components_to_response(changed, strict, entity, entity_ref, &type_registry)?;
let response = match response {
BrpGetComponentsResponse::Lenient {
components,
errors: mut errs,
} => BrpGetComponentsWatchingResponse::Lenient {
components,
removed,
errors: {
errs.extend(errors);
errs
},
},
BrpGetComponentsResponse::Strict(components) => BrpGetComponentsWatchingResponse::Strict {
components,
removed,
},
};
Ok(Some(
serde_json::to_value(response).map_err(BrpError::internal)?,
))
}
fn reflect_components_to_response(
components: Vec<String>,
strict: bool,
entity: Entity,
entity_ref: EntityRef,
type_registry: &TypeRegistry,
) -> BrpResult<BrpGetComponentsResponse> {
let mut response = if strict {
BrpGetComponentsResponse::Strict(Default::default())
} else {
BrpGetComponentsResponse::Lenient {
components: Default::default(),
errors: Default::default(),
}
};
for component_path in components {
match reflect_component(&component_path, entity, entity_ref, type_registry) {
Ok(serialized_object) => match response {
BrpGetComponentsResponse::Strict(ref mut components)
| BrpGetComponentsResponse::Lenient {
ref mut components, ..
} => {
components.extend(serialized_object.into_iter());
}
},
Err(err) => match response {
BrpGetComponentsResponse::Strict(_) => return Err(err),
BrpGetComponentsResponse::Lenient { ref mut errors, .. } => {
let err_value = serde_json::to_value(err).map_err(BrpError::internal)?;
errors.insert(component_path, err_value);
}
},
}
}
Ok(response)
}
fn reflect_component(
component_path: &str,
entity: Entity,
entity_ref: EntityRef,
type_registry: &TypeRegistry,
) -> BrpResult<Map<String, Value>> {
let reflect_component =
get_reflect_component(type_registry, component_path).map_err(BrpError::component_error)?;
let Some(reflected) = reflect_component.reflect(entity_ref) else {
return Err(BrpError::component_not_present(component_path, entity));
};
let reflect_serializer = ReflectSerializer::new(reflected.as_partial_reflect(), type_registry);
let Value::Object(serialized_object) =
serde_json::to_value(&reflect_serializer).map_err(BrpError::component_error)?
else {
return Err(BrpError {
code: error_codes::COMPONENT_ERROR,
message: format!("Component `{component_path}` could not be serialized"),
data: None,
});
};
Ok(serialized_object)
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ComponentSelector {
All,
#[serde(untagged)]
Paths(Vec<String>),
}
impl Default for ComponentSelector {
fn default() -> Self {
Self::Paths(Vec::default())
}
}
pub fn process_remote_query_request(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
let BrpQueryParams {
data: BrpQuery {
components,
option,
has,
},
filter,
strict,
} = 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 (required, unregistered_in_required) =
get_component_ids(&type_registry, world, components.clone(), strict)
.map_err(BrpError::component_error)?;
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()),
};
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, 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)?;
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 &required {
query.ref_id(*component);
}
for (_, option) in &optional {
query.optional(|query| {
query.ref_id(*option);
});
}
for (_, has) in &has_ids {
query.optional(|query| {
query.ref_id(*has);
});
}
for (_, without) in without {
query.without_id(without);
}
for (_, with) in with {
query.with_id(with);
}
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) {
let entity_id = row.id();
let entity_ref = world.get_entity(entity_id).expect("Entity should exist");
let mut components_map = serialize_components(
entity_ref,
&type_registry,
required
.iter()
.map(|(type_id, component_id)| (*type_id, Some(*component_id))),
);
match &option {
ComponentSelector::All => {
let all_optionals =
entity_ref
.archetype()
.components()
.iter()
.filter_map(|&component_id| {
let info = world.components().get_info(component_id)?;
let type_id = info.type_id()?;
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(_) => {
let optionals = optional.iter().filter(|(_, component_id)| {
!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))),
));
}
}
let has_map = build_has_map(
row,
has_paths_and_reflect_components.iter().copied(),
&unregistered_in_has,
);
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)
}
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 let Some(component_id) = component_id_opt
&& !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
}
pub fn process_remote_spawn_entity_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpSpawnEntityParams { components } = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let reflect_components =
deserialize_components(&type_registry, components).map_err(BrpError::component_error)?;
let entity = world.spawn_empty();
let entity_id = entity.id();
insert_reflected_components(entity, reflect_components).map_err(BrpError::component_error)?;
let response = BrpSpawnEntityResponse { entity: entity_id };
serde_json::to_value(response).map_err(BrpError::internal)
}
pub fn process_remote_list_methods_request(
In(_params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let remote_methods = world.resource::<crate::RemoteMethods>();
#[cfg(all(feature = "http", not(target_family = "wasm")))]
let servers = match (
world.get_resource::<crate::http::HostAddress>(),
world.get_resource::<crate::http::HostPort>(),
) {
(Some(url), Some(port)) => Some(vec![ServerObject {
name: "Server".to_owned(),
url: format!("{}:{}", url.0, port.0),
..default()
}]),
(Some(url), None) => Some(vec![ServerObject {
name: "Server".to_owned(),
url: url.0.to_string(),
..default()
}]),
_ => None,
};
#[cfg(any(not(feature = "http"), target_family = "wasm"))]
let servers = None;
let doc = OpenRpcDocument {
info: Default::default(),
methods: remote_methods.into(),
openrpc: "1.3.2".to_owned(),
servers,
};
serde_json::to_value(doc).map_err(BrpError::internal)
}
pub fn process_remote_insert_components_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpInsertComponentsParams { entity, components } = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let reflect_components =
deserialize_components(&type_registry, components).map_err(BrpError::component_error)?;
insert_reflected_components(get_entity_mut(world, entity)?, reflect_components)
.map_err(BrpError::component_error)?;
Ok(Value::Null)
}
pub fn process_remote_insert_resources_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpInsertResourcesParams {
resource: resource_path,
value,
} = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let reflected_resource = deserialize_resource(&type_registry, &resource_path, value)
.map_err(BrpError::resource_error)?;
let reflect_resource =
get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?;
reflect_resource.insert(world, &*reflected_resource, &type_registry);
Ok(Value::Null)
}
pub fn process_remote_mutate_components_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpMutateComponentsParams {
entity,
component,
path,
value,
} = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let component_type: &TypeRegistration = type_registry
.get_with_type_path(&component)
.ok_or_else(|| {
BrpError::component_error(anyhow!("Unknown component type: `{}`", component))
})?;
let mut reflected = component_type
.data::<ReflectComponent>()
.ok_or_else(|| {
BrpError::component_error(anyhow!("Component `{}` isn't registered", component))
})?
.reflect_mut(world.entity_mut(entity))
.ok_or_else(|| {
BrpError::component_error(anyhow!("Cannot reflect component `{}`", component))
})?;
let value_type: &TypeRegistration = type_registry
.get_with_type_path(
reflected
.reflect_path(path.as_str())
.map_err(BrpError::component_error)?
.reflect_type_path(),
)
.ok_or_else(|| {
BrpError::component_error(anyhow!("Unknown component field type: `{}`", component))
})?;
let value: Box<dyn PartialReflect> = TypedReflectDeserializer::new(value_type, &type_registry)
.deserialize(&value)
.map_err(BrpError::component_error)?;
reflected
.reflect_path_mut(path.as_str())
.map_err(BrpError::component_error)?
.try_apply(value.as_ref())
.map_err(BrpError::component_error)?;
Ok(Value::Null)
}
pub fn process_remote_mutate_resources_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpMutateResourcesParams {
resource: resource_path,
path: field_path,
value,
} = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let reflect_resource =
get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?;
let mut reflected_resource = reflect_resource
.reflect_mut(world)
.map_err(|_| BrpError::resource_not_present(&resource_path))?;
let value_registration = type_registry
.get_with_type_path(
reflected_resource
.reflect_path(field_path.as_str())
.map_err(BrpError::resource_error)?
.reflect_type_path(),
)
.ok_or_else(|| {
BrpError::resource_error(anyhow!("Unknown resource field type: `{}`", resource_path))
})?;
let deserialized_value: Box<dyn PartialReflect> =
TypedReflectDeserializer::new(value_registration, &type_registry)
.deserialize(&value)
.map_err(BrpError::resource_error)?;
reflected_resource
.reflect_path_mut(field_path.as_str())
.map_err(BrpError::resource_error)?
.try_apply(&*deserialized_value)
.map_err(BrpError::resource_error)?;
Ok(Value::Null)
}
pub fn process_remote_remove_components_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpRemoveComponentsParams { entity, components } = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let component_ids = get_component_ids(&type_registry, world, components, true)
.and_then(|(registered, unregistered)| {
if unregistered.is_empty() {
Ok(registered)
} else {
Err(anyhow!("Unregistered component types: {:?}", unregistered))
}
})
.map_err(BrpError::component_error)?;
let mut entity_world_mut = get_entity_mut(world, entity)?;
for (_, component_id) in component_ids.iter() {
entity_world_mut.remove_by_id(*component_id);
}
Ok(Value::Null)
}
pub fn process_remote_remove_resources_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpRemoveResourcesParams {
resource: resource_path,
} = parse_some(params)?;
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let reflect_resource =
get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?;
reflect_resource.remove(world);
Ok(Value::Null)
}
pub fn process_remote_despawn_entity_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpDespawnEntityParams { entity } = parse_some(params)?;
get_entity_mut(world, entity)?.despawn();
Ok(Value::Null)
}
pub fn process_remote_reparent_entities_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpReparentEntitiesParams {
entities,
parent: maybe_parent,
} = parse_some(params)?;
if let Some(parent) = maybe_parent {
let mut parent_commands =
get_entity_mut(world, parent).map_err(|_| BrpError::entity_not_found(parent))?;
for entity in entities {
if entity == parent {
return Err(BrpError::self_reparent(entity));
}
parent_commands.add_child(entity);
}
}
else {
for entity in entities {
get_entity_mut(world, entity)?.remove::<ChildOf>();
}
}
Ok(Value::Null)
}
pub fn process_remote_list_components_request(
In(params): In<Option<Value>>,
world: &World,
) -> BrpResult {
let app_type_registry = world.resource::<AppTypeRegistry>();
let type_registry = app_type_registry.read();
let mut response = BrpListComponentsResponse::default();
if let Some(BrpListComponentsParams { entity }) = params.map(parse).transpose()? {
let entity = get_entity(world, entity)?;
for &component_id in entity.archetype().components().iter() {
let Some(component_info) = world.components().get_info(component_id) else {
continue;
};
response.push(component_info.name().to_string());
}
}
else {
for registered_type in type_registry.iter() {
if registered_type.data::<ReflectComponent>().is_some() {
response.push(registered_type.type_info().type_path().to_owned());
}
}
}
response.sort();
serde_json::to_value(response).map_err(BrpError::internal)
}
pub fn process_remote_list_resources_request(
In(_params): In<Option<Value>>,
world: &World,
) -> BrpResult {
let mut response = BrpListResourcesResponse::default();
let app_type_registry = world.resource::<AppTypeRegistry>();
let type_registry = app_type_registry.read();
for registered_type in type_registry.iter() {
if registered_type.data::<ReflectResource>().is_some() {
response.push(registered_type.type_info().type_path().to_owned());
}
}
response.sort();
serde_json::to_value(response).map_err(BrpError::internal)
}
pub fn process_remote_list_components_watching_request(
In(params): In<Option<Value>>,
world: &World,
mut removal_cursors: Local<HashMap<ComponentId, MessageCursor<RemovedComponentEntity>>>,
) -> BrpResult<Option<Value>> {
let BrpListComponentsParams { entity } = parse_some(params)?;
let entity_ref = get_entity(world, entity)?;
let mut response = BrpListComponentsWatchingResponse::default();
for &component_id in entity_ref.archetype().components().iter() {
let ticks = entity_ref
.get_change_ticks_by_id(component_id)
.ok_or(BrpError::internal("Failed to get ticks"))?;
if ticks.is_added(world.last_change_tick(), world.read_change_tick()) {
let Some(component_info) = world.components().get_info(component_id) else {
continue;
};
response.added.push(component_info.name().to_string());
}
}
for (component_id, events) in world.removed_components().iter() {
let cursor = removal_cursors
.entry(*component_id)
.or_insert_with(|| events.get_cursor());
for event in cursor.read(events) {
if Entity::from(event.clone()) == entity {
let Some(component_info) = world.components().get_info(*component_id) else {
continue;
};
response.removed.push(component_info.name().to_string());
}
}
}
if response.added.is_empty() && response.removed.is_empty() {
Ok(None)
} else {
Ok(Some(
serde_json::to_value(response).map_err(BrpError::internal)?,
))
}
}
pub fn process_remote_trigger_event_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpTriggerEventParams { event, value } = parse_some(params)?;
world.resource_scope(|world, registry: Mut<AppTypeRegistry>| {
let registry = registry.read();
let Some(registration) = registry.get_with_type_path(&event) else {
return Err(BrpError::resource_error(format!(
"Unknown event type: `{event}`"
)));
};
let Some(reflect_event) = registration.data::<ReflectEvent>() else {
return Err(BrpError::resource_error(format!(
"Event `{event}` is not reflectable"
)));
};
if let Some(payload) = value {
let payload: Box<dyn PartialReflect> =
TypedReflectDeserializer::new(registration, ®istry)
.deserialize(payload.into_deserializer())
.map_err(|err| {
BrpError::resource_error(format!("{event} is invalid: {err}"))
})?;
reflect_event.trigger(world, &*payload, ®istry);
} else {
let payload = DynamicStruct::default();
reflect_event.trigger(world, &payload, ®istry);
}
Ok(Value::Null)
})
}
pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> BrpResult {
let filter: BrpJsonSchemaQueryFilter = match params {
None => Default::default(),
Some(params) => parse(params)?,
};
let extra_info = world.resource::<crate::schemas::SchemaTypesMetadata>();
let types = world.resource::<AppTypeRegistry>();
let types = types.read();
let schemas = types
.iter()
.filter_map(|type_reg| {
let path_table = type_reg.type_info().type_path_table();
if let Some(crate_name) = &path_table.crate_name() {
if !filter.with_crates.is_empty()
&& !filter.with_crates.iter().any(|c| crate_name.eq(c))
{
return None;
}
if !filter.without_crates.is_empty()
&& filter.without_crates.iter().any(|c| crate_name.eq(c))
{
return None;
}
}
let (id, schema) = export_type(type_reg, extra_info);
if !filter.type_limit.with.is_empty()
&& !filter
.type_limit
.with
.iter()
.any(|c| schema.reflect_types.iter().any(|cc| c.eq(cc)))
{
return None;
}
if !filter.type_limit.without.is_empty()
&& filter
.type_limit
.without
.iter()
.any(|c| schema.reflect_types.iter().any(|cc| c.eq(cc)))
{
return None;
}
Some((id.to_string(), schema))
})
.collect::<HashMap<String, JsonSchemaBevyType>>();
serde_json::to_value(schemas).map_err(BrpError::internal)
}
fn get_entity(world: &World, entity: Entity) -> Result<EntityRef<'_>, BrpError> {
world
.get_entity(entity)
.map_err(|_| BrpError::entity_not_found(entity))
}
fn get_entity_mut(world: &mut World, entity: Entity) -> Result<EntityWorldMut<'_>, BrpError> {
world
.get_entity_mut(entity)
.map_err(|_| BrpError::entity_not_found(entity))
}
fn get_component_ids(
type_registry: &TypeRegistry,
world: &World,
component_paths: Vec<String>,
strict: bool,
) -> AnyhowResult<(Vec<(TypeId, ComponentId)>, Vec<String>)> {
let mut component_ids = vec![];
let mut unregistered_components = vec![];
for component_path in component_paths {
let maybe_component_tuple = get_component_type_registration(type_registry, &component_path)
.ok()
.and_then(|type_registration| {
let type_id = type_registration.type_id();
world
.components()
.get_valid_id(type_id)
.map(|component_id| (type_id, component_id))
});
if let Some((type_id, component_id)) = maybe_component_tuple {
component_ids.push((type_id, component_id));
} else if strict {
return Err(anyhow!(
"Component `{}` isn't registered or used in the world",
component_path
));
} else {
unregistered_components.push(component_path);
}
}
Ok((component_ids, unregistered_components))
}
fn build_has_map<'a>(
entity_ref: FilteredEntityRef,
paths_and_reflect_components: impl Iterator<Item = (&'a str, &'a ReflectComponent)>,
unregistered_components: &[String],
) -> HashMap<String, Value> {
let mut has_map = <HashMap<_, _>>::default();
for (type_path, reflect_component) in paths_and_reflect_components {
let has = reflect_component.contains(entity_ref);
has_map.insert(type_path.to_owned(), Value::Bool(has));
}
unregistered_components.iter().for_each(|component| {
has_map.insert(component.to_owned(), Value::Bool(false));
});
has_map
}
fn reflect_component_from_id(
component_type_id: TypeId,
type_registry: &TypeRegistry,
) -> AnyhowResult<(&str, &ReflectComponent)> {
let Some(type_registration) = type_registry.get(component_type_id) else {
return Err(anyhow!(
"Component `{:?}` isn't registered",
component_type_id
));
};
let type_path = type_registration.type_info().type_path();
let Some(reflect_component) = type_registration.data::<ReflectComponent>() else {
return Err(anyhow!("Component `{}` isn't reflectable", type_path));
};
Ok((type_path, reflect_component))
}
fn deserialize_components(
type_registry: &TypeRegistry,
components: HashMap<String, Value>,
) -> AnyhowResult<Vec<Box<dyn PartialReflect>>> {
let mut reflect_components = vec![];
for (component_path, component) in components {
let Some(component_type) = type_registry.get_with_type_path(&component_path) else {
return Err(anyhow!("Unknown component type: `{}`", component_path));
};
let reflected: Box<dyn PartialReflect> =
TypedReflectDeserializer::new(component_type, type_registry)
.deserialize(&component)
.map_err(|err| anyhow!("{component_path} is invalid: {err}"))?;
reflect_components.push(reflected);
}
Ok(reflect_components)
}
fn deserialize_resource(
type_registry: &TypeRegistry,
resource_path: &str,
value: Value,
) -> AnyhowResult<Box<dyn PartialReflect>> {
let Some(resource_type) = type_registry.get_with_type_path(resource_path) else {
return Err(anyhow!("Unknown resource type: `{}`", resource_path));
};
let reflected: Box<dyn PartialReflect> =
TypedReflectDeserializer::new(resource_type, type_registry)
.deserialize(&value)
.map_err(|err| anyhow!("{resource_path} is invalid: {err}"))?;
Ok(reflected)
}
fn insert_reflected_components(
mut entity_world_mut: EntityWorldMut,
reflect_components: Vec<Box<dyn PartialReflect>>,
) -> AnyhowResult<()> {
for reflected in reflect_components {
entity_world_mut.insert_reflect(reflected);
}
Ok(())
}
fn get_reflect_component<'r>(
type_registry: &'r TypeRegistry,
component_path: &str,
) -> AnyhowResult<&'r ReflectComponent> {
let component_registration = get_component_type_registration(type_registry, component_path)?;
component_registration
.data::<ReflectComponent>()
.ok_or_else(|| anyhow!("Component `{}` isn't reflectable", component_path))
}
fn get_component_type_registration<'r>(
type_registry: &'r TypeRegistry,
component_path: &str,
) -> AnyhowResult<&'r TypeRegistration> {
type_registry
.get_with_type_path(component_path)
.ok_or_else(|| anyhow!("Unknown component type: `{}`", component_path))
}
fn get_reflect_resource<'r>(
type_registry: &'r TypeRegistry,
resource_path: &str,
) -> AnyhowResult<&'r ReflectResource> {
let resource_registration = get_resource_type_registration(type_registry, resource_path)?;
resource_registration
.data::<ReflectResource>()
.ok_or_else(|| anyhow!("Resource `{}` isn't reflectable", resource_path))
}
fn get_resource_type_registration<'r>(
type_registry: &'r TypeRegistry,
resource_path: &str,
) -> AnyhowResult<&'r TypeRegistration> {
type_registry
.get_with_type_path(resource_path)
.ok_or_else(|| anyhow!("Unknown resource type: `{}`", resource_path))
}
#[cfg(test)]
mod tests {
fn test_serialize_deserialize<T>(value: T)
where
T: Serialize + for<'a> Deserialize<'a> + PartialEq + core::fmt::Debug,
{
let serialized = serde_json::to_string(&value).expect("Failed to serialize");
let deserialized: T = serde_json::from_str(&serialized).expect("Failed to deserialize");
assert_eq!(
&value, &deserialized,
"Deserialized value does not match original"
);
}
use super::*;
use bevy_ecs::{
component::Component, event::Event, observer::On, resource::Resource, system::ResMut,
};
use bevy_reflect::Reflect;
use serde_json::Value::Null;
#[test]
fn insert_reflect_only_component() {
#[derive(Reflect, Component)]
#[reflect(Component)]
struct Player {
name: String,
health: u32,
}
let components: HashMap<String, Value> = [(
String::from("bevy_remote::builtin_methods::tests::Player"),
serde_json::json!({"name": "John", "health": 50}),
)]
.into();
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<Player>();
}
let deserialized_components = {
let type_reg = atr.read();
deserialize_components(&type_reg, components).expect("FAIL")
};
let mut world = World::new();
world.insert_resource(atr);
let e = world.spawn_empty();
insert_reflected_components(e, deserialized_components).expect("FAIL");
}
#[test]
fn trigger_reflect_only_event() {
#[derive(Event, Reflect)]
#[reflect(Event)]
struct Pass;
#[derive(Resource)]
struct TestResult(pub bool);
let atr = AppTypeRegistry::default();
{
let mut register = atr.write();
register.register::<Pass>();
}
let mut world = World::new();
world.add_observer(move |_event: On<Pass>, mut result: ResMut<TestResult>| result.0 = true);
world.insert_resource(TestResult(false));
world.insert_resource(atr);
let params = serde_json::to_value(&BrpTriggerEventParams {
event: "bevy_remote::builtin_methods::tests::Pass".to_owned(),
value: None,
})
.expect("FAIL");
assert_eq!(
process_remote_trigger_event_request(In(Some(params)), &mut world),
Ok(Null)
);
assert!(world.resource::<TestResult>().0);
}
#[test]
fn serialization_tests() {
test_serialize_deserialize(BrpQueryRow {
components: Default::default(),
entity: Entity::from_raw_u32(0).unwrap(),
has: Default::default(),
});
test_serialize_deserialize(BrpListComponentsWatchingResponse::default());
test_serialize_deserialize(BrpQuery::default());
test_serialize_deserialize(BrpJsonSchemaQueryFilter::default());
test_serialize_deserialize(BrpJsonSchemaQueryFilter {
type_limit: JsonSchemaTypeLimit {
with: vec!["Resource".to_owned()],
..Default::default()
},
..Default::default()
});
test_serialize_deserialize(BrpListComponentsParams {
entity: Entity::from_raw_u32(0).unwrap(),
});
}
}