Add BRP method to mutate a component (#16940)
# Objective Add a method to mutate components with BRP. Currently the only way to modify a component on an entity with BRP is to insert a new one with the new values. This isn't ideal for several reasons, one reason being that the client has to know what all the fields are of the component and stay in sync with the server. ## Solution Add a new BRP method called `bevy/mutate_component` to mutate a single field in a component on an entity. ## Testing Tested on a simple scene on all `Transform`, `Name`, and a custom component. --- ## Showcase Example JSON-RPC request to change the `Name` of an entity to "New name!" ```json { "jsonrpc": "2.0", "id": 0, "method": "bevy/mutate_component", "params": { "entity": 4294967308, "component": "bevy_ecs::name::Name", "path": ".name", "value": "New name!" } } ``` Or setting the X translation to 10.0 on a Transform: ```json { "jsonrpc": "2.0", "id": 0, "method": "bevy/mutate_component", "params": { "entity": 4294967308, "component": "bevy_transform::components::transform::Transform", "path": ".translation.x", "value": 10.0 } } ``` Clip of my Emacs BRP package using this method: https://github.com/user-attachments/assets/a786b245-5c20-4189-859f-2261c5086a68 --------- Co-authored-by: François Mockers <mockersf@gmail.com>
This commit is contained in:
parent
41fd280596
commit
b0beeab41a
@ -17,8 +17,8 @@ use bevy_hierarchy::BuildChildren as _;
|
||||
use bevy_reflect::{
|
||||
prelude::ReflectDefault,
|
||||
serde::{ReflectSerializer, TypedReflectDeserializer},
|
||||
NamedField, OpaqueInfo, PartialReflect, ReflectDeserialize, ReflectSerialize, TypeInfo,
|
||||
TypeRegistration, TypeRegistry, VariantInfo,
|
||||
GetPath as _, NamedField, OpaqueInfo, PartialReflect, ReflectDeserialize, ReflectSerialize,
|
||||
TypeInfo, TypeRegistration, TypeRegistry, VariantInfo,
|
||||
};
|
||||
use bevy_utils::HashMap;
|
||||
use serde::{de::DeserializeSeed as _, Deserialize, Serialize};
|
||||
@ -50,6 +50,9 @@ pub const BRP_REPARENT_METHOD: &str = "bevy/reparent";
|
||||
/// The method path for a `bevy/list` request.
|
||||
pub const BRP_LIST_METHOD: &str = "bevy/list";
|
||||
|
||||
/// The method path for a `bevy/reparent` request.
|
||||
pub const BRP_MUTATE_COMPONENT_METHOD: &str = "bevy/mutate_component";
|
||||
|
||||
/// The method path for a `bevy/get+watch` request.
|
||||
pub const BRP_GET_AND_WATCH_METHOD: &str = "bevy/get+watch";
|
||||
|
||||
@ -197,6 +200,28 @@ pub struct BrpListParams {
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
/// `bevy/mutate`:
|
||||
///
|
||||
/// The server responds with a null.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct BrpMutateParams {
|
||||
/// The entity of the component to mutate.
|
||||
pub entity: Entity,
|
||||
|
||||
/// The [full path] of component to mutate.
|
||||
///
|
||||
/// [full path]: bevy_reflect::TypePath::type_path
|
||||
pub component: String,
|
||||
|
||||
/// The [path] of the field within the component.
|
||||
///
|
||||
/// [path]: bevy_reflect::GetPath
|
||||
pub path: String,
|
||||
|
||||
/// The value to insert at `path`.
|
||||
pub value: Value,
|
||||
}
|
||||
|
||||
/// Describes the data that is to be fetched in a query.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
|
||||
pub struct BrpQuery {
|
||||
@ -694,6 +719,70 @@ pub fn process_remote_insert_request(
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
/// Handles a `bevy/mutate_component` request coming from a client.
|
||||
///
|
||||
/// This method allows you to mutate a single field inside an Entity's
|
||||
/// component.
|
||||
pub fn process_remote_mutate_component_request(
|
||||
In(params): In<Option<Value>>,
|
||||
world: &mut World,
|
||||
) -> BrpResult {
|
||||
let BrpMutateParams {
|
||||
entity,
|
||||
component,
|
||||
path,
|
||||
value,
|
||||
} = parse_some(params)?;
|
||||
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
|
||||
let type_registry = app_type_registry.read();
|
||||
|
||||
// Get the fully-qualified type names of the component to be mutated.
|
||||
let component_type: &TypeRegistration = type_registry
|
||||
.get_with_type_path(&component)
|
||||
.ok_or_else(|| {
|
||||
BrpError::component_error(anyhow!("Unknown component type: `{}`", component))
|
||||
})?;
|
||||
|
||||
// Get the reflected representation of the 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))
|
||||
})?;
|
||||
|
||||
// Get the type of the field in the component that is to be
|
||||
// mutated.
|
||||
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))
|
||||
})?;
|
||||
|
||||
// Get the reflected representation of the value to be inserted
|
||||
// into the component.
|
||||
let value: Box<dyn PartialReflect> = TypedReflectDeserializer::new(value_type, &type_registry)
|
||||
.deserialize(&value)
|
||||
.map_err(BrpError::component_error)?;
|
||||
|
||||
// Apply the mutation.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Handles a `bevy/remove` request (remove components) coming from a client.
|
||||
pub fn process_remote_remove_request(
|
||||
In(params): In<Option<Value>>,
|
||||
|
@ -194,6 +194,19 @@
|
||||
//!
|
||||
//! `result`: null.
|
||||
//!
|
||||
//! ### `bevy/mutate_component`
|
||||
//!
|
||||
//! Mutate a field in a component.
|
||||
//!
|
||||
//! `params`:
|
||||
//! - `entity`: The ID of the entity to with the component to mutate.
|
||||
//! - `component`: The component's [fully-qualified type name].
|
||||
//! - `path`: The path of the field within the component. See
|
||||
//! [`GetPath`](bevy_reflect::GetPath#syntax) for more information on formatting this string.
|
||||
//! - `value`: The value to insert at `path`.
|
||||
//!
|
||||
//! `result`: null.
|
||||
//!
|
||||
//! ### bevy/reparent
|
||||
//!
|
||||
//! Assign a new parent to one or more entities.
|
||||
@ -419,6 +432,10 @@ impl Default for RemotePlugin {
|
||||
builtin_methods::BRP_REGISTRY_SCHEMA_METHOD,
|
||||
builtin_methods::export_registry_types,
|
||||
)
|
||||
.with_method(
|
||||
builtin_methods::BRP_MUTATE_COMPONENT_METHOD,
|
||||
builtin_methods::process_remote_mutate_component_request,
|
||||
)
|
||||
.with_watching_method(
|
||||
builtin_methods::BRP_GET_AND_WATCH_METHOD,
|
||||
builtin_methods::process_remote_get_watching_request,
|
||||
|
Loading…
Reference in New Issue
Block a user