Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_remote/src/lib.rs
9328 views
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_core_pipeline::tonemapping::DebandDither": "Enabled",
213
//! "bevy_core_pipeline::tonemapping::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
535
extern crate alloc;
536
537
use async_channel::{Receiver, Sender};
538
use bevy_app::{prelude::*, MainScheduleOrder};
539
use bevy_derive::{Deref, DerefMut};
540
use bevy_ecs::{
541
entity::Entity,
542
resource::Resource,
543
schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet},
544
system::{Commands, In, IntoSystem, ResMut, System, SystemId},
545
world::World,
546
};
547
use bevy_platform::collections::HashMap;
548
use bevy_utils::prelude::default;
549
use serde::{Deserialize, Serialize};
550
use serde_json::Value;
551
use std::sync::RwLock;
552
553
pub mod builtin_methods;
554
#[cfg(feature = "http")]
555
pub mod http;
556
pub mod schemas;
557
558
const CHANNEL_SIZE: usize = 16;
559
560
/// Add this plugin to your [`App`] to allow remote connections to inspect and modify entities.
561
///
562
/// This the main plugin for `bevy_remote`. See the [crate-level documentation] for details on
563
/// the available protocols and its default methods.
564
///
565
/// [crate-level documentation]: crate
566
pub struct RemotePlugin {
567
/// The verbs that the server will recognize and respond to.
568
methods: RwLock<Vec<(String, RemoteMethodHandler)>>,
569
}
570
571
impl RemotePlugin {
572
/// Create a [`RemotePlugin`] with the default address and port but without
573
/// any associated methods.
574
fn empty() -> Self {
575
Self {
576
methods: RwLock::new(vec![]),
577
}
578
}
579
580
/// Add a remote method to the plugin using the given `name` and `handler`.
581
#[must_use]
582
pub fn with_method<M>(
583
mut self,
584
name: impl Into<String>,
585
handler: impl IntoSystem<In<Option<Value>>, BrpResult, M>,
586
) -> Self {
587
self.methods.get_mut().unwrap().push((
588
name.into(),
589
RemoteMethodHandler::Instant(Box::new(IntoSystem::into_system(handler))),
590
));
591
self
592
}
593
594
/// Add a remote method with a watching handler to the plugin using the given `name`.
595
#[must_use]
596
pub fn with_watching_method<M>(
597
mut self,
598
name: impl Into<String>,
599
handler: impl IntoSystem<In<Option<Value>>, BrpResult<Option<Value>>, M>,
600
) -> Self {
601
self.methods.get_mut().unwrap().push((
602
name.into(),
603
RemoteMethodHandler::Watching(Box::new(IntoSystem::into_system(handler))),
604
));
605
self
606
}
607
}
608
609
impl Default for RemotePlugin {
610
fn default() -> Self {
611
Self::empty()
612
.with_method(
613
builtin_methods::BRP_GET_COMPONENTS_METHOD,
614
builtin_methods::process_remote_get_components_request,
615
)
616
.with_method(
617
builtin_methods::BRP_QUERY_METHOD,
618
builtin_methods::process_remote_query_request,
619
)
620
.with_method(
621
builtin_methods::BRP_SPAWN_ENTITY_METHOD,
622
builtin_methods::process_remote_spawn_entity_request,
623
)
624
.with_method(
625
builtin_methods::BRP_INSERT_COMPONENTS_METHOD,
626
builtin_methods::process_remote_insert_components_request,
627
)
628
.with_method(
629
builtin_methods::BRP_REMOVE_COMPONENTS_METHOD,
630
builtin_methods::process_remote_remove_components_request,
631
)
632
.with_method(
633
builtin_methods::BRP_DESPAWN_COMPONENTS_METHOD,
634
builtin_methods::process_remote_despawn_entity_request,
635
)
636
.with_method(
637
builtin_methods::BRP_REPARENT_ENTITIES_METHOD,
638
builtin_methods::process_remote_reparent_entities_request,
639
)
640
.with_method(
641
builtin_methods::BRP_LIST_COMPONENTS_METHOD,
642
builtin_methods::process_remote_list_components_request,
643
)
644
.with_method(
645
builtin_methods::BRP_MUTATE_COMPONENTS_METHOD,
646
builtin_methods::process_remote_mutate_components_request,
647
)
648
.with_method(
649
builtin_methods::RPC_DISCOVER_METHOD,
650
builtin_methods::process_remote_list_methods_request,
651
)
652
.with_watching_method(
653
builtin_methods::BRP_GET_COMPONENTS_AND_WATCH_METHOD,
654
builtin_methods::process_remote_get_components_watching_request,
655
)
656
.with_watching_method(
657
builtin_methods::BRP_LIST_COMPONENTS_AND_WATCH_METHOD,
658
builtin_methods::process_remote_list_components_watching_request,
659
)
660
.with_method(
661
builtin_methods::BRP_GET_RESOURCE_METHOD,
662
builtin_methods::process_remote_get_resources_request,
663
)
664
.with_method(
665
builtin_methods::BRP_INSERT_RESOURCE_METHOD,
666
builtin_methods::process_remote_insert_resources_request,
667
)
668
.with_method(
669
builtin_methods::BRP_REMOVE_RESOURCE_METHOD,
670
builtin_methods::process_remote_remove_resources_request,
671
)
672
.with_method(
673
builtin_methods::BRP_MUTATE_RESOURCE_METHOD,
674
builtin_methods::process_remote_mutate_resources_request,
675
)
676
.with_method(
677
builtin_methods::BRP_LIST_RESOURCES_METHOD,
678
builtin_methods::process_remote_list_resources_request,
679
)
680
.with_method(
681
builtin_methods::BRP_TRIGGER_EVENT_METHOD,
682
builtin_methods::process_remote_trigger_event_request,
683
)
684
.with_method(
685
builtin_methods::BRP_REGISTRY_SCHEMA_METHOD,
686
builtin_methods::export_registry_types,
687
)
688
}
689
}
690
691
impl Plugin for RemotePlugin {
692
fn build(&self, app: &mut App) {
693
let mut remote_methods = RemoteMethods::new();
694
695
let plugin_methods = &mut *self.methods.write().unwrap();
696
for (name, handler) in plugin_methods.drain(..) {
697
remote_methods.insert(
698
name,
699
match handler {
700
RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant(
701
app.main_mut().world_mut().register_boxed_system(system),
702
),
703
RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching(
704
app.main_mut().world_mut().register_boxed_system(system),
705
),
706
},
707
);
708
}
709
710
app.init_schedule(RemoteLast)
711
.world_mut()
712
.resource_mut::<MainScheduleOrder>()
713
.insert_after(Last, RemoteLast);
714
715
app.insert_resource(remote_methods)
716
.init_resource::<schemas::SchemaTypesMetadata>()
717
.init_resource::<RemoteWatchingRequests>()
718
.add_systems(PreStartup, setup_mailbox_channel)
719
.configure_sets(
720
RemoteLast,
721
(RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(),
722
)
723
.add_systems(
724
RemoteLast,
725
(
726
(process_remote_requests, process_ongoing_watching_requests)
727
.chain()
728
.in_set(RemoteSystems::ProcessRequests),
729
remove_closed_watching_requests.in_set(RemoteSystems::Cleanup),
730
),
731
);
732
}
733
}
734
735
/// Schedule that contains all systems to process Bevy Remote Protocol requests
736
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
737
pub struct RemoteLast;
738
739
/// The systems sets of the [`RemoteLast`] schedule.
740
///
741
/// These can be useful for ordering.
742
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
743
pub enum RemoteSystems {
744
/// Processing of remote requests.
745
ProcessRequests,
746
/// Cleanup (remove closed watchers etc)
747
Cleanup,
748
}
749
750
/// A type to hold the allowed types of systems to be used as method handlers.
751
#[derive(Debug)]
752
pub enum RemoteMethodHandler {
753
/// A handler that only runs once and returns one response.
754
Instant(Box<dyn System<In = In<Option<Value>>, Out = BrpResult>>),
755
/// A handler that watches for changes and response when a change is detected.
756
Watching(Box<dyn System<In = In<Option<Value>>, Out = BrpResult<Option<Value>>>>),
757
}
758
759
/// The [`SystemId`] of a function that implements a remote instant method (`world.get_components`, `world.query`, etc.)
760
///
761
/// The first parameter is the JSON value of the `params`. Typically, an
762
/// implementation will deserialize these as the first thing they do.
763
///
764
/// The returned JSON value will be returned as the response. Bevy will
765
/// automatically populate the `id` field before sending.
766
pub type RemoteInstantMethodSystemId = SystemId<In<Option<Value>>, BrpResult>;
767
768
/// The [`SystemId`] of a function that implements a remote watching method (`world.get_components+watch`, `world.list_components+watch`, etc.)
769
///
770
/// The first parameter is the JSON value of the `params`. Typically, an
771
/// implementation will deserialize these as the first thing they do.
772
///
773
/// The optional returned JSON value will be sent as a response. If no
774
/// changes were detected this should be [`None`]. Re-running of this
775
/// handler is done in the [`RemotePlugin`].
776
pub type RemoteWatchingMethodSystemId = SystemId<In<Option<Value>>, BrpResult<Option<Value>>>;
777
778
/// The [`SystemId`] of a function that can be used as a remote method.
779
#[derive(Debug, Clone, Copy)]
780
pub enum RemoteMethodSystemId {
781
/// A handler that only runs once and returns one response.
782
Instant(RemoteInstantMethodSystemId),
783
/// A handler that watches for changes and response when a change is detected.
784
Watching(RemoteWatchingMethodSystemId),
785
}
786
787
/// Holds all implementations of methods known to the server.
788
///
789
/// Custom methods can be added to this list using [`RemoteMethods::insert`].
790
#[derive(Debug, Resource, Default)]
791
pub struct RemoteMethods(HashMap<String, RemoteMethodSystemId>);
792
793
impl RemoteMethods {
794
/// Creates a new [`RemoteMethods`] resource with no methods registered in it.
795
pub fn new() -> Self {
796
default()
797
}
798
799
/// Adds a new method, replacing any existing method with that name.
800
///
801
/// If there was an existing method with that name, returns its handler.
802
pub fn insert(
803
&mut self,
804
method_name: impl Into<String>,
805
handler: RemoteMethodSystemId,
806
) -> Option<RemoteMethodSystemId> {
807
self.0.insert(method_name.into(), handler)
808
}
809
810
/// Get a [`RemoteMethodSystemId`] with its method name.
811
pub fn get(&self, method: &str) -> Option<&RemoteMethodSystemId> {
812
self.0.get(method)
813
}
814
815
/// Get a [`Vec<String>`] with method names.
816
pub fn methods(&self) -> Vec<String> {
817
self.0.keys().cloned().collect()
818
}
819
}
820
821
/// Holds the [`BrpMessage`]'s of all ongoing watching requests along with their handlers.
822
#[derive(Debug, Resource, Default)]
823
pub struct RemoteWatchingRequests(Vec<(BrpMessage, RemoteWatchingMethodSystemId)>);
824
825
/// A single request from a Bevy Remote Protocol client to the server,
826
/// serialized in JSON.
827
///
828
/// The JSON payload is expected to look like this:
829
///
830
/// ```json
831
/// {
832
/// "jsonrpc": "2.0",
833
/// "method": "world.get_components",
834
/// "id": 0,
835
/// "params": {
836
/// "entity": 4294967298,
837
/// "components": [
838
/// "bevy_transform::components::transform::Transform"
839
/// ]
840
/// }
841
/// }
842
/// ```
843
/// Or, to list all the fully-qualified type paths in **your** project, pass Null to the
844
/// `params`.
845
/// ```json
846
/// {
847
/// "jsonrpc": "2.0",
848
/// "method": "world.list_components",
849
/// "id": 0,
850
/// "params": null
851
///}
852
///```
853
///
854
/// In Rust:
855
/// ```ignore
856
/// let req = BrpRequest {
857
/// jsonrpc: "2.0".to_string(),
858
/// method: BRP_LIST_METHOD.to_string(), // All the methods have consts
859
/// id: Some(ureq::json!(0)),
860
/// params: None,
861
/// };
862
/// ```
863
#[derive(Debug, Serialize, Deserialize, Clone)]
864
pub struct BrpRequest {
865
/// This field is mandatory and must be set to `"2.0"` for the request to be accepted.
866
pub jsonrpc: String,
867
868
/// The action to be performed.
869
pub method: String,
870
871
/// Arbitrary data that will be returned verbatim to the client as part of
872
/// the response.
873
#[serde(skip_serializing_if = "Option::is_none")]
874
pub id: Option<Value>,
875
876
/// The parameters, specific to each method.
877
///
878
/// These are passed as the first argument to the method handler.
879
/// Sometimes params can be omitted.
880
#[serde(skip_serializing_if = "Option::is_none")]
881
pub params: Option<Value>,
882
}
883
884
/// A response according to BRP.
885
#[derive(Debug, Serialize, Deserialize, Clone)]
886
pub struct BrpResponse {
887
/// This field is mandatory and must be set to `"2.0"`.
888
pub jsonrpc: &'static str,
889
890
/// The id of the original request.
891
pub id: Option<Value>,
892
893
/// The actual response payload.
894
#[serde(flatten)]
895
pub payload: BrpPayload,
896
}
897
898
impl BrpResponse {
899
/// Generates a [`BrpResponse`] from an id and a `Result`.
900
#[must_use]
901
pub fn new(id: Option<Value>, result: BrpResult) -> Self {
902
Self {
903
jsonrpc: "2.0",
904
id,
905
payload: BrpPayload::from(result),
906
}
907
}
908
}
909
910
/// A result/error payload present in every response.
911
#[derive(Debug, Serialize, Deserialize, Clone)]
912
#[serde(rename_all = "snake_case")]
913
pub enum BrpPayload {
914
/// `Ok` variant
915
Result(Value),
916
/// `Err` variant
917
Error(BrpError),
918
}
919
920
impl From<BrpResult> for BrpPayload {
921
fn from(value: BrpResult) -> Self {
922
match value {
923
Ok(v) => Self::Result(v),
924
Err(err) => Self::Error(err),
925
}
926
}
927
}
928
929
/// An error a request might return.
930
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
931
pub struct BrpError {
932
/// Defines the general type of the error.
933
pub code: i16,
934
/// Short, human-readable description of the error.
935
pub message: String,
936
/// Optional additional error data.
937
#[serde(skip_serializing_if = "Option::is_none")]
938
pub data: Option<Value>,
939
}
940
941
impl BrpError {
942
/// Entity wasn't found.
943
#[must_use]
944
pub fn entity_not_found(entity: Entity) -> Self {
945
Self {
946
code: error_codes::ENTITY_NOT_FOUND,
947
message: format!("Entity {entity} not found"),
948
data: None,
949
}
950
}
951
952
/// Component wasn't found in an entity.
953
#[must_use]
954
pub fn component_not_present(component: &str, entity: Entity) -> Self {
955
Self {
956
code: error_codes::COMPONENT_NOT_PRESENT,
957
message: format!("Component `{component}` not present in Entity {entity}"),
958
data: None,
959
}
960
}
961
962
/// An arbitrary component error. Possibly related to reflection.
963
#[must_use]
964
pub fn component_error<E: ToString>(error: E) -> Self {
965
Self {
966
code: error_codes::COMPONENT_ERROR,
967
message: error.to_string(),
968
data: None,
969
}
970
}
971
972
/// Resource was not present in the world.
973
#[must_use]
974
pub fn resource_not_present(resource: &str) -> Self {
975
Self {
976
code: error_codes::RESOURCE_NOT_PRESENT,
977
message: format!("Resource `{resource}` not present in the world"),
978
data: None,
979
}
980
}
981
982
/// An arbitrary resource error. Possibly related to reflection.
983
#[must_use]
984
pub fn resource_error<E: ToString>(error: E) -> Self {
985
Self {
986
code: error_codes::RESOURCE_ERROR,
987
message: error.to_string(),
988
data: None,
989
}
990
}
991
992
/// An arbitrary internal error.
993
#[must_use]
994
pub fn internal<E: ToString>(error: E) -> Self {
995
Self {
996
code: error_codes::INTERNAL_ERROR,
997
message: error.to_string(),
998
data: None,
999
}
1000
}
1001
1002
/// Attempt to reparent an entity to itself.
1003
#[must_use]
1004
pub fn self_reparent(entity: Entity) -> Self {
1005
Self {
1006
code: error_codes::SELF_REPARENT,
1007
message: format!("Cannot reparent Entity {entity} to itself"),
1008
data: None,
1009
}
1010
}
1011
}
1012
1013
/// Error codes used by BRP.
1014
pub mod error_codes {
1015
// JSON-RPC errors
1016
// Note that the range -32728 to -32000 (inclusive) is reserved by the JSON-RPC specification.
1017
1018
/// Invalid JSON.
1019
pub const PARSE_ERROR: i16 = -32700;
1020
1021
/// JSON sent is not a valid request object.
1022
pub const INVALID_REQUEST: i16 = -32600;
1023
1024
/// The method does not exist / is not available.
1025
pub const METHOD_NOT_FOUND: i16 = -32601;
1026
1027
/// Invalid method parameter(s).
1028
pub const INVALID_PARAMS: i16 = -32602;
1029
1030
/// Internal error.
1031
pub const INTERNAL_ERROR: i16 = -32603;
1032
1033
// Bevy errors (i.e. application errors)
1034
1035
/// Entity not found.
1036
pub const ENTITY_NOT_FOUND: i16 = -23401;
1037
1038
/// Could not reflect or find component.
1039
pub const COMPONENT_ERROR: i16 = -23402;
1040
1041
/// Could not find component in entity.
1042
pub const COMPONENT_NOT_PRESENT: i16 = -23403;
1043
1044
/// Cannot reparent an entity to itself.
1045
pub const SELF_REPARENT: i16 = -23404;
1046
1047
/// Could not reflect or find resource.
1048
pub const RESOURCE_ERROR: i16 = -23501;
1049
1050
/// Could not find resource in the world.
1051
pub const RESOURCE_NOT_PRESENT: i16 = -23502;
1052
}
1053
1054
/// The result of a request.
1055
pub type BrpResult<T = Value> = Result<T, BrpError>;
1056
1057
/// The requests may occur on their own or in batches.
1058
/// Actual parsing is deferred for the sake of proper
1059
/// error reporting.
1060
#[derive(Debug, Clone, Serialize, Deserialize)]
1061
#[serde(untagged)]
1062
pub enum BrpBatch {
1063
/// Multiple requests with deferred parsing.
1064
Batch(Vec<Value>),
1065
/// A single request with deferred parsing.
1066
Single(Value),
1067
}
1068
1069
/// A message from the Bevy Remote Protocol server thread to the main world.
1070
///
1071
/// This is placed in the [`BrpReceiver`].
1072
#[derive(Debug, Clone)]
1073
pub struct BrpMessage {
1074
/// The request method.
1075
pub method: String,
1076
1077
/// The request params.
1078
pub params: Option<Value>,
1079
1080
/// The channel on which the response is to be sent.
1081
///
1082
/// The value sent here is serialized and sent back to the client.
1083
pub sender: Sender<BrpResult>,
1084
}
1085
1086
/// A resource holding the matching sender for the [`BrpReceiver`]'s receiver.
1087
#[derive(Debug, Resource, Deref, DerefMut)]
1088
pub struct BrpSender(Sender<BrpMessage>);
1089
1090
/// A resource that receives messages sent by Bevy Remote Protocol clients.
1091
///
1092
/// Every frame, the `process_remote_requests` system drains this mailbox and
1093
/// processes the messages within.
1094
#[derive(Debug, Resource, Deref, DerefMut)]
1095
pub struct BrpReceiver(Receiver<BrpMessage>);
1096
1097
fn setup_mailbox_channel(mut commands: Commands) {
1098
// Create the channel and the mailbox.
1099
let (request_sender, request_receiver) = async_channel::bounded(CHANNEL_SIZE);
1100
commands.insert_resource(BrpSender(request_sender));
1101
commands.insert_resource(BrpReceiver(request_receiver));
1102
}
1103
1104
/// A system that receives requests placed in the [`BrpReceiver`] and processes
1105
/// them, using the [`RemoteMethods`] resource to map each request to its handler.
1106
///
1107
/// This needs exclusive access to the [`World`] because clients can manipulate
1108
/// anything in the ECS.
1109
fn process_remote_requests(world: &mut World) {
1110
if !world.contains_resource::<BrpReceiver>() {
1111
return;
1112
}
1113
1114
while let Ok(message) = world.resource_mut::<BrpReceiver>().try_recv() {
1115
// Fetch the handler for the method. If there's no such handler
1116
// registered, return an error.
1117
let Some(&handler) = world.resource::<RemoteMethods>().get(&message.method) else {
1118
let _ = message.sender.force_send(Err(BrpError {
1119
code: error_codes::METHOD_NOT_FOUND,
1120
message: format!("Method `{}` not found", message.method),
1121
data: None,
1122
}));
1123
return;
1124
};
1125
1126
match handler {
1127
RemoteMethodSystemId::Instant(id) => {
1128
let result = match world.run_system_with(id, message.params) {
1129
Ok(result) => result,
1130
Err(error) => {
1131
let _ = message.sender.force_send(Err(BrpError {
1132
code: error_codes::INTERNAL_ERROR,
1133
message: format!("Failed to run method handler: {error}"),
1134
data: None,
1135
}));
1136
continue;
1137
}
1138
};
1139
1140
let _ = message.sender.force_send(result);
1141
}
1142
RemoteMethodSystemId::Watching(id) => {
1143
world
1144
.resource_mut::<RemoteWatchingRequests>()
1145
.0
1146
.push((message, id));
1147
}
1148
}
1149
}
1150
}
1151
1152
/// A system that checks all ongoing watching requests for changes that should be sent
1153
/// and handles it if so.
1154
fn process_ongoing_watching_requests(world: &mut World) {
1155
world.resource_scope::<RemoteWatchingRequests, ()>(|world, requests| {
1156
for (message, system_id) in requests.0.iter() {
1157
let handler_result = process_single_ongoing_watching_request(world, message, system_id);
1158
let sender_result = match handler_result {
1159
Ok(Some(value)) => message.sender.try_send(Ok(value)),
1160
Err(err) => message.sender.try_send(Err(err)),
1161
Ok(None) => continue,
1162
};
1163
1164
if sender_result.is_err() {
1165
// The [`remove_closed_watching_requests`] system will clean this up.
1166
message.sender.close();
1167
}
1168
}
1169
});
1170
}
1171
1172
fn process_single_ongoing_watching_request(
1173
world: &mut World,
1174
message: &BrpMessage,
1175
system_id: &RemoteWatchingMethodSystemId,
1176
) -> BrpResult<Option<Value>> {
1177
world
1178
.run_system_with(*system_id, message.params.clone())
1179
.map_err(|error| BrpError {
1180
code: error_codes::INTERNAL_ERROR,
1181
message: format!("Failed to run method handler: {error}"),
1182
data: None,
1183
})?
1184
}
1185
1186
fn remove_closed_watching_requests(mut requests: ResMut<RemoteWatchingRequests>) {
1187
for i in (0..requests.0.len()).rev() {
1188
let Some((message, _)) = requests.0.get(i) else {
1189
unreachable!()
1190
};
1191
1192
if message.sender.is_closed() {
1193
requests.0.swap_remove(i);
1194
}
1195
}
1196
}
1197
1198