Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BRP method to mutate a component #16940

Merged
merged 9 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 91 additions & 2 deletions crates/bevy_remote/src/builtin_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the convention is for multiple words in a method name is. Should the method be named differently? I'm trying to consider future methods for modifying other things like Resource's.

I had a few other ideas for methods:

  • bevy/modify/component
  • bevy/mutate/component
  • bevy/component_mut this feels too Rusty in this context


/// The method path for a `bevy/get+watch` request.
pub const BRP_GET_AND_WATCH_METHOD: &str = "bevy/get+watch";

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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>>,
Expand Down
17 changes: 17 additions & 0 deletions crates/bevy_remote/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
Loading