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}