Skip to main content

bevy_remote/
lib.rs

1//! An implementation of the Bevy Remote Protocol, to allow for remote control of a Bevy app.
2//!
3//! Adding the [`RemotePlugin`] to your [`App`] will setup everything needed without
4//! starting any transports. To start accepting remote connections you will need to
5//! add a second plugin like the [`RemoteHttpPlugin`](http::RemoteHttpPlugin) to enable communication
6//! over HTTP. These *remote clients* can inspect and alter the state of the
7//! entity-component system.
8//!
9//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
10//!
11//! ## Request objects
12//!
13//! A typical client request might look like this:
14//!
15//! ```json
16//! {
17//!     "method": "world.get_components",
18//!     "id": 0,
19//!     "params": {
20//!         "entity": 4294967298,
21//!         "components": [
22//!             "bevy_transform::components::transform::Transform"
23//!         ]
24//!     }
25//! }
26//! ```
27//!
28//! The `id` and `method` fields are required. The `params` field may be omitted
29//! for certain methods:
30//!
31//! * `id` is arbitrary JSON data. The server completely ignores its contents,
32//!   and the client may use it for any purpose. It will be copied via
33//!   serialization and deserialization (so object property order, etc. can't be
34//!   relied upon to be identical) and sent back to the client as part of the
35//!   response.
36//!
37//! * `method` is a string that specifies one of the possible [`BrpRequest`]
38//!   variants: `world.query`, `world.get_components`, `world.insert_components`, etc. It's case-sensitive.
39//!
40//! * `params` is parameter data specific to the request.
41//!
42//! For more information, see the documentation for [`BrpRequest`].
43//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
44//! documentation] may be useful to clarify the correspondence between the Rust
45//! structure and the JSON format.
46//!
47//! ## Response objects
48//!
49//! A response from the server to the client might look like this:
50//!
51//! ```json
52//! {
53//!     "jsonrpc": "2.0",
54//!     "id": 0,
55//!     "result": {
56//!         "bevy_transform::components::transform::Transform": {
57//!             "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
58//!             "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
59//!             "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
60//!         }
61//!     }
62//! }
63//! ```
64//!
65//! The `id` field will always be present. The `result` field will be present if the
66//! request was successful. Otherwise, an `error` field will replace it.
67//!
68//! * `id` is the arbitrary JSON data that was sent as part of the request. It
69//!   will be identical to the `id` data sent during the request, modulo
70//!   serialization and deserialization. If there's an error reading the `id` field,
71//!   it will be `null`.
72//!
73//! * `result` will be present if the request succeeded and will contain the response
74//!   specific to the request.
75//!
76//! * `error` will be present if the request failed and will contain an error object
77//!   with more information about the cause of failure.
78//!
79//! ## Error objects
80//!
81//! An error object might look like this:
82//!
83//! ```json
84//! {
85//!     "code": -32602,
86//!     "message": "Missing \"entity\" field"
87//! }
88//! ```
89//!
90//! The `code` and `message` fields will always be present. There may also be a `data` field.
91//!
92//! * `code` is an integer representing the kind of an error that happened. Error codes documented
93//!   in the [`error_codes`] module.
94//!
95//! * `message` is a short, one-sentence human-readable description of the error.
96//!
97//! * `data` is an optional field of arbitrary type containing additional information about the error.
98//!
99//! ## Built-in methods
100//!
101//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
102//! in the ECS.
103//!
104//! ### `world.get_components`
105//!
106//! Retrieve the values of one or more components from an entity.
107//!
108//! `params`:
109//! - `entity`: The ID of the entity whose components will be fetched.
110//! - `components`: An array of [fully-qualified type names] of components to fetch.
111//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the
112//!   components is not present or can not be reflected. Defaults to false.
113//!
114//! If `strict` is false:
115//!
116//! `result`:
117//! - `components`: A map associating each type name to its value on the requested entity.
118//! - `errors`: A map associating each type name with an error if it was not on the entity
119//!   or could not be reflected.
120//!
121//! If `strict` is true:
122//!
123//! `result`: A map associating each type name to its value on the requested entity.
124//!
125//! ### `world.query`
126//!
127//! Perform a query over components in the ECS, returning all matching entities and their associated
128//! component values.
129//!
130//! All of the arrays that comprise this request are optional, and when they are not provided, they
131//! will be treated as if they were empty.
132//!
133//! `params`:
134//! - `data`:
135//!   - `components` (optional): An array of [fully-qualified type names] of components to fetch,
136//!     see _below_ example for a query to list all the type names in **your** project.
137//!   - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
138//!     to fetch all reflectable components, you can pass in the string `"all"`.
139//!   - `has` (optional): An array of fully-qualified type names of components whose presence will be
140//!     reported as boolean values.
141//! - `filter` (optional):
142//!   - `with` (optional): An array of fully-qualified type names of components that must be present
143//!     on entities in order for them to be included in results.
144//!   - `without` (optional): An array of fully-qualified type names of components that must *not* be
145//!     present on entities in order for them to be included in results.
146//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the components
147//!   is not present or can not be reflected. Defaults to false.
148//!
149//! `result`: An array, each of which is an object containing:
150//! - `entity`: The ID of a query-matching entity.
151//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
152//!   entity if the component is present.
153//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
154//!   entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
155//!
156//! #### Example
157//! To use the query API and retrieve Transform data for all entities that have a Transform
158//! use this query:
159//!
160//! ```json
161//! {
162//!     "jsonrpc": "2.0",
163//!     "method": "world.query",
164//!     "id": 0,
165//!     "params": {
166//!         "data": {
167//!             "components": ["bevy_transform::components::transform::Transform"]
168//!             "option": [],
169//!             "has": []
170//!         },
171//!         "filter": {
172//!           "with": [],
173//!           "without": []
174//!         },
175//!         "strict": false
176//!     }
177//! }
178//! ```
179//!
180//!
181//! To query all entities and all of their Reflectable components (and retrieve their values), you can pass in "all" for the option field:
182//! ```json
183//! {
184//!     "jsonrpc": "2.0",
185//!     "method": "world.query",
186//!     "id": 0,
187//!     "params": {
188//!         "data": {
189//!             "components": []
190//!             "option": "all",
191//!             "has": []
192//!         },
193//!         "filter": {
194//!            "with": [],
195//!           "without": []
196//!         },
197//!         "strict": false
198//!     }
199//! }
200//! ```
201//!
202//! This should return you something like the below (in a larger list):
203//! ```json
204//! {
205//!   "components": {
206//!     "bevy_camera::Camera3d": {
207//!       "depth_load_op": {
208//!         "Clear": 0.0
209//!       },
210//!       "depth_texture_usages": 16,
211//!     },
212//!     "bevy_render::view::DebandDither": "Enabled",
213//!     "bevy_render::view::Tonemapping": "TonyMcMapface",
214//!     "bevy_light::cluster::ClusterConfig": {
215//!       "FixedZ": {
216//!      "dynamic_resizing": true,
217//!         "total": 4096,
218//!         "z_config": {
219//!           "far_z_mode": "MaxClusterableObjectRange",
220//!           "first_slice_depth": 5.0
221//!         },
222//!         "z_slices": 24
223//!       }
224//!     },
225//!     "bevy_camera::Camera": {
226//!       "clear_color": "Default",
227//!       "is_active": true,
228//!       "msaa_writeback": true,
229//!       "order": 0,
230//!       "sub_camera_view": null,
231//!       "target": {
232//!         "Window": "Primary"
233//!       },
234//!    "viewport": null
235//!     },
236//!     "bevy_camera::Projection": {
237//!       "Perspective": {
238//!         "aspect_ratio": 1.7777777910232544,
239//!         "far": 1000.0,
240//!         "fov": 0.7853981852531433,
241//!         "near": 0.10000000149011612
242//!       }
243//!     },
244//!     "bevy_camera::primitives::Frustum": {},
245//!  "bevy_render::sync_world::RenderEntity": 4294967291,
246//!     "bevy_render::sync_world::SyncToRenderWorld": {},
247//!     "bevy_render::view::Msaa": "Sample4",
248//!     "bevy_camera::visibility::InheritedVisibility": true,
249//!     "bevy_camera::visibility::ViewVisibility": false,
250//!     "bevy_camera::visibility::Visibility": "Inherited",
251//!     "bevy_camera::visibility::VisibleEntities": {},
252//!     "bevy_transform::components::global_transform::GlobalTransform": [
253//!       0.9635179042816162,
254//!       -3.725290298461914e-9,
255//!       0.26764383912086487,
256//!       0.11616238951683044,
257//!       0.9009039402008056,
258//!       -0.4181846082210541,
259//!       -0.24112138152122495,
260//!       0.4340185225009918,
261//!       0.8680371046066284,
262//!       -2.5,
263//!       4.5,
264//!       9.0
265//!     ],
266//!     "bevy_transform::components::transform::Transform": {
267//!    "rotation": [
268//!         -0.22055435180664065,
269//!         -0.13167093694210052,
270//!         -0.03006339818239212,
271//!         0.9659786224365234
272//!       ],
273//!       "scale": [
274//!         1.0,
275//!         1.0,
276//!         1.0
277//!    ],
278//!       "translation": [
279//!         -2.5,
280//!       4.5,
281//!         9.0
282//!       ]
283//!     },
284//!     "bevy_transform::components::transform::TransformTreeChanged": null
285//!   },
286//!   "entity": 4294967261
287//!},
288//! ```
289//!
290//! ### `world.spawn_entity`
291//!
292//! Create a new entity with the provided components and return the resulting entity ID.
293//!
294//! `params`:
295//! - `components`: A map associating each component's [fully-qualified type name] with its value.
296//!
297//! `result`:
298//! - `entity`: The ID of the newly spawned entity.
299//!
300//! ### `world.despawn_entity`
301//!
302//! Despawn the entity with the given ID.
303//!
304//! `params`:
305//! - `entity`: The ID of the entity to be despawned.
306//!
307//! `result`: null.
308//!
309//! ### `world.remove_components`
310//!
311//! Delete one or more components from an entity.
312//!
313//! `params`:
314//! - `entity`: The ID of the entity whose components should be removed.
315//! - `components`: An array of [fully-qualified type names] of components to be removed.
316//!
317//! `result`: null.
318//!
319//! ### `world.insert_components`
320//!
321//! Insert one or more components into an entity.
322//!
323//! `params`:
324//! - `entity`: The ID of the entity to insert components into.
325//! - `components`: A map associating each component's fully-qualified type name with its value.
326//!
327//! `result`: null.
328//!
329//! ### `world.mutate_components`
330//!
331//! Mutate a field in a component.
332//!
333//! `params`:
334//! - `entity`: The ID of the entity with the component to mutate.
335//! - `component`: The component's [fully-qualified type name].
336//! - `path`: The path of the field within the component. See
337//!   [`GetPath`](bevy_reflect::GetPath#syntax) for more information on formatting this string.
338//! - `value`: The value to insert at `path`.
339//!
340//! `result`: null.
341//!
342//! ### `world.reparent_entities`
343//!
344//! Assign a new parent to one or more entities.
345//!
346//! `params`:
347//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
348//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
349//!   If excluded, the given entities will be removed from their parents.
350//!
351//! `result`: null.
352//!
353//! ### `world.list_components`
354//!
355//! List all registered components or all components present on an entity.
356//!
357//! When `params` is not provided, this lists all registered components. If `params` is provided,
358//! this lists only those components present on the provided entity.
359//!
360//! `params` (optional):
361//! - `entity`: The ID of the entity whose components will be listed.
362//!
363//! `result`: An array of fully-qualified type names of components.
364//!
365//! ### `world.get_components+watch`
366//!
367//! Watch the values of one or more components from an entity.
368//!
369//! `params`:
370//! - `entity`: The ID of the entity whose components will be fetched.
371//! - `components`: An array of [fully-qualified type names] of components to fetch.
372//! - `strict` (optional): A flag to enable strict mode which will fail if any one of the
373//!   components is not present or can not be reflected. Defaults to false.
374//!
375//! If `strict` is false:
376//!
377//! `result`:
378//! - `components`: A map of components added or changed in the last tick associating each type
379//!   name to its value on the requested entity.
380//! - `removed`: An array of fully-qualified type names of components removed from the entity
381//!   in the last tick.
382//! - `errors`: A map associating each type name with an error if it was not on the entity
383//!   or could not be reflected.
384//!
385//! If `strict` is true:
386//!
387//! `result`:
388//! - `components`: A map of components added or changed in the last tick associating each type
389//!   name to its value on the requested entity.
390//! - `removed`: An array of fully-qualified type names of components removed from the entity
391//!   in the last tick.
392//!
393//! ### `world.list_components+watch`
394//!
395//! Watch all components present on an entity.
396//!
397//! When `params` is not provided, this lists all registered components. If `params` is provided,
398//! this lists only those components present on the provided entity.
399//!
400//! `params`:
401//! - `entity`: The ID of the entity whose components will be listed.
402//!
403//! `result`:
404//! - `added`: An array of fully-qualified type names of components added to the entity in the
405//!   last tick.
406//! - `removed`: An array of fully-qualified type names of components removed from the entity
407//!   in the last tick.
408//!
409//! ### `world.get_resources`
410//!
411//! Extract the value of a given resource from the world.
412//!
413//! `params`:
414//! - `resource`: The [fully-qualified type name] of the resource to get.
415//!
416//! `result`:
417//! - `value`: The value of the resource in the world.
418//!
419//! ### `world.insert_resources`
420//!
421//! Insert the given resource into the world with the given value.
422//!
423//! `params`:
424//! - `resource`: The [fully-qualified type name] of the resource to insert.
425//! - `value`: The value of the resource to be inserted.
426//!
427//! `result`: null.
428//!
429//! ### `world.remove_resources`
430//!
431//! Remove the given resource from the world.
432//!
433//! `params`
434//! - `resource`: The [fully-qualified type name] of the resource to remove.
435//!
436//! `result`: null.
437//!
438//! ### `world.mutate_resources`
439//!
440//! Mutate a field in a resource.
441//!
442//! `params`:
443//! - `resource`: The [fully-qualified type name] of the resource to mutate.
444//! - `path`: The path of the field within the resource. See
445//!   [`GetPath`](bevy_reflect::GetPath#syntax) for more information on formatting this string.
446//! - `value`: The value to be inserted at `path`.
447//!
448//! `result`: null.
449//!
450//! ### `world.list_resources`
451//!
452//! List all reflectable registered resource types. This method has no parameters.
453//!
454//! `result`: An array of [fully-qualified type names] of registered resource types.
455//!
456//! ### `world.trigger_event`
457//!
458//! Triggers an event.
459//!
460//! `params`:
461//! - `event`: The [fully-qualified type name] of the event to trigger.
462//! - `value`: The value of the event to trigger.
463//!
464//! `result`: null.
465//!
466//! ### `registry.schema`
467//!
468//! Retrieve schema information about registered types in the Bevy app's type registry.
469//!
470//! `params` (optional):
471//! - `with_crates`: An array of crate names to include in the results. When empty or omitted, types from all crates will be included.
472//! - `without_crates`: An array of crate names to exclude from the results. When empty or omitted, no crates will be excluded.
473//! - `type_limit`: Additional type constraints:
474//!   - `with`: An array of [fully-qualified type names] that must be present for a type to be included
475//!   - `without`: An array of [fully-qualified type names] that must not be present for a type to be excluded
476//!
477//! `result`: A map associating each type's [fully-qualified type name] to a [`JsonSchemaBevyType`](crate::schemas::json_schema::JsonSchemaBevyType).
478//! This contains schema information about that type, including field definitions, type information, reflect type information, and other metadata
479//! helpful for understanding the structure of the type.
480//!
481//! ### `rpc.discover`
482//!
483//! Discover available remote methods and server information. This follows the [`OpenRPC` specification for service discovery](https://spec.open-rpc.org/#service-discovery-method).
484//!
485//! This method takes no parameters.
486//!
487//! `result`: An `OpenRPC` document containing:
488//! - Information about all available remote methods
489//! - Server connection information (when using HTTP transport)
490//! - `OpenRPC` specification version
491//!
492//! ## Custom methods
493//!
494//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
495//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
496//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
497//!
498//! ### Example
499//! ```ignore
500//! fn main() {
501//!     App::new()
502//!         .add_plugins(DefaultPlugins)
503//!         .add_plugins(
504//!             // `default` adds all of the built-in methods, while `with_method` extends them
505//!             RemotePlugin::default()
506//!                 .with_method("super_user/cool_method", path::to::my::cool::handler)
507//!                 // ... more methods can be added by chaining `with_method`
508//!         )
509//!         .add_systems(
510//!             // ... standard application setup
511//!         )
512//!         .run();
513//! }
514//! ```
515//!
516//! The handler is expected to be a system-convertible function which takes optional JSON parameters
517//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
518//! something like this:
519//! ```
520//! # use serde_json::Value;
521//! # use bevy_ecs::prelude::{In, World};
522//! # use bevy_remote::BrpResult;
523//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
524//!     todo!()
525//! }
526//! ```
527//!
528//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
529//! handler system will always run with exclusive `World` access.
530//!
531//! [the `serde` documentation]: https://serde.rs/
532//! [fully-qualified type names]: bevy_reflect::TypePath::type_path
533//! [fully-qualified type name]: bevy_reflect::TypePath::type_path
534
535extern crate alloc;
536
537use async_channel::{Receiver, Sender};
538use bevy_app::{prelude::*, MainScheduleOrder};
539use bevy_derive::{Deref, DerefMut};
540use bevy_ecs::{
541    entity::Entity,
542    observer::On,
543    resource::Resource,
544    schedule::{
545        InternedScheduleLabel, IntoScheduleConfigs, ScheduleBuildMetadata, ScheduleBuilt,
546        ScheduleLabel, SystemSet,
547    },
548    system::{Commands, In, IntoSystem, ResMut, System, SystemId},
549    world::World,
550};
551use bevy_platform::collections::HashMap;
552#[cfg(feature = "bevy_render")]
553use bevy_render::{Render, RenderApp, RenderScheduleOrder, RenderStartup};
554use bevy_utils::prelude::default;
555use serde::{ser::SerializeMap, Deserialize, Serialize};
556use serde_json::Value;
557use std::sync::RwLock;
558
559pub mod builtin_methods;
560#[cfg(feature = "http")]
561pub mod http;
562pub mod schemas;
563
564const CHANNEL_SIZE: usize = 16;
565
566/// Add this plugin to your [`App`] to allow remote connections to inspect and modify entities.
567///
568/// This the main plugin for `bevy_remote`. See the [crate-level documentation] for details on
569/// the available protocols and its default methods.
570///
571/// [crate-level documentation]: crate
572pub struct RemotePlugin {
573    /// The verbs that the server will recognize and respond to for the main app.
574    methods: RwLock<Vec<(String, RemoteMethodHandler)>>,
575    /// The verbs that the server will recognize and respond to for the render subapp.
576    render_methods: RwLock<Vec<(String, RemoteMethodHandler)>>,
577}
578
579impl RemotePlugin {
580    /// Create a [`RemotePlugin`] with the default address and port but without
581    /// any associated methods.
582    fn empty() -> Self {
583        Self {
584            methods: RwLock::new(vec![]),
585            render_methods: RwLock::new(vec![]),
586        }
587    }
588
589    /// Add a remote method to the plugin using the given `name` and `handler` to main app.
590    #[inline]
591    pub fn with_method_main<M>(
592        self,
593        name: impl Into<String>,
594        handler: impl IntoSystem<In<Option<Value>>, BrpResult, M>,
595    ) -> Self {
596        self.with_method(name, handler, true)
597    }
598
599    /// Add a remote method to the plugin using the given `name` and `handler` to render app.
600    #[inline]
601    pub fn with_method_render<M>(
602        self,
603        name: impl Into<String>,
604        handler: impl IntoSystem<In<Option<Value>>, BrpResult, M>,
605    ) -> Self {
606        self.with_method(name, handler, false)
607    }
608
609    /// Add a remote method to the plugin using the given `name` and `handler` to given app.
610    #[must_use]
611    fn with_method<M>(
612        mut self,
613        name: impl Into<String>,
614        handler: impl IntoSystem<In<Option<Value>>, BrpResult, M>,
615        to_main: bool,
616    ) -> Self {
617        (if to_main {
618            self.methods.get_mut()
619        } else {
620            self.render_methods.get_mut()
621        })
622        .unwrap()
623        .push((
624            name.into(),
625            RemoteMethodHandler::Instant(Box::new(IntoSystem::into_system(handler))),
626        ));
627        self
628    }
629
630    /// Add a remote method with a watching handler to the plugin using the given `name` to main app.
631    #[inline]
632    pub fn with_watching_method_main<M>(
633        self,
634        name: impl Into<String>,
635        handler: impl IntoSystem<In<Option<Value>>, BrpResult<Option<Value>>, M>,
636    ) -> Self {
637        self.with_watching_method(name, handler, true)
638    }
639
640    /// Add a remote method with a watching handler to the plugin using the given `name` to render app.
641    #[inline]
642    pub fn with_watching_method_render<M>(
643        self,
644        name: impl Into<String>,
645        handler: impl IntoSystem<In<Option<Value>>, BrpResult<Option<Value>>, M>,
646    ) -> Self {
647        self.with_watching_method(name, handler, false)
648    }
649
650    /// Add a remote method with a watching handler to the plugin using the given `name` to given app.
651    #[must_use]
652    fn with_watching_method<M>(
653        mut self,
654        name: impl Into<String>,
655        handler: impl IntoSystem<In<Option<Value>>, BrpResult<Option<Value>>, M>,
656        to_main: bool,
657    ) -> Self {
658        (if to_main {
659            self.methods.get_mut()
660        } else {
661            self.render_methods.get_mut()
662        })
663        .unwrap()
664        .push((
665            name.into(),
666            RemoteMethodHandler::Watching(Box::new(IntoSystem::into_system(handler))),
667        ));
668        self
669    }
670
671    /// Create the default list of BRP methods
672    fn add_default_methods(self, to_main: bool) -> Self {
673        self.with_method(
674            builtin_methods::BRP_GET_COMPONENTS_METHOD,
675            builtin_methods::process_remote_get_components_request,
676            to_main,
677        )
678        .with_method(
679            builtin_methods::BRP_QUERY_METHOD,
680            builtin_methods::process_remote_query_request,
681            to_main,
682        )
683        .with_method(
684            builtin_methods::BRP_SPAWN_ENTITY_METHOD,
685            builtin_methods::process_remote_spawn_entity_request,
686            to_main,
687        )
688        .with_method(
689            builtin_methods::BRP_INSERT_COMPONENTS_METHOD,
690            builtin_methods::process_remote_insert_components_request,
691            to_main,
692        )
693        .with_method(
694            builtin_methods::BRP_REMOVE_COMPONENTS_METHOD,
695            builtin_methods::process_remote_remove_components_request,
696            to_main,
697        )
698        .with_method(
699            builtin_methods::BRP_DESPAWN_COMPONENTS_METHOD,
700            builtin_methods::process_remote_despawn_entity_request,
701            to_main,
702        )
703        .with_method(
704            builtin_methods::BRP_REPARENT_ENTITIES_METHOD,
705            builtin_methods::process_remote_reparent_entities_request,
706            to_main,
707        )
708        .with_method(
709            builtin_methods::BRP_LIST_COMPONENTS_METHOD,
710            builtin_methods::process_remote_list_components_request,
711            to_main,
712        )
713        .with_method(
714            builtin_methods::BRP_MUTATE_COMPONENTS_METHOD,
715            builtin_methods::process_remote_mutate_components_request,
716            to_main,
717        )
718        .with_method(
719            builtin_methods::RPC_DISCOVER_METHOD,
720            builtin_methods::process_remote_list_methods_request,
721            to_main,
722        )
723        .with_watching_method(
724            builtin_methods::BRP_GET_COMPONENTS_AND_WATCH_METHOD,
725            builtin_methods::process_remote_get_components_watching_request,
726            to_main,
727        )
728        .with_watching_method(
729            builtin_methods::BRP_LIST_COMPONENTS_AND_WATCH_METHOD,
730            builtin_methods::process_remote_list_components_watching_request,
731            to_main,
732        )
733        .with_method(
734            builtin_methods::BRP_GET_RESOURCE_METHOD,
735            builtin_methods::process_remote_get_resources_request,
736            to_main,
737        )
738        .with_method(
739            builtin_methods::BRP_INSERT_RESOURCE_METHOD,
740            builtin_methods::process_remote_insert_resources_request,
741            to_main,
742        )
743        .with_method(
744            builtin_methods::BRP_REMOVE_RESOURCE_METHOD,
745            builtin_methods::process_remote_remove_resources_request,
746            to_main,
747        )
748        .with_method(
749            builtin_methods::BRP_MUTATE_RESOURCE_METHOD,
750            builtin_methods::process_remote_mutate_resources_request,
751            to_main,
752        )
753        .with_method(
754            builtin_methods::BRP_LIST_RESOURCES_METHOD,
755            builtin_methods::process_remote_list_resources_request,
756            to_main,
757        )
758        .with_method(
759            builtin_methods::BRP_TRIGGER_EVENT_METHOD,
760            builtin_methods::process_remote_trigger_event_request,
761            to_main,
762        )
763        .with_method(
764            builtin_methods::BRP_WRITE_MESSAGE_METHOD,
765            builtin_methods::process_remote_write_message_request,
766            to_main,
767        )
768        .with_watching_method(
769            builtin_methods::BRP_OBSERVE_METHOD,
770            builtin_methods::process_remote_observe_watching_request,
771            to_main,
772        )
773        .with_method(
774            builtin_methods::BRP_REGISTRY_SCHEMA_METHOD,
775            builtin_methods::export_registry_types,
776            to_main,
777        )
778        .with_method(
779            builtin_methods::BRP_SCHEDULE_LIST,
780            builtin_methods::schedule_list,
781            to_main,
782        )
783        .with_method(
784            builtin_methods::BRP_SCHEDULE_GRAPH,
785            builtin_methods::schedule_graph,
786            to_main,
787        )
788    }
789}
790
791impl Default for RemotePlugin {
792    fn default() -> Self {
793        let mut t = Self::empty();
794        t = t.add_default_methods(true);
795
796        #[cfg(feature = "bevy_render")]
797        {
798            t = t.add_default_methods(false);
799        }
800
801        t
802    }
803}
804
805impl Plugin for RemotePlugin {
806    fn build(&self, app: &mut App) {
807        let mut remote_methods = RemoteMethods::new();
808
809        let plugin_methods = &mut *self.methods.write().unwrap();
810        for (name, handler) in plugin_methods.drain(..) {
811            remote_methods.insert(
812                name.clone(),
813                match handler {
814                    RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant(
815                        app.main_mut().world_mut().register_boxed_system(system),
816                    ),
817                    RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching(
818                        app.main_mut().world_mut().register_boxed_system(system),
819                    ),
820                },
821            );
822        }
823
824        if remote_methods
825            .0
826            .contains_key(builtin_methods::BRP_SCHEDULE_GRAPH)
827        {
828            app.init_resource::<PreviousScheduleBuildMetadata>()
829                .add_observer(cache_schedule_build_metadata);
830        }
831
832        app.init_schedule(RemoteLast)
833            .world_mut()
834            .resource_mut::<MainScheduleOrder>()
835            .insert_after(Last, RemoteLast);
836
837        app.insert_resource(remote_methods)
838            .init_resource::<schemas::SchemaTypesMetadata>()
839            .init_resource::<RemoteWatchingRequests>()
840            .init_resource::<builtin_methods::BrpEventObservers>()
841            .add_systems(PreStartup, setup_mailbox_channel)
842            .configure_sets(
843                RemoteLast,
844                (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(),
845            )
846            .add_systems(
847                RemoteLast,
848                (
849                    (process_remote_requests, process_ongoing_watching_requests)
850                        .chain()
851                        .in_set(RemoteSystems::ProcessRequests),
852                    remove_closed_watching_requests.in_set(RemoteSystems::Cleanup),
853                ),
854            );
855
856        #[cfg(feature = "bevy_render")]
857        {
858            use bevy_ecs::schedule::common_conditions::run_once;
859
860            let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
861                return;
862            };
863
864            let mut render_remote_methods = RemoteMethods::new();
865
866            let render_plugin_methods = &mut *self.render_methods.write().unwrap();
867            for (name, handler) in render_plugin_methods.drain(..) {
868                render_remote_methods.insert(
869                    name,
870                    match handler {
871                        RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant(
872                            render_app.world_mut().register_boxed_system(system),
873                        ),
874                        RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching(
875                            render_app.world_mut().register_boxed_system(system),
876                        ),
877                    },
878                );
879            }
880
881            render_app
882                .init_schedule(RemoteLast)
883                .world_mut()
884                .resource_mut::<RenderScheduleOrder>()
885                .insert_after(Render, RemoteLast);
886
887            render_app
888                .insert_resource(render_remote_methods)
889                .init_resource::<schemas::SchemaTypesMetadata>()
890                .init_resource::<RemoteWatchingRequests>()
891                .add_systems(RenderStartup, setup_mailbox_channel.run_if(run_once))
892                .configure_sets(
893                    RemoteLast,
894                    (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(),
895                )
896                .add_systems(
897                    RemoteLast,
898                    (
899                        (process_remote_requests, process_ongoing_watching_requests)
900                            .chain()
901                            .in_set(RemoteSystems::ProcessRequests),
902                        remove_closed_watching_requests.in_set(RemoteSystems::Cleanup),
903                    ),
904                );
905        }
906    }
907}
908
909/// Schedule that contains all systems to process Bevy Remote Protocol requests
910#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
911pub struct RemoteLast;
912
913/// The systems sets of the [`RemoteLast`] schedule.
914///
915/// These can be useful for ordering.
916#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
917pub enum RemoteSystems {
918    /// Processing of remote requests.
919    ProcessRequests,
920    /// Cleanup (remove closed watchers etc)
921    Cleanup,
922}
923
924/// A type to hold the allowed types of systems to be used as method handlers.
925#[derive(Debug)]
926pub enum RemoteMethodHandler {
927    /// A handler that only runs once and returns one response.
928    Instant(Box<dyn System<In = In<Option<Value>>, Out = BrpResult>>),
929    /// A handler that watches for changes and response when a change is detected.
930    Watching(Box<dyn System<In = In<Option<Value>>, Out = BrpResult<Option<Value>>>>),
931}
932
933/// The [`SystemId`] of a function that implements a remote instant method (`world.get_components`, `world.query`, etc.)
934///
935/// The first parameter is the JSON value of the `params`. Typically, an
936/// implementation will deserialize these as the first thing they do.
937///
938/// The returned JSON value will be returned as the response. Bevy will
939/// automatically populate the `id` field before sending.
940pub type RemoteInstantMethodSystemId = SystemId<In<Option<Value>>, BrpResult>;
941
942/// The [`SystemId`] of a function that implements a remote watching method (`world.get_components+watch`, `world.list_components+watch`, etc.)
943///
944/// The first parameter is the JSON value of the `params`. Typically, an
945/// implementation will deserialize these as the first thing they do.
946///
947/// The optional returned JSON value will be sent as a response. If no
948/// changes were detected this should be [`None`]. Re-running of this
949/// handler is done in the [`RemotePlugin`].
950pub type RemoteWatchingMethodSystemId = SystemId<In<Option<Value>>, BrpResult<Option<Value>>>;
951
952/// The [`SystemId`] of a function that can be used as a remote method.
953#[derive(Debug, Clone, Copy)]
954pub enum RemoteMethodSystemId {
955    /// A handler that only runs once and returns one response.
956    Instant(RemoteInstantMethodSystemId),
957    /// A handler that watches for changes and response when a change is detected.
958    Watching(RemoteWatchingMethodSystemId),
959}
960
961/// Holds all implementations of methods known to the server.
962///
963/// Custom methods can be added to this list using [`RemoteMethods::insert`].
964#[derive(Debug, Resource, Default)]
965pub struct RemoteMethods(HashMap<String, RemoteMethodSystemId>);
966
967impl RemoteMethods {
968    /// Creates a new [`RemoteMethods`] resource with no methods registered in it.
969    pub fn new() -> Self {
970        default()
971    }
972
973    /// Adds a new method, replacing any existing method with that name.
974    ///
975    /// If there was an existing method with that name, returns its handler.
976    pub fn insert(
977        &mut self,
978        method_name: impl Into<String>,
979        handler: RemoteMethodSystemId,
980    ) -> Option<RemoteMethodSystemId> {
981        self.0.insert(method_name.into(), handler)
982    }
983
984    /// Get a [`RemoteMethodSystemId`] with its method name.
985    pub fn get(&self, method: &str) -> Option<&RemoteMethodSystemId> {
986        self.0.get(method)
987    }
988
989    /// Get a [`Vec<String>`] with method names.
990    pub fn methods(&self) -> Vec<String> {
991        self.0.keys().cloned().collect()
992    }
993}
994
995/// Holds the [`BrpMessage`]'s of all ongoing watching requests along with their handlers.
996#[derive(Debug, Resource, Default)]
997pub struct RemoteWatchingRequests(Vec<(BrpMessage, RemoteWatchingMethodSystemId)>);
998
999/// A single request from a Bevy Remote Protocol client to the server,
1000/// serialized in JSON.
1001///
1002/// The JSON payload is expected to look like this:
1003///
1004/// ```json
1005/// {
1006///     "jsonrpc": "2.0",
1007///     "method": "world.get_components",
1008///     "id": 0,
1009///     "params": {
1010///         "entity": 4294967298,
1011///         "components": [
1012///             "bevy_transform::components::transform::Transform"
1013///         ]
1014///     }
1015/// }
1016/// ```
1017/// Or, to list all the fully-qualified type paths in **your** project, pass Null to the
1018/// `params`.
1019/// ```json
1020/// {
1021///    "jsonrpc": "2.0",
1022///    "method": "world.list_components",
1023///    "id": 0,
1024///    "params": null
1025///}
1026///```
1027///
1028/// In Rust:
1029/// ```
1030/// # use bevy_remote::builtin_methods::BRP_LIST_COMPONENTS_METHOD;
1031/// # use bevy_remote::BrpRequest;
1032/// # use serde_json::Value;
1033/// let req = BrpRequest {
1034///     method: BRP_LIST_COMPONENTS_METHOD.to_string(), // All the methods are consts
1035///     id: Some(Value::from(1)),
1036///     params: None,
1037/// };
1038/// ```
1039#[derive(Debug, Clone)]
1040pub struct BrpRequest {
1041    /// The action to be performed.
1042    pub method: String,
1043
1044    /// Arbitrary data that will be returned verbatim to the client as part of
1045    /// the response.
1046    pub id: Option<Value>,
1047
1048    /// The parameters, specific to each method.
1049    ///
1050    /// These are passed as the first argument to the method handler.
1051    /// Sometimes params can be omitted.
1052    pub params: Option<Value>,
1053}
1054
1055// BRP uses json-rpc 2.0, so we need to include `"jsonrpc":"2.0"` in the json output
1056// and check for its presence in the input.
1057// This is similar to the inverse of `#[serde(skip)]`, but serde doesn't provide
1058// an attribute for this behavior so we need a manual ser/de implementation.
1059impl Serialize for BrpRequest {
1060    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1061    where
1062        S: serde::Serializer,
1063    {
1064        let mut map = serializer.serialize_map(None)?;
1065        map.serialize_entry("jsonrpc", "2.0")?;
1066        map.serialize_entry("method", &self.method)?;
1067        if self.id.is_some() {
1068            map.serialize_entry("id", &self.id)?;
1069        }
1070        if self.params.is_some() {
1071            map.serialize_entry("params", &self.params)?;
1072        }
1073        map.end()
1074    }
1075}
1076
1077impl<'de> Deserialize<'de> for BrpRequest {
1078    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1079    where
1080        D: serde::de::Deserializer<'de>,
1081    {
1082        use serde::de;
1083
1084        #[derive(Deserialize)]
1085        #[serde(field_identifier, rename_all = "lowercase")]
1086        enum Field {
1087            JsonRpc,
1088            Method,
1089            Id,
1090            Params,
1091        }
1092
1093        struct Visitor;
1094
1095        impl<'de> de::Visitor<'de> for Visitor {
1096            type Value = BrpRequest;
1097
1098            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
1099                formatter.write_str("struct BrpRequest")
1100            }
1101
1102            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
1103            where
1104                V: de::MapAccess<'de>,
1105            {
1106                let mut jsonrpc = false;
1107                let mut method = None;
1108                let mut id = None;
1109                let mut params = None;
1110                while let Some(key) = map.next_key()? {
1111                    match key {
1112                        Field::JsonRpc => {
1113                            let value = map.next_value::<String>()?;
1114                            if value != "2.0" {
1115                                return Err(de::Error::invalid_value(
1116                                    de::Unexpected::Str(&value),
1117                                    &"2.0",
1118                                ));
1119                            }
1120                            if jsonrpc {
1121                                return Err(de::Error::duplicate_field("jsonrpc"));
1122                            }
1123                            jsonrpc = true;
1124                        }
1125                        Field::Method => {
1126                            if method.is_some() {
1127                                return Err(de::Error::duplicate_field("method"));
1128                            }
1129                            method = Some(map.next_value()?);
1130                        }
1131                        Field::Id => {
1132                            if id.is_some() {
1133                                return Err(de::Error::duplicate_field("id"));
1134                            }
1135                            id = Some(map.next_value()?);
1136                        }
1137                        Field::Params => {
1138                            if params.is_some() {
1139                                return Err(de::Error::duplicate_field("params"));
1140                            }
1141                            params = Some(map.next_value()?);
1142                        }
1143                    }
1144                }
1145                if !jsonrpc {
1146                    return Err(de::Error::missing_field("jsonrpc"));
1147                }
1148                let method = method.ok_or_else(|| de::Error::missing_field("method"))?;
1149                let id = id.flatten();
1150                let params = params.flatten();
1151                Ok(BrpRequest { method, id, params })
1152            }
1153        }
1154
1155        deserializer.deserialize_map(Visitor)
1156    }
1157}
1158
1159/// A response according to BRP.
1160#[derive(Debug, Clone)]
1161pub struct BrpResponse {
1162    /// The id of the original request.
1163    pub id: Option<Value>,
1164
1165    /// The actual response payload.
1166    pub payload: BrpPayload,
1167}
1168
1169// BRP uses json-rpc 2.0, so we need to include `"jsonrpc":"2.0"` in the json output
1170// and check for its presence in the input.
1171impl Serialize for BrpResponse {
1172    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1173    where
1174        S: serde::Serializer,
1175    {
1176        let mut map = serializer.serialize_map(None)?;
1177        map.serialize_entry("jsonrpc", "2.0")?;
1178        if self.id.is_some() {
1179            map.serialize_entry("id", &self.id)?;
1180        }
1181        match &self.payload {
1182            BrpPayload::Result(value) => {
1183                map.serialize_entry("result", value)?;
1184            }
1185            BrpPayload::Error(error) => {
1186                map.serialize_entry("error", error)?;
1187            }
1188        }
1189        map.end()
1190    }
1191}
1192
1193impl<'de> Deserialize<'de> for BrpResponse {
1194    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1195    where
1196        D: serde::de::Deserializer<'de>,
1197    {
1198        use serde::de;
1199
1200        #[derive(Deserialize)]
1201        #[serde(field_identifier, rename_all = "lowercase")]
1202        enum Field {
1203            JsonRpc,
1204            Id,
1205            Result,
1206            Error,
1207        }
1208
1209        struct Visitor;
1210
1211        impl<'de> de::Visitor<'de> for Visitor {
1212            type Value = BrpResponse;
1213
1214            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
1215                formatter.write_str("struct BrpResponse")
1216            }
1217
1218            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
1219            where
1220                V: de::MapAccess<'de>,
1221            {
1222                let mut jsonrpc = false;
1223                let mut id = None;
1224                let mut payload = None;
1225                while let Some(key) = map.next_key()? {
1226                    match key {
1227                        Field::JsonRpc => {
1228                            let value = map.next_value::<String>()?;
1229                            if value != "2.0" {
1230                                return Err(de::Error::invalid_value(
1231                                    de::Unexpected::Str(&value),
1232                                    &"2.0",
1233                                ));
1234                            }
1235                            if jsonrpc {
1236                                return Err(de::Error::duplicate_field("jsonrpc"));
1237                            }
1238                            jsonrpc = true;
1239                        }
1240                        Field::Id => {
1241                            if id.is_some() {
1242                                return Err(de::Error::duplicate_field("id"));
1243                            }
1244                            id = Some(map.next_value()?);
1245                        }
1246                        Field::Result => {
1247                            if payload.is_some() {
1248                                return Err(de::Error::duplicate_field("payload"));
1249                            }
1250                            payload = Some(BrpPayload::Result(map.next_value()?));
1251                        }
1252                        Field::Error => {
1253                            if payload.is_some() {
1254                                return Err(de::Error::duplicate_field("payload"));
1255                            }
1256                            payload = Some(BrpPayload::Error(map.next_value()?));
1257                        }
1258                    }
1259                }
1260                if !jsonrpc {
1261                    return Err(de::Error::missing_field("jsonrpc"));
1262                }
1263                let payload = payload.ok_or_else(|| de::Error::missing_field("payload"))?;
1264                Ok(BrpResponse { id, payload })
1265            }
1266        }
1267
1268        deserializer.deserialize_map(Visitor)
1269    }
1270}
1271
1272impl BrpResponse {
1273    /// Generates a [`BrpResponse`] from an id and a `Result`.
1274    #[must_use]
1275    pub fn new(id: Option<Value>, result: BrpResult) -> Self {
1276        Self {
1277            id,
1278            payload: BrpPayload::from(result),
1279        }
1280    }
1281}
1282
1283/// A result/error payload present in every response.
1284#[derive(Debug, Serialize, Deserialize, Clone)]
1285#[serde(rename_all = "snake_case")]
1286pub enum BrpPayload {
1287    /// `Ok` variant
1288    Result(Value),
1289    /// `Err` variant
1290    Error(BrpError),
1291}
1292
1293impl From<BrpResult> for BrpPayload {
1294    fn from(value: BrpResult) -> Self {
1295        match value {
1296            Ok(v) => Self::Result(v),
1297            Err(err) => Self::Error(err),
1298        }
1299    }
1300}
1301
1302/// An error a request might return.
1303#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1304pub struct BrpError {
1305    /// Defines the general type of the error.
1306    pub code: i16,
1307    /// Short, human-readable description of the error.
1308    pub message: String,
1309    /// Optional additional error data.
1310    #[serde(skip_serializing_if = "Option::is_none")]
1311    pub data: Option<Value>,
1312}
1313
1314impl BrpError {
1315    /// Entity wasn't found.
1316    #[must_use]
1317    pub fn entity_not_found(entity: Entity) -> Self {
1318        Self {
1319            code: error_codes::ENTITY_NOT_FOUND,
1320            message: format!("Entity {entity} not found"),
1321            data: None,
1322        }
1323    }
1324
1325    /// Component wasn't found in an entity.
1326    #[must_use]
1327    pub fn component_not_present(component: &str, entity: Entity) -> Self {
1328        Self {
1329            code: error_codes::COMPONENT_NOT_PRESENT,
1330            message: format!("Component `{component}` not present in Entity {entity}"),
1331            data: None,
1332        }
1333    }
1334
1335    /// An arbitrary component error. Possibly related to reflection.
1336    #[must_use]
1337    pub fn component_error<E: ToString>(error: E) -> Self {
1338        Self {
1339            code: error_codes::COMPONENT_ERROR,
1340            message: error.to_string(),
1341            data: None,
1342        }
1343    }
1344
1345    /// Resource was not present in the world.
1346    #[must_use]
1347    pub fn resource_not_present(resource: &str) -> Self {
1348        Self {
1349            code: error_codes::RESOURCE_NOT_PRESENT,
1350            message: format!("Resource `{resource}` not present in the world"),
1351            data: None,
1352        }
1353    }
1354
1355    /// An arbitrary resource error. Possibly related to reflection.
1356    #[must_use]
1357    pub fn resource_error<E: ToString>(error: E) -> Self {
1358        Self {
1359            code: error_codes::RESOURCE_ERROR,
1360            message: error.to_string(),
1361            data: None,
1362        }
1363    }
1364
1365    /// An arbitrary internal error.
1366    #[must_use]
1367    pub fn internal<E: ToString>(error: E) -> Self {
1368        Self {
1369            code: error_codes::INTERNAL_ERROR,
1370            message: error.to_string(),
1371            data: None,
1372        }
1373    }
1374
1375    /// Attempt to reparent an entity to itself.
1376    #[must_use]
1377    pub fn self_reparent(entity: Entity) -> Self {
1378        Self {
1379            code: error_codes::SELF_REPARENT,
1380            message: format!("Cannot reparent Entity {entity} to itself"),
1381            data: None,
1382        }
1383    }
1384}
1385
1386/// Error codes used by BRP.
1387pub mod error_codes {
1388    // JSON-RPC errors
1389    // Note that the range -32728 to -32000 (inclusive) is reserved by the JSON-RPC specification.
1390
1391    /// Invalid JSON.
1392    pub const PARSE_ERROR: i16 = -32700;
1393
1394    /// JSON sent is not a valid request object.
1395    pub const INVALID_REQUEST: i16 = -32600;
1396
1397    /// The method does not exist / is not available.
1398    pub const METHOD_NOT_FOUND: i16 = -32601;
1399
1400    /// Invalid method parameter(s).
1401    pub const INVALID_PARAMS: i16 = -32602;
1402
1403    /// Internal error.
1404    pub const INTERNAL_ERROR: i16 = -32603;
1405
1406    // Bevy errors (i.e. application errors)
1407
1408    /// Entity not found.
1409    pub const ENTITY_NOT_FOUND: i16 = -23401;
1410
1411    /// Could not reflect or find component.
1412    pub const COMPONENT_ERROR: i16 = -23402;
1413
1414    /// Could not find component in entity.
1415    pub const COMPONENT_NOT_PRESENT: i16 = -23403;
1416
1417    /// Cannot reparent an entity to itself.
1418    pub const SELF_REPARENT: i16 = -23404;
1419
1420    /// Could not reflect or find resource.
1421    pub const RESOURCE_ERROR: i16 = -23501;
1422
1423    /// Could not find resource in the world.
1424    pub const RESOURCE_NOT_PRESENT: i16 = -23502;
1425}
1426
1427/// The result of a request.
1428pub type BrpResult<T = Value> = Result<T, BrpError>;
1429
1430/// The requests may occur on their own or in batches.
1431/// Actual parsing is deferred for the sake of proper
1432/// error reporting.
1433#[derive(Debug, Clone, Serialize, Deserialize)]
1434#[serde(untagged)]
1435pub enum BrpBatch {
1436    /// Multiple requests with deferred parsing.
1437    Batch(Vec<Value>),
1438    /// A single request with deferred parsing.
1439    Single(Value),
1440}
1441
1442/// A message from the Bevy Remote Protocol server thread to the main world.
1443///
1444/// This is placed in the [`BrpReceiver`].
1445#[derive(Debug, Clone)]
1446pub struct BrpMessage {
1447    /// The request method.
1448    pub method: String,
1449
1450    /// The request params.
1451    pub params: Option<Value>,
1452
1453    /// The channel on which the response is to be sent.
1454    ///
1455    /// The value sent here is serialized and sent back to the client.
1456    pub sender: Sender<BrpResult>,
1457}
1458
1459/// A resource holding the matching sender for the [`BrpReceiver`]'s receiver.
1460#[derive(Debug, Resource, Deref, DerefMut)]
1461pub struct BrpSender(Sender<BrpMessage>);
1462
1463/// A resource that receives messages sent by Bevy Remote Protocol clients.
1464///
1465/// Every frame, the `process_remote_requests` system drains this mailbox and
1466/// processes the messages within.
1467#[derive(Debug, Resource, Deref, DerefMut)]
1468pub struct BrpReceiver(Receiver<BrpMessage>);
1469
1470fn setup_mailbox_channel(mut commands: Commands) {
1471    // Create the channel and the mailbox.
1472    let (request_sender, request_receiver) = async_channel::bounded(CHANNEL_SIZE);
1473    commands.insert_resource(BrpSender(request_sender));
1474    commands.insert_resource(BrpReceiver(request_receiver));
1475}
1476
1477/// A system that receives requests placed in the [`BrpReceiver`] and processes
1478/// them, using the [`RemoteMethods`] resource to map each request to its handler.
1479///
1480/// This needs exclusive access to the [`World`] because clients can manipulate
1481/// anything in the ECS.
1482fn process_remote_requests(world: &mut World) {
1483    if !world.contains_resource::<BrpReceiver>() {
1484        return;
1485    }
1486
1487    while let Ok(message) = world.resource_mut::<BrpReceiver>().try_recv() {
1488        // Fetch the handler for the method. If there's no such handler
1489        // registered, return an error.
1490        let Some(&handler) = world.resource::<RemoteMethods>().get(&message.method) else {
1491            let _ = message.sender.force_send(Err(BrpError {
1492                code: error_codes::METHOD_NOT_FOUND,
1493                message: format!("Method `{}` not found", message.method),
1494                data: None,
1495            }));
1496            return;
1497        };
1498
1499        match handler {
1500            RemoteMethodSystemId::Instant(id) => {
1501                let result = match world.run_system_with(id, message.params) {
1502                    Ok(result) => result,
1503                    Err(error) => {
1504                        let _ = message.sender.force_send(Err(BrpError {
1505                            code: error_codes::INTERNAL_ERROR,
1506                            message: format!("Failed to run method handler: {error}"),
1507                            data: None,
1508                        }));
1509                        continue;
1510                    }
1511                };
1512
1513                let _ = message.sender.force_send(result);
1514            }
1515            RemoteMethodSystemId::Watching(id) => {
1516                world
1517                    .resource_mut::<RemoteWatchingRequests>()
1518                    .0
1519                    .push((message, id));
1520            }
1521        }
1522    }
1523}
1524
1525/// A system that checks all ongoing watching requests for changes that should be sent
1526/// and handles it if so.
1527fn process_ongoing_watching_requests(world: &mut World) {
1528    world.resource_scope::<RemoteWatchingRequests, ()>(|world, requests| {
1529        for (message, system_id) in requests.0.iter() {
1530            let handler_result = process_single_ongoing_watching_request(world, message, system_id);
1531            let sender_result = match handler_result {
1532                Ok(Some(value)) => message.sender.try_send(Ok(value)),
1533                Err(err) => message.sender.try_send(Err(err)),
1534                Ok(None) => continue,
1535            };
1536
1537            if sender_result.is_err() {
1538                // The [`remove_closed_watching_requests`] system will clean this up.
1539                message.sender.close();
1540            }
1541        }
1542    });
1543}
1544
1545fn process_single_ongoing_watching_request(
1546    world: &mut World,
1547    message: &BrpMessage,
1548    system_id: &RemoteWatchingMethodSystemId,
1549) -> BrpResult<Option<Value>> {
1550    world
1551        .run_system_with(*system_id, message.params.clone())
1552        .map_err(|error| BrpError {
1553            code: error_codes::INTERNAL_ERROR,
1554            message: format!("Failed to run method handler: {error}"),
1555            data: None,
1556        })?
1557}
1558
1559fn remove_closed_watching_requests(mut requests: ResMut<RemoteWatchingRequests>) {
1560    for i in (0..requests.0.len()).rev() {
1561        let Some((message, _)) = requests.0.get(i) else {
1562            unreachable!()
1563        };
1564
1565        if message.sender.is_closed() {
1566            requests.0.swap_remove(i);
1567        }
1568    }
1569}
1570
1571/// Resource tracking the last [`ScheduleBuildMetadata`] of each schedule as it is built.
1572///
1573/// This allows the `schedule.graph` endpoint to return better results for schedules.
1574#[derive(Resource, Default)]
1575struct PreviousScheduleBuildMetadata(HashMap<InternedScheduleLabel, ScheduleBuildMetadata>);
1576
1577fn cache_schedule_build_metadata(
1578    event: On<ScheduleBuilt>,
1579    mut metadata: ResMut<PreviousScheduleBuildMetadata>,
1580) {
1581    let new_metadata = ScheduleBuildMetadata {
1582        // We intentionally don't bother cloning the warnings, since they aren't used by the
1583        // `ScheduleData`.
1584        warnings: vec![],
1585        edges_added_by_build_passes: event.build_metadata.edges_added_by_build_passes.clone(),
1586    };
1587    metadata.0.insert(event.label, new_metadata);
1588}
1589
1590#[cfg(test)]
1591mod tests {
1592    use crate::BrpRequest;
1593    use serde_json::json;
1594
1595    #[test]
1596    fn deserialize_brp_request_params_optional() {
1597        let request_json: &str = r#"{
1598            "jsonrpc": "2.0",
1599            "method": "world.list_components",
1600            "id": 1
1601        }"#;
1602
1603        let request: BrpRequest = serde_json::from_str(request_json).unwrap();
1604
1605        assert_eq!(request.method, "world.list_components");
1606        assert_eq!(request.id, Some(json!(1)));
1607        assert_eq!(request.params, None);
1608    }
1609
1610    #[test]
1611    fn deserialize_brp_request_id_optional() {
1612        let request_json: &str = r#"{
1613            "jsonrpc": "2.0",
1614            "method": "world.list_components",
1615            "params": {
1616                "number": 5
1617            }
1618        }"#;
1619
1620        let request: BrpRequest = serde_json::from_str(request_json).unwrap();
1621
1622        assert_eq!(request.method, "world.list_components");
1623        assert_eq!(request.id, None);
1624        assert_eq!(request.params, Some(json!({ "number": 5 })));
1625    }
1626}