Skip to content
Closed
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
158 changes: 157 additions & 1 deletion crates/bevy_remote/src/builtin_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use bevy_ecs::{
use bevy_platform_support::collections::HashMap;
use bevy_reflect::{
prelude::ReflectDefault,
serde::{ReflectSerializer, TypedReflectDeserializer},
serde::{ReflectSerializer, TypedReflectDeserializer, TypedReflectSerializer},
GetPath as _, NamedField, OpaqueInfo, PartialReflect, ReflectDeserialize, ReflectSerialize,
TypeInfo, TypeRegistration, TypeRegistry, VariantInfo,
};
Expand Down Expand Up @@ -53,6 +53,15 @@ pub const BRP_LIST_METHOD: &str = "bevy/list";
/// The method path for a `bevy/mutate_component` request.
pub const BRP_MUTATE_COMPONENT_METHOD: &str = "bevy/mutate_component";

/// The method path for a `bevy/resources/list` request.
pub const BRP_LIST_RESOURCES_METHOD: &str = "bevy/resources/list";

/// The method path for a `bevy/resources/get` request.
pub const BRP_GET_RESOURCE_METHOD: &str = "bevy/resources/get";

/// The method path for a `bevy/resources/mutate` request.
pub const BRP_MUTATE_RESOURCE_METHOD: &str = "bevy/resources/mutate";

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

Expand Down Expand Up @@ -200,6 +209,33 @@ pub struct BrpListParams {
pub entity: Entity,
}

/// `bevy/resources/get`: Returns a state of a resource (params provided).
///
/// The server responds with a json value with the state of the resource
///
/// [full path]: bevy_reflect::TypePath::type_path
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpResourceGetParams(pub String);

/// `bevy/resources/mutate`:
///
/// The server responds with a null.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct BrpMutateResourceParams {
/// The [full path] of the resource to mutate.
///
/// [full path]: bevy_reflect::TypePath::type_path
pub resource: 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,
}

/// `bevy/mutate_component`:
///
/// The server responds with a null.
Expand Down Expand Up @@ -927,6 +963,126 @@ pub fn process_remote_list_watching_request(
}
}

/// Handles a `bevy/list_resources` request (list all resources) coming from a client.
pub fn process_remote_resources_list_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 = BrpListResponse::default();

for registered_type in type_registry.iter() {
if registered_type.data::<ReflectResource>().is_some() {
response.push(registered_type.type_info().type_path().to_owned());
}
}

// Sort both for cleanliness and to reduce the risk that clients start
// accidentally depending on the order.
response.sort();

serde_json::to_value(response).map_err(BrpError::internal)
}

/// Handles a `bevy/resources/get` request (get resource) coming from a client.
pub fn process_remote_get_resources_list_request(
In(params): In<Option<Value>>,
world: &World,
) -> BrpResult {
let app_type_registry = world.resource::<AppTypeRegistry>();
let type_registry = app_type_registry.read();
let par: BrpResourceGetParams = parse_some(params)?;

// Get the fully-qualified type names of the component to be mutated.
let res_type: &TypeRegistration = type_registry
.get_with_type_path(&par.0)
.ok_or_else(|| BrpError::component_error(anyhow!("Unknown component type: `{}`", par.0)))?;

let Some(reflect_resource) = res_type.data::<ReflectResource>() else {
return Err(BrpError::component_error(anyhow!(
"Unknown component type: `{}`",
par.0
)));
};

let Some(resource) = reflect_resource.reflect(world) else {
return Err(BrpError::component_error(anyhow!(
"Resource doesn't exist in world: `{}`",
par.0
)));
};

let reflect_serializer =
TypedReflectSerializer::new(resource.as_partial_reflect(), &type_registry);

serde_json::to_value(reflect_serializer).map_err(BrpError::internal)
}

/// Handles a `bevy/resources/mutate` request coming from a client.
///
/// This method allows you to mutate a single field inside an Resource
pub fn process_remote_mutate_resource_request(
In(params): In<Option<Value>>,
world: &mut World,
) -> BrpResult {
let BrpMutateResourceParams {
resource,
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 res_type: &TypeRegistration =
type_registry.get_with_type_path(&resource).ok_or_else(|| {
BrpError::component_error(anyhow!("Unknown component type: `{}`", resource))
})?;

let Some(reflect_resource) = res_type.data::<ReflectResource>() else {
return Err(BrpError::component_error(anyhow!(
"Unknown resource type: `{}`",
resource
)));
};

let Some(mut reflected) = reflect_resource.reflect_mut(world) else {
return Err(BrpError::component_error(anyhow!(
"Resource doesn't exist in world: `{}`",
resource
)));
};
// Get the type of the field in the resource 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: `{}`", resource))
})?;

// Get the reflected representation of the value to be inserted
// into the resource.
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/registry/schema` request (list all registry types in form of schema) coming from a client.
pub fn export_registry_types(In(params): In<Option<Value>>, world: &World) -> BrpResult {
let filter: BrpJsonSchemaQueryFilter = match params {
Expand Down
12 changes: 12 additions & 0 deletions crates/bevy_remote/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,18 @@ impl Default for RemotePlugin {
builtin_methods::BRP_LIST_METHOD,
builtin_methods::process_remote_list_request,
)
.with_method(
builtin_methods::BRP_LIST_RESOURCES_METHOD,
builtin_methods::process_remote_resources_list_request,
)
.with_method(
builtin_methods::BRP_GET_RESOURCE_METHOD,
builtin_methods::process_remote_get_resources_list_request,
)
.with_method(
builtin_methods::BRP_MUTATE_RESOURCE_METHOD,
builtin_methods::process_remote_mutate_resource_request,
)
.with_method(
builtin_methods::BRP_REGISTRY_SCHEMA_METHOD,
builtin_methods::export_registry_types,
Expand Down