diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index ce5fa259a1..b980c0cc53 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -718,17 +718,27 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W let app_type_registry = world.resource::().clone(); let type_registry = app_type_registry.read(); - let components = get_component_ids(&type_registry, world, components, strict) + let (components, unregistered_in_components) = + get_component_ids(&type_registry, world, components, strict) + .map_err(BrpError::component_error)?; + let (option, _) = get_component_ids(&type_registry, world, option, strict) .map_err(BrpError::component_error)?; - let option = get_component_ids(&type_registry, world, option, strict) - .map_err(BrpError::component_error)?; - let has = + let (has, 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) + let (without, _) = get_component_ids(&type_registry, world, without, strict) .map_err(BrpError::component_error)?; - let with = get_component_ids(&type_registry, world, with, strict) + let (with, unregistered_in_with) = get_component_ids(&type_registry, world, with, 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() { + return serde_json::to_value(BrpQueryResponse::default()).map_err(BrpError::internal); + } + let mut query = QueryBuilder::::new(world); for (_, component) in &components { query.ref_id(*component); @@ -784,6 +794,7 @@ pub fn process_remote_query_request(In(params): In>, world: &mut W let has_map = build_has_map( row.clone(), has_paths_and_reflect_components.iter().copied(), + &unregistered_in_has, ); response.push(BrpQueryRow { entity: row.id(), @@ -1024,12 +1035,19 @@ pub fn process_remote_remove_request( 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)?; // Remove the components. let mut entity_world_mut = get_entity_mut(world, entity)?; - for (_, component_id) in component_ids { - entity_world_mut.remove_by_id(component_id); + for (_, component_id) in component_ids.iter() { + entity_world_mut.remove_by_id(*component_id); } Ok(Value::Null) @@ -1264,8 +1282,9 @@ fn get_entity_mut(world: &mut World, entity: Entity) -> Result, strict: bool, -) -> AnyhowResult> { +) -> AnyhowResult<(Vec<(TypeId, ComponentId)>, Vec)> { let mut component_ids = vec![]; + let mut unregistered_components = vec![]; for component_path in component_paths { - let type_id = get_component_type_registration(type_registry, &component_path)?.type_id(); - let Some(component_id) = world.components().get_id(type_id) else { - if strict { - return Err(anyhow!( - "Component `{}` isn't used in the world", - component_path - )); - } - continue; - }; - - component_ids.push((type_id, component_id)); + 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_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) + Ok((component_ids, unregistered_components)) } /// Given an entity (`entity_ref`) and a list of reflected component information @@ -1325,12 +1352,16 @@ fn build_components_map<'a>( Ok(serialized_components_map) } -/// Given an entity (`entity_ref`) and list of reflected component information -/// (`paths_and_reflect_components`), return a map which associates each component to -/// a boolean value indicating whether or not that component is present on the entity. +/// Given an entity (`entity_ref`), +/// a list of reflected component information (`paths_and_reflect_components`) +/// and a list of unregistered components, +/// return a map which associates each component to a boolean value indicating +/// whether or not that component is present on the entity. +/// Unregistered components are considered absent from the entity. fn build_has_map<'a>( entity_ref: FilteredEntityRef, paths_and_reflect_components: impl Iterator, + unregistered_components: &[String], ) -> HashMap { let mut has_map = >::default(); @@ -1338,6 +1369,9 @@ fn build_has_map<'a>( let has = reflect_component.contains(entity_ref.clone()); 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 }