//! An implementation of the Bevy Remote Protocol, to allow for remote control of a Bevy app.1//!2//! Adding the [`RemotePlugin`] to your [`App`] will setup everything needed without3//! starting any transports. To start accepting remote connections you will need to4//! add a second plugin like the [`RemoteHttpPlugin`](http::RemoteHttpPlugin) to enable communication5//! over HTTP. These *remote clients* can inspect and alter the state of the6//! entity-component system.7//!8//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.9//!10//! ## Request objects11//!12//! A typical client request might look like this:13//!14//! ```json15//! {16//! "method": "world.get_components",17//! "id": 0,18//! "params": {19//! "entity": 4294967298,20//! "components": [21//! "bevy_transform::components::transform::Transform"22//! ]23//! }24//! }25//! ```26//!27//! The `id` and `method` fields are required. The `params` field may be omitted28//! for certain methods:29//!30//! * `id` is arbitrary JSON data. The server completely ignores its contents,31//! and the client may use it for any purpose. It will be copied via32//! serialization and deserialization (so object property order, etc. can't be33//! relied upon to be identical) and sent back to the client as part of the34//! response.35//!36//! * `method` is a string that specifies one of the possible [`BrpRequest`]37//! variants: `world.query`, `world.get_components`, `world.insert_components`, etc. It's case-sensitive.38//!39//! * `params` is parameter data specific to the request.40//!41//! For more information, see the documentation for [`BrpRequest`].42//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`43//! documentation] may be useful to clarify the correspondence between the Rust44//! structure and the JSON format.45//!46//! ## Response objects47//!48//! A response from the server to the client might look like this:49//!50//! ```json51//! {52//! "jsonrpc": "2.0",53//! "id": 0,54//! "result": {55//! "bevy_transform::components::transform::Transform": {56//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },57//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },58//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }59//! }60//! }61//! }62//! ```63//!64//! The `id` field will always be present. The `result` field will be present if the65//! request was successful. Otherwise, an `error` field will replace it.66//!67//! * `id` is the arbitrary JSON data that was sent as part of the request. It68//! will be identical to the `id` data sent during the request, modulo69//! serialization and deserialization. If there's an error reading the `id` field,70//! it will be `null`.71//!72//! * `result` will be present if the request succeeded and will contain the response73//! specific to the request.74//!75//! * `error` will be present if the request failed and will contain an error object76//! with more information about the cause of failure.77//!78//! ## Error objects79//!80//! An error object might look like this:81//!82//! ```json83//! {84//! "code": -32602,85//! "message": "Missing \"entity\" field"86//! }87//! ```88//!89//! The `code` and `message` fields will always be present. There may also be a `data` field.90//!91//! * `code` is an integer representing the kind of an error that happened. Error codes documented92//! in the [`error_codes`] module.93//!94//! * `message` is a short, one-sentence human-readable description of the error.95//!96//! * `data` is an optional field of arbitrary type containing additional information about the error.97//!98//! ## Built-in methods99//!100//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data101//! in the ECS.102//!103//! ### `world.get_components`104//!105//! Retrieve the values of one or more components from an entity.106//!107//! `params`:108//! - `entity`: The ID of the entity whose components will be fetched.109//! - `components`: An array of [fully-qualified type names] of components to fetch.110//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the111//! components is not present or can not be reflected. Defaults to false.112//!113//! If `strict` is false:114//!115//! `result`:116//! - `components`: A map associating each type name to its value on the requested entity.117//! - `errors`: A map associating each type name with an error if it was not on the entity118//! or could not be reflected.119//!120//! If `strict` is true:121//!122//! `result`: A map associating each type name to its value on the requested entity.123//!124//! ### `world.query`125//!126//! Perform a query over components in the ECS, returning all matching entities and their associated127//! component values.128//!129//! All of the arrays that comprise this request are optional, and when they are not provided, they130//! will be treated as if they were empty.131//!132//! `params`:133//! - `data`:134//! - `components` (optional): An array of [fully-qualified type names] of components to fetch,135//! see _below_ example for a query to list all the type names in **your** project.136//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.137//! to fetch all reflectable components, you can pass in the string `"all"`.138//! - `has` (optional): An array of fully-qualified type names of components whose presence will be139//! reported as boolean values.140//! - `filter` (optional):141//! - `with` (optional): An array of fully-qualified type names of components that must be present142//! on entities in order for them to be included in results.143//! - `without` (optional): An array of fully-qualified type names of components that must *not* be144//! present on entities in order for them to be included in results.145//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the components146//! is not present or can not be reflected. Defaults to false.147//!148//! `result`: An array, each of which is an object containing:149//! - `entity`: The ID of a query-matching entity.150//! - `components`: A map associating each type name from `components`/`option` to its value on the matching151//! entity if the component is present.152//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the153//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.154//!155//! ### Example156//! To use the query API and retrieve Transform data for all entities that have a Transform157//! use this query:158//!159//! ```json160//! {161//! "jsonrpc": "2.0",162//! "method": "bevy/query",163//! "id": 0,164//! "params": {165//! "data": {166//! "components": ["bevy_transform::components::transform::Transform"]167//! "option": [],168//! "has": []169//! },170//! "filter": {171//! "with": [],172//! "without": []173//! },174//! "strict": false175//! }176//! }177//! ```178//!179//!180//! To query all entities and all of their Reflectable components (and retrieve their values), you can pass in "all" for the option field:181//! ```json182//! {183//! "jsonrpc": "2.0",184//! "method": "bevy/query",185//! "id": 0,186//! "params": {187//! "data": {188//! "components": []189//! "option": "all",190//! "has": []191//! },192//! "filter": {193//! "with": [],194//! "without": []195//! },196//! "strict": false197//! }198//! }199//! ```200//!201//! This should return you something like the below (in a larger list):202//! ```json203//! {204//! "components": {205//! "bevy_camera::Camera3d": {206//! "depth_load_op": {207//! "Clear": 0.0208//! },209//! "depth_texture_usages": 16,210//! "screen_space_specular_transmission_quality": "Medium",211//! "screen_space_specular_transmission_steps": 1212//! },213//! "bevy_core_pipeline::tonemapping::DebandDither": "Enabled",214//! "bevy_core_pipeline::tonemapping::Tonemapping": "TonyMcMapface",215//! "bevy_light::cluster::ClusterConfig": {216//! "FixedZ": {217//! "dynamic_resizing": true,218//! "total": 4096,219//! "z_config": {220//! "far_z_mode": "MaxClusterableObjectRange",221//! "first_slice_depth": 5.0222//! },223//! "z_slices": 24224//! }225//! },226//! "bevy_camera::Camera": {227//! "clear_color": "Default",228//! "is_active": true,229//! "msaa_writeback": true,230//! "order": 0,231//! "sub_camera_view": null,232//! "target": {233//! "Window": "Primary"234//! },235//! "viewport": null236//! },237//! "bevy_camera::Projection": {238//! "Perspective": {239//! "aspect_ratio": 1.7777777910232544,240//! "far": 1000.0,241//! "fov": 0.7853981852531433,242//! "near": 0.10000000149011612243//! }244//! },245//! "bevy_camera::primitives::Frustum": {},246//! "bevy_render::sync_world::RenderEntity": 4294967291,247//! "bevy_render::sync_world::SyncToRenderWorld": {},248//! "bevy_render::view::Msaa": "Sample4",249//! "bevy_camera::visibility::InheritedVisibility": true,250//! "bevy_camera::visibility::ViewVisibility": false,251//! "bevy_camera::visibility::Visibility": "Inherited",252//! "bevy_camera::visibility::VisibleEntities": {},253//! "bevy_transform::components::global_transform::GlobalTransform": [254//! 0.9635179042816162,255//! -3.725290298461914e-9,256//! 0.26764383912086487,257//! 0.11616238951683044,258//! 0.9009039402008056,259//! -0.4181846082210541,260//! -0.24112138152122495,261//! 0.4340185225009918,262//! 0.8680371046066284,263//! -2.5,264//! 4.5,265//! 9.0266//! ],267//! "bevy_transform::components::transform::Transform": {268//! "rotation": [269//! -0.22055435180664065,270//! -0.13167093694210052,271//! -0.03006339818239212,272//! 0.9659786224365234273//! ],274//! "scale": [275//! 1.0,276//! 1.0,277//! 1.0278//! ],279//! "translation": [280//! -2.5,281//! 4.5,282//! 9.0283//! ]284//! },285//! "bevy_transform::components::transform::TransformTreeChanged": null286//! },287//! "entity": 4294967261288//!},289//! ```290//!291//! ### `world.spawn_entity`292//!293//! Create a new entity with the provided components and return the resulting entity ID.294//!295//! `params`:296//! - `components`: A map associating each component's [fully-qualified type name] with its value.297//!298//! `result`:299//! - `entity`: The ID of the newly spawned entity.300//!301//! ### `world.despawn_entity`302//!303//! Despawn the entity with the given ID.304//!305//! `params`:306//! - `entity`: The ID of the entity to be despawned.307//!308//! `result`: null.309//!310//! ### `world.remove_components`311//!312//! Delete one or more components from an entity.313//!314//! `params`:315//! - `entity`: The ID of the entity whose components should be removed.316//! - `components`: An array of [fully-qualified type names] of components to be removed.317//!318//! `result`: null.319//!320//! ### `world.insert_components`321//!322//! Insert one or more components into an entity.323//!324//! `params`:325//! - `entity`: The ID of the entity to insert components into.326//! - `components`: A map associating each component's fully-qualified type name with its value.327//!328//! `result`: null.329//!330//! ### `world.mutate_components`331//!332//! Mutate a field in a component.333//!334//! `params`:335//! - `entity`: The ID of the entity with the component to mutate.336//! - `component`: The component's [fully-qualified type name].337//! - `path`: The path of the field within the component. See338//! [`GetPath`](bevy_reflect::GetPath#syntax) for more information on formatting this string.339//! - `value`: The value to insert at `path`.340//!341//! `result`: null.342//!343//! ### `world.reparent_entities`344//!345//! Assign a new parent to one or more entities.346//!347//! `params`:348//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.349//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.350//! If excluded, the given entities will be removed from their parents.351//!352//! `result`: null.353//!354//! ### `world.list_components`355//!356//! List all registered components or all components present on an entity.357//!358//! When `params` is not provided, this lists all registered components. If `params` is provided,359//! this lists only those components present on the provided entity.360//!361//! `params` (optional):362//! - `entity`: The ID of the entity whose components will be listed.363//!364//! `result`: An array of fully-qualified type names of components.365//!366//! ### `world.get_components+watch`367//!368//! Watch the values of one or more components from an entity.369//!370//! `params`:371//! - `entity`: The ID of the entity whose components will be fetched.372//! - `components`: An array of [fully-qualified type names] of components to fetch.373//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the374//! components is not present or can not be reflected. Defaults to false.375//!376//! If `strict` is false:377//!378//! `result`:379//! - `components`: A map of components added or changed in the last tick associating each type380//! name to its value on the requested entity.381//! - `removed`: An array of fully-qualified type names of components removed from the entity382//! in the last tick.383//! - `errors`: A map associating each type name with an error if it was not on the entity384//! or could not be reflected.385//!386//! If `strict` is true:387//!388//! `result`:389//! - `components`: A map of components added or changed in the last tick associating each type390//! name to its value on the requested entity.391//! - `removed`: An array of fully-qualified type names of components removed from the entity392//! in the last tick.393//!394//! ### `world.list_components+watch`395//!396//! Watch all components present on an entity.397//!398//! When `params` is not provided, this lists all registered components. If `params` is provided,399//! this lists only those components present on the provided entity.400//!401//! `params`:402//! - `entity`: The ID of the entity whose components will be listed.403//!404//! `result`:405//! - `added`: An array of fully-qualified type names of components added to the entity in the406//! last tick.407//! - `removed`: An array of fully-qualified type names of components removed from the entity408//! in the last tick.409//!410//! ### `world.get_resources`411//!412//! Extract the value of a given resource from the world.413//!414//! `params`:415//! - `resource`: The [fully-qualified type name] of the resource to get.416//!417//! `result`:418//! - `value`: The value of the resource in the world.419//!420//! ### `world.insert_resources`421//!422//! Insert the given resource into the world with the given value.423//!424//! `params`:425//! - `resource`: The [fully-qualified type name] of the resource to insert.426//! - `value`: The value of the resource to be inserted.427//!428//! `result`: null.429//!430//! ### `world.remove_resources`431//!432//! Remove the given resource from the world.433//!434//! `params`435//! - `resource`: The [fully-qualified type name] of the resource to remove.436//!437//! `result`: null.438//!439//! ### `world.mutate_resources`440//!441//! Mutate a field in a resource.442//!443//! `params`:444//! - `resource`: The [fully-qualified type name] of the resource to mutate.445//! - `path`: The path of the field within the resource. See446//! [`GetPath`](bevy_reflect::GetPath#syntax) for more information on formatting this string.447//! - `value`: The value to be inserted at `path`.448//!449//! `result`: null.450//!451//! ### `world.list_resources`452//!453//! List all reflectable registered resource types. This method has no parameters.454//!455//! `result`: An array of [fully-qualified type names] of registered resource types.456//!457//! ## Custom methods458//!459//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom460//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the461//! methods may also be extended at runtime using the [`RemoteMethods`] resource.462//!463//! ### Example464//! ```ignore465//! fn main() {466//! App::new()467//! .add_plugins(DefaultPlugins)468//! .add_plugins(469//! // `default` adds all of the built-in methods, while `with_method` extends them470//! RemotePlugin::default()471//! .with_method("super_user/cool_method", path::to::my::cool::handler)472//! // ... more methods can be added by chaining `with_method`473//! )474//! .add_systems(475//! // ... standard application setup476//! )477//! .run();478//! }479//! ```480//!481//! The handler is expected to be a system-convertible function which takes optional JSON parameters482//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks483//! something like this:484//! ```485//! # use serde_json::Value;486//! # use bevy_ecs::prelude::{In, World};487//! # use bevy_remote::BrpResult;488//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {489//! todo!()490//! }491//! ```492//!493//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The494//! handler system will always run with exclusive `World` access.495//!496//! [the `serde` documentation]: https://serde.rs/497//! [fully-qualified type names]: bevy_reflect::TypePath::type_path498//! [fully-qualified type name]: bevy_reflect::TypePath::type_path499500extern crate alloc;501502use async_channel::{Receiver, Sender};503use bevy_app::{prelude::*, MainScheduleOrder};504use bevy_derive::{Deref, DerefMut};505use bevy_ecs::{506entity::Entity,507resource::Resource,508schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet},509system::{Commands, In, IntoSystem, ResMut, System, SystemId},510world::World,511};512use bevy_platform::collections::HashMap;513use bevy_utils::prelude::default;514use serde::{Deserialize, Serialize};515use serde_json::Value;516use std::sync::RwLock;517518pub mod builtin_methods;519#[cfg(feature = "http")]520pub mod http;521pub mod schemas;522523const CHANNEL_SIZE: usize = 16;524525/// Add this plugin to your [`App`] to allow remote connections to inspect and modify entities.526///527/// This the main plugin for `bevy_remote`. See the [crate-level documentation] for details on528/// the available protocols and its default methods.529///530/// [crate-level documentation]: crate531pub struct RemotePlugin {532/// The verbs that the server will recognize and respond to.533methods: RwLock<Vec<(String, RemoteMethodHandler)>>,534}535536impl RemotePlugin {537/// Create a [`RemotePlugin`] with the default address and port but without538/// any associated methods.539fn empty() -> Self {540Self {541methods: RwLock::new(vec![]),542}543}544545/// Add a remote method to the plugin using the given `name` and `handler`.546#[must_use]547pub fn with_method<M>(548mut self,549name: impl Into<String>,550handler: impl IntoSystem<In<Option<Value>>, BrpResult, M>,551) -> Self {552self.methods.get_mut().unwrap().push((553name.into(),554RemoteMethodHandler::Instant(Box::new(IntoSystem::into_system(handler))),555));556self557}558559/// Add a remote method with a watching handler to the plugin using the given `name`.560#[must_use]561pub fn with_watching_method<M>(562mut self,563name: impl Into<String>,564handler: impl IntoSystem<In<Option<Value>>, BrpResult<Option<Value>>, M>,565) -> Self {566self.methods.get_mut().unwrap().push((567name.into(),568RemoteMethodHandler::Watching(Box::new(IntoSystem::into_system(handler))),569));570self571}572}573574impl Default for RemotePlugin {575fn default() -> Self {576Self::empty()577.with_method(578builtin_methods::BRP_GET_COMPONENTS_METHOD,579builtin_methods::process_remote_get_components_request,580)581.with_method(582builtin_methods::BRP_QUERY_METHOD,583builtin_methods::process_remote_query_request,584)585.with_method(586builtin_methods::BRP_SPAWN_ENTITY_METHOD,587builtin_methods::process_remote_spawn_entity_request,588)589.with_method(590builtin_methods::BRP_INSERT_COMPONENTS_METHOD,591builtin_methods::process_remote_insert_components_request,592)593.with_method(594builtin_methods::BRP_REMOVE_COMPONENTS_METHOD,595builtin_methods::process_remote_remove_components_request,596)597.with_method(598builtin_methods::BRP_DESPAWN_COMPONENTS_METHOD,599builtin_methods::process_remote_despawn_entity_request,600)601.with_method(602builtin_methods::BRP_REPARENT_ENTITIES_METHOD,603builtin_methods::process_remote_reparent_entities_request,604)605.with_method(606builtin_methods::BRP_LIST_COMPONENTS_METHOD,607builtin_methods::process_remote_list_components_request,608)609.with_method(610builtin_methods::BRP_MUTATE_COMPONENTS_METHOD,611builtin_methods::process_remote_mutate_components_request,612)613.with_method(614builtin_methods::RPC_DISCOVER_METHOD,615builtin_methods::process_remote_list_methods_request,616)617.with_watching_method(618builtin_methods::BRP_GET_COMPONENTS_AND_WATCH_METHOD,619builtin_methods::process_remote_get_components_watching_request,620)621.with_watching_method(622builtin_methods::BRP_LIST_COMPONENTS_AND_WATCH_METHOD,623builtin_methods::process_remote_list_components_watching_request,624)625.with_method(626builtin_methods::BRP_GET_RESOURCE_METHOD,627builtin_methods::process_remote_get_resources_request,628)629.with_method(630builtin_methods::BRP_INSERT_RESOURCE_METHOD,631builtin_methods::process_remote_insert_resources_request,632)633.with_method(634builtin_methods::BRP_REMOVE_RESOURCE_METHOD,635builtin_methods::process_remote_remove_resources_request,636)637.with_method(638builtin_methods::BRP_MUTATE_RESOURCE_METHOD,639builtin_methods::process_remote_mutate_resources_request,640)641.with_method(642builtin_methods::BRP_LIST_RESOURCES_METHOD,643builtin_methods::process_remote_list_resources_request,644)645.with_method(646builtin_methods::BRP_REGISTRY_SCHEMA_METHOD,647builtin_methods::export_registry_types,648)649}650}651652impl Plugin for RemotePlugin {653fn build(&self, app: &mut App) {654let mut remote_methods = RemoteMethods::new();655656let plugin_methods = &mut *self.methods.write().unwrap();657for (name, handler) in plugin_methods.drain(..) {658remote_methods.insert(659name,660match handler {661RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant(662app.main_mut().world_mut().register_boxed_system(system),663),664RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching(665app.main_mut().world_mut().register_boxed_system(system),666),667},668);669}670671app.init_schedule(RemoteLast)672.world_mut()673.resource_mut::<MainScheduleOrder>()674.insert_after(Last, RemoteLast);675676app.insert_resource(remote_methods)677.init_resource::<schemas::SchemaTypesMetadata>()678.init_resource::<RemoteWatchingRequests>()679.add_systems(PreStartup, setup_mailbox_channel)680.configure_sets(681RemoteLast,682(RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(),683)684.add_systems(685RemoteLast,686(687(process_remote_requests, process_ongoing_watching_requests)688.chain()689.in_set(RemoteSystems::ProcessRequests),690remove_closed_watching_requests.in_set(RemoteSystems::Cleanup),691),692);693}694}695696/// Schedule that contains all systems to process Bevy Remote Protocol requests697#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]698pub struct RemoteLast;699700/// The systems sets of the [`RemoteLast`] schedule.701///702/// These can be useful for ordering.703#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]704pub enum RemoteSystems {705/// Processing of remote requests.706ProcessRequests,707/// Cleanup (remove closed watchers etc)708Cleanup,709}710711/// Deprecated alias for [`RemoteSystems`].712#[deprecated(since = "0.17.0", note = "Renamed to `RemoteSystems`.")]713pub type RemoteSet = RemoteSystems;714715/// A type to hold the allowed types of systems to be used as method handlers.716#[derive(Debug)]717pub enum RemoteMethodHandler {718/// A handler that only runs once and returns one response.719Instant(Box<dyn System<In = In<Option<Value>>, Out = BrpResult>>),720/// A handler that watches for changes and response when a change is detected.721Watching(Box<dyn System<In = In<Option<Value>>, Out = BrpResult<Option<Value>>>>),722}723724/// The [`SystemId`] of a function that implements a remote instant method (`world.get_components`, `world.query`, etc.)725///726/// The first parameter is the JSON value of the `params`. Typically, an727/// implementation will deserialize these as the first thing they do.728///729/// The returned JSON value will be returned as the response. Bevy will730/// automatically populate the `id` field before sending.731pub type RemoteInstantMethodSystemId = SystemId<In<Option<Value>>, BrpResult>;732733/// The [`SystemId`] of a function that implements a remote watching method (`world.get_components+watch`, `world.list_components+watch`, etc.)734///735/// The first parameter is the JSON value of the `params`. Typically, an736/// implementation will deserialize these as the first thing they do.737///738/// The optional returned JSON value will be sent as a response. If no739/// changes were detected this should be [`None`]. Re-running of this740/// handler is done in the [`RemotePlugin`].741pub type RemoteWatchingMethodSystemId = SystemId<In<Option<Value>>, BrpResult<Option<Value>>>;742743/// The [`SystemId`] of a function that can be used as a remote method.744#[derive(Debug, Clone, Copy)]745pub enum RemoteMethodSystemId {746/// A handler that only runs once and returns one response.747Instant(RemoteInstantMethodSystemId),748/// A handler that watches for changes and response when a change is detected.749Watching(RemoteWatchingMethodSystemId),750}751752/// Holds all implementations of methods known to the server.753///754/// Custom methods can be added to this list using [`RemoteMethods::insert`].755#[derive(Debug, Resource, Default)]756pub struct RemoteMethods(HashMap<String, RemoteMethodSystemId>);757758impl RemoteMethods {759/// Creates a new [`RemoteMethods`] resource with no methods registered in it.760pub fn new() -> Self {761default()762}763764/// Adds a new method, replacing any existing method with that name.765///766/// If there was an existing method with that name, returns its handler.767pub fn insert(768&mut self,769method_name: impl Into<String>,770handler: RemoteMethodSystemId,771) -> Option<RemoteMethodSystemId> {772self.0.insert(method_name.into(), handler)773}774775/// Get a [`RemoteMethodSystemId`] with its method name.776pub fn get(&self, method: &str) -> Option<&RemoteMethodSystemId> {777self.0.get(method)778}779780/// Get a [`Vec<String>`] with method names.781pub fn methods(&self) -> Vec<String> {782self.0.keys().cloned().collect()783}784}785786/// Holds the [`BrpMessage`]'s of all ongoing watching requests along with their handlers.787#[derive(Debug, Resource, Default)]788pub struct RemoteWatchingRequests(Vec<(BrpMessage, RemoteWatchingMethodSystemId)>);789790/// A single request from a Bevy Remote Protocol client to the server,791/// serialized in JSON.792///793/// The JSON payload is expected to look like this:794///795/// ```json796/// {797/// "jsonrpc": "2.0",798/// "method": "world.get_components",799/// "id": 0,800/// "params": {801/// "entity": 4294967298,802/// "components": [803/// "bevy_transform::components::transform::Transform"804/// ]805/// }806/// }807/// ```808/// Or, to list all the fully-qualified type paths in **your** project, pass Null to the809/// `params`.810/// ```json811/// {812/// "jsonrpc": "2.0",813/// "method": "world.list_components",814/// "id": 0,815/// "params": null816///}817///```818///819/// In Rust:820/// ```ignore821/// let req = BrpRequest {822/// jsonrpc: "2.0".to_string(),823/// method: BRP_LIST_METHOD.to_string(), // All the methods have consts824/// id: Some(ureq::json!(0)),825/// params: None,826/// };827/// ```828#[derive(Debug, Serialize, Deserialize, Clone)]829pub struct BrpRequest {830/// This field is mandatory and must be set to `"2.0"` for the request to be accepted.831pub jsonrpc: String,832833/// The action to be performed.834pub method: String,835836/// Arbitrary data that will be returned verbatim to the client as part of837/// the response.838#[serde(skip_serializing_if = "Option::is_none")]839pub id: Option<Value>,840841/// The parameters, specific to each method.842///843/// These are passed as the first argument to the method handler.844/// Sometimes params can be omitted.845#[serde(skip_serializing_if = "Option::is_none")]846pub params: Option<Value>,847}848849/// A response according to BRP.850#[derive(Debug, Serialize, Deserialize, Clone)]851pub struct BrpResponse {852/// This field is mandatory and must be set to `"2.0"`.853pub jsonrpc: &'static str,854855/// The id of the original request.856pub id: Option<Value>,857858/// The actual response payload.859#[serde(flatten)]860pub payload: BrpPayload,861}862863impl BrpResponse {864/// Generates a [`BrpResponse`] from an id and a `Result`.865#[must_use]866pub fn new(id: Option<Value>, result: BrpResult) -> Self {867Self {868jsonrpc: "2.0",869id,870payload: BrpPayload::from(result),871}872}873}874875/// A result/error payload present in every response.876#[derive(Debug, Serialize, Deserialize, Clone)]877#[serde(rename_all = "snake_case")]878pub enum BrpPayload {879/// `Ok` variant880Result(Value),881/// `Err` variant882Error(BrpError),883}884885impl From<BrpResult> for BrpPayload {886fn from(value: BrpResult) -> Self {887match value {888Ok(v) => Self::Result(v),889Err(err) => Self::Error(err),890}891}892}893894/// An error a request might return.895#[derive(Debug, Serialize, Deserialize, Clone)]896pub struct BrpError {897/// Defines the general type of the error.898pub code: i16,899/// Short, human-readable description of the error.900pub message: String,901/// Optional additional error data.902#[serde(skip_serializing_if = "Option::is_none")]903pub data: Option<Value>,904}905906impl BrpError {907/// Entity wasn't found.908#[must_use]909pub fn entity_not_found(entity: Entity) -> Self {910Self {911code: error_codes::ENTITY_NOT_FOUND,912message: format!("Entity {entity} not found"),913data: None,914}915}916917/// Component wasn't found in an entity.918#[must_use]919pub fn component_not_present(component: &str, entity: Entity) -> Self {920Self {921code: error_codes::COMPONENT_NOT_PRESENT,922message: format!("Component `{component}` not present in Entity {entity}"),923data: None,924}925}926927/// An arbitrary component error. Possibly related to reflection.928#[must_use]929pub fn component_error<E: ToString>(error: E) -> Self {930Self {931code: error_codes::COMPONENT_ERROR,932message: error.to_string(),933data: None,934}935}936937/// Resource was not present in the world.938#[must_use]939pub fn resource_not_present(resource: &str) -> Self {940Self {941code: error_codes::RESOURCE_NOT_PRESENT,942message: format!("Resource `{resource}` not present in the world"),943data: None,944}945}946947/// An arbitrary resource error. Possibly related to reflection.948#[must_use]949pub fn resource_error<E: ToString>(error: E) -> Self {950Self {951code: error_codes::RESOURCE_ERROR,952message: error.to_string(),953data: None,954}955}956957/// An arbitrary internal error.958#[must_use]959pub fn internal<E: ToString>(error: E) -> Self {960Self {961code: error_codes::INTERNAL_ERROR,962message: error.to_string(),963data: None,964}965}966967/// Attempt to reparent an entity to itself.968#[must_use]969pub fn self_reparent(entity: Entity) -> Self {970Self {971code: error_codes::SELF_REPARENT,972message: format!("Cannot reparent Entity {entity} to itself"),973data: None,974}975}976}977978/// Error codes used by BRP.979pub mod error_codes {980// JSON-RPC errors981// Note that the range -32728 to -32000 (inclusive) is reserved by the JSON-RPC specification.982983/// Invalid JSON.984pub const PARSE_ERROR: i16 = -32700;985986/// JSON sent is not a valid request object.987pub const INVALID_REQUEST: i16 = -32600;988989/// The method does not exist / is not available.990pub const METHOD_NOT_FOUND: i16 = -32601;991992/// Invalid method parameter(s).993pub const INVALID_PARAMS: i16 = -32602;994995/// Internal error.996pub const INTERNAL_ERROR: i16 = -32603;997998// Bevy errors (i.e. application errors)9991000/// Entity not found.1001pub const ENTITY_NOT_FOUND: i16 = -23401;10021003/// Could not reflect or find component.1004pub const COMPONENT_ERROR: i16 = -23402;10051006/// Could not find component in entity.1007pub const COMPONENT_NOT_PRESENT: i16 = -23403;10081009/// Cannot reparent an entity to itself.1010pub const SELF_REPARENT: i16 = -23404;10111012/// Could not reflect or find resource.1013pub const RESOURCE_ERROR: i16 = -23501;10141015/// Could not find resource in the world.1016pub const RESOURCE_NOT_PRESENT: i16 = -23502;1017}10181019/// The result of a request.1020pub type BrpResult<T = Value> = Result<T, BrpError>;10211022/// The requests may occur on their own or in batches.1023/// Actual parsing is deferred for the sake of proper1024/// error reporting.1025#[derive(Debug, Clone, Serialize, Deserialize)]1026#[serde(untagged)]1027pub enum BrpBatch {1028/// Multiple requests with deferred parsing.1029Batch(Vec<Value>),1030/// A single request with deferred parsing.1031Single(Value),1032}10331034/// A message from the Bevy Remote Protocol server thread to the main world.1035///1036/// This is placed in the [`BrpReceiver`].1037#[derive(Debug, Clone)]1038pub struct BrpMessage {1039/// The request method.1040pub method: String,10411042/// The request params.1043pub params: Option<Value>,10441045/// The channel on which the response is to be sent.1046///1047/// The value sent here is serialized and sent back to the client.1048pub sender: Sender<BrpResult>,1049}10501051/// A resource holding the matching sender for the [`BrpReceiver`]'s receiver.1052#[derive(Debug, Resource, Deref, DerefMut)]1053pub struct BrpSender(Sender<BrpMessage>);10541055/// A resource that receives messages sent by Bevy Remote Protocol clients.1056///1057/// Every frame, the `process_remote_requests` system drains this mailbox and1058/// processes the messages within.1059#[derive(Debug, Resource, Deref, DerefMut)]1060pub struct BrpReceiver(Receiver<BrpMessage>);10611062fn setup_mailbox_channel(mut commands: Commands) {1063// Create the channel and the mailbox.1064let (request_sender, request_receiver) = async_channel::bounded(CHANNEL_SIZE);1065commands.insert_resource(BrpSender(request_sender));1066commands.insert_resource(BrpReceiver(request_receiver));1067}10681069/// A system that receives requests placed in the [`BrpReceiver`] and processes1070/// them, using the [`RemoteMethods`] resource to map each request to its handler.1071///1072/// This needs exclusive access to the [`World`] because clients can manipulate1073/// anything in the ECS.1074fn process_remote_requests(world: &mut World) {1075if !world.contains_resource::<BrpReceiver>() {1076return;1077}10781079while let Ok(message) = world.resource_mut::<BrpReceiver>().try_recv() {1080// Fetch the handler for the method. If there's no such handler1081// registered, return an error.1082let Some(&handler) = world.resource::<RemoteMethods>().get(&message.method) else {1083let _ = message.sender.force_send(Err(BrpError {1084code: error_codes::METHOD_NOT_FOUND,1085message: format!("Method `{}` not found", message.method),1086data: None,1087}));1088return;1089};10901091match handler {1092RemoteMethodSystemId::Instant(id) => {1093let result = match world.run_system_with(id, message.params) {1094Ok(result) => result,1095Err(error) => {1096let _ = message.sender.force_send(Err(BrpError {1097code: error_codes::INTERNAL_ERROR,1098message: format!("Failed to run method handler: {error}"),1099data: None,1100}));1101continue;1102}1103};11041105let _ = message.sender.force_send(result);1106}1107RemoteMethodSystemId::Watching(id) => {1108world1109.resource_mut::<RemoteWatchingRequests>()1110.01111.push((message, id));1112}1113}1114}1115}11161117/// A system that checks all ongoing watching requests for changes that should be sent1118/// and handles it if so.1119fn process_ongoing_watching_requests(world: &mut World) {1120world.resource_scope::<RemoteWatchingRequests, ()>(|world, requests| {1121for (message, system_id) in requests.0.iter() {1122let handler_result = process_single_ongoing_watching_request(world, message, system_id);1123let sender_result = match handler_result {1124Ok(Some(value)) => message.sender.try_send(Ok(value)),1125Err(err) => message.sender.try_send(Err(err)),1126Ok(None) => continue,1127};11281129if sender_result.is_err() {1130// The [`remove_closed_watching_requests`] system will clean this up.1131message.sender.close();1132}1133}1134});1135}11361137fn process_single_ongoing_watching_request(1138world: &mut World,1139message: &BrpMessage,1140system_id: &RemoteWatchingMethodSystemId,1141) -> BrpResult<Option<Value>> {1142world1143.run_system_with(*system_id, message.params.clone())1144.map_err(|error| BrpError {1145code: error_codes::INTERNAL_ERROR,1146message: format!("Failed to run method handler: {error}"),1147data: None,1148})?1149}11501151fn remove_closed_watching_requests(mut requests: ResMut<RemoteWatchingRequests>) {1152for i in (0..requests.0.len()).rev() {1153let Some((message, _)) = requests.0.get(i) else {1154unreachable!()1155};11561157if message.sender.is_closed() {1158requests.0.swap_remove(i);1159}1160}1161}116211631164