Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
//! A Bevy app that you can connect to with the BRP and edit.
|
|
|
|
|
2024-10-08 16:21:46 +00:00
|
|
|
use bevy::math::ops::cos;
|
2024-09-27 20:09:46 +00:00
|
|
|
use bevy::{
|
2024-10-08 16:21:46 +00:00
|
|
|
input::common_conditions::input_just_pressed,
|
2024-09-27 20:09:46 +00:00
|
|
|
prelude::*,
|
|
|
|
remote::{http::RemoteHttpPlugin, RemotePlugin},
|
|
|
|
};
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
App::new()
|
|
|
|
.add_plugins(DefaultPlugins)
|
|
|
|
.add_plugins(RemotePlugin::default())
|
2024-09-27 20:09:46 +00:00
|
|
|
.add_plugins(RemoteHttpPlugin::default())
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
.add_systems(Startup, setup)
|
2024-10-08 16:21:46 +00:00
|
|
|
.add_systems(Update, remove.run_if(input_just_pressed(KeyCode::Space)))
|
|
|
|
.add_systems(Update, move_cube)
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
.register_type::<Cube>()
|
|
|
|
.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn setup(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
|
|
) {
|
|
|
|
// circular base
|
Migrate meshes and materials to required components (#15524)
# Objective
A big step in the migration to required components: meshes and
materials!
## Solution
As per the [selected
proposal](https://hackmd.io/@bevy/required_components/%2Fj9-PnF-2QKK0on1KQ29UWQ):
- Deprecate `MaterialMesh2dBundle`, `MaterialMeshBundle`, and
`PbrBundle`.
- Add `Mesh2d` and `Mesh3d` components, which wrap a `Handle<Mesh>`.
- Add `MeshMaterial2d<M: Material2d>` and `MeshMaterial3d<M: Material>`,
which wrap a `Handle<M>`.
- Meshes *without* a mesh material should be rendered with a default
material. The existence of a material is determined by
`HasMaterial2d`/`HasMaterial3d`, which is required by
`MeshMaterial2d`/`MeshMaterial3d`. This gets around problems with the
generics.
Previously:
```rust
commands.spawn(MaterialMesh2dBundle {
mesh: meshes.add(Circle::new(100.0)).into(),
material: materials.add(Color::srgb(7.5, 0.0, 7.5)),
transform: Transform::from_translation(Vec3::new(-200., 0., 0.)),
..default()
});
```
Now:
```rust
commands.spawn((
Mesh2d(meshes.add(Circle::new(100.0))),
MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))),
Transform::from_translation(Vec3::new(-200., 0., 0.)),
));
```
If the mesh material is missing, previously nothing was rendered. Now,
it renders a white default `ColorMaterial` in 2D and a
`StandardMaterial` in 3D (this can be overridden). Below, only every
other entity has a material:
![Näyttökuva 2024-09-29
181746](https://github.com/user-attachments/assets/5c8be029-d2fe-4b8c-ae89-17a72ff82c9a)
![Näyttökuva 2024-09-29
181918](https://github.com/user-attachments/assets/58adbc55-5a1e-4c7d-a2c7-ed456227b909)
Why white? This is still open for discussion, but I think white makes
sense for a *default* material, while *invalid* asset handles pointing
to nothing should have something like a pink material to indicate that
something is broken (I don't handle that in this PR yet). This is kind
of a mix of Godot and Unity: Godot just renders a white material for
non-existent materials, while Unity renders nothing when no materials
exist, but renders pink for invalid materials. I can also change the
default material to pink if that is preferable though.
## Testing
I ran some 2D and 3D examples to test if anything changed visually. I
have not tested all examples or features yet however. If anyone wants to
test more extensively, it would be appreciated!
## Implementation Notes
- The relationship between `bevy_render` and `bevy_pbr` is weird here.
`bevy_render` needs `Mesh3d` for its own systems, but `bevy_pbr` has all
of the material logic, and `bevy_render` doesn't depend on it. I feel
like the two crates should be refactored in some way, but I think that's
out of scope for this PR.
- I didn't migrate meshlets to required components yet. That can
probably be done in a follow-up, as this is already a huge PR.
- It is becoming increasingly clear to me that we really, *really* want
to disallow raw asset handles as components. They caused me a *ton* of
headache here already, and it took me a long time to find every place
that queried for them or inserted them directly on entities, since there
were no compiler errors for it. If we don't remove the `Component`
derive, I expect raw asset handles to be a *huge* footgun for users as
we transition to wrapper components, especially as handles as components
have been the norm so far. I personally consider this to be a blocker
for 0.15: we need to migrate to wrapper components for asset handles
everywhere, and remove the `Component` derive. Also see
https://github.com/bevyengine/bevy/issues/14124.
---
## Migration Guide
Asset handles for meshes and mesh materials must now be wrapped in the
`Mesh2d` and `MeshMaterial2d` or `Mesh3d` and `MeshMaterial3d`
components for 2D and 3D respectively. Raw handles as components no
longer render meshes.
Additionally, `MaterialMesh2dBundle`, `MaterialMeshBundle`, and
`PbrBundle` have been deprecated. Instead, use the mesh and material
components directly.
Previously:
```rust
commands.spawn(MaterialMesh2dBundle {
mesh: meshes.add(Circle::new(100.0)).into(),
material: materials.add(Color::srgb(7.5, 0.0, 7.5)),
transform: Transform::from_translation(Vec3::new(-200., 0., 0.)),
..default()
});
```
Now:
```rust
commands.spawn((
Mesh2d(meshes.add(Circle::new(100.0))),
MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))),
Transform::from_translation(Vec3::new(-200., 0., 0.)),
));
```
If the mesh material is missing, a white default material is now used.
Previously, nothing was rendered if the material was missing.
The `WithMesh2d` and `WithMesh3d` query filter type aliases have also
been removed. Simply use `With<Mesh2d>` or `With<Mesh3d>`.
---------
Co-authored-by: Tim Blackbird <justthecooldude@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-01 21:33:17 +00:00
|
|
|
commands.spawn((
|
|
|
|
Mesh3d(meshes.add(Circle::new(4.0))),
|
|
|
|
MeshMaterial3d(materials.add(Color::WHITE)),
|
|
|
|
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
|
|
|
));
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
|
|
|
|
// cube
|
|
|
|
commands.spawn((
|
Migrate meshes and materials to required components (#15524)
# Objective
A big step in the migration to required components: meshes and
materials!
## Solution
As per the [selected
proposal](https://hackmd.io/@bevy/required_components/%2Fj9-PnF-2QKK0on1KQ29UWQ):
- Deprecate `MaterialMesh2dBundle`, `MaterialMeshBundle`, and
`PbrBundle`.
- Add `Mesh2d` and `Mesh3d` components, which wrap a `Handle<Mesh>`.
- Add `MeshMaterial2d<M: Material2d>` and `MeshMaterial3d<M: Material>`,
which wrap a `Handle<M>`.
- Meshes *without* a mesh material should be rendered with a default
material. The existence of a material is determined by
`HasMaterial2d`/`HasMaterial3d`, which is required by
`MeshMaterial2d`/`MeshMaterial3d`. This gets around problems with the
generics.
Previously:
```rust
commands.spawn(MaterialMesh2dBundle {
mesh: meshes.add(Circle::new(100.0)).into(),
material: materials.add(Color::srgb(7.5, 0.0, 7.5)),
transform: Transform::from_translation(Vec3::new(-200., 0., 0.)),
..default()
});
```
Now:
```rust
commands.spawn((
Mesh2d(meshes.add(Circle::new(100.0))),
MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))),
Transform::from_translation(Vec3::new(-200., 0., 0.)),
));
```
If the mesh material is missing, previously nothing was rendered. Now,
it renders a white default `ColorMaterial` in 2D and a
`StandardMaterial` in 3D (this can be overridden). Below, only every
other entity has a material:
![Näyttökuva 2024-09-29
181746](https://github.com/user-attachments/assets/5c8be029-d2fe-4b8c-ae89-17a72ff82c9a)
![Näyttökuva 2024-09-29
181918](https://github.com/user-attachments/assets/58adbc55-5a1e-4c7d-a2c7-ed456227b909)
Why white? This is still open for discussion, but I think white makes
sense for a *default* material, while *invalid* asset handles pointing
to nothing should have something like a pink material to indicate that
something is broken (I don't handle that in this PR yet). This is kind
of a mix of Godot and Unity: Godot just renders a white material for
non-existent materials, while Unity renders nothing when no materials
exist, but renders pink for invalid materials. I can also change the
default material to pink if that is preferable though.
## Testing
I ran some 2D and 3D examples to test if anything changed visually. I
have not tested all examples or features yet however. If anyone wants to
test more extensively, it would be appreciated!
## Implementation Notes
- The relationship between `bevy_render` and `bevy_pbr` is weird here.
`bevy_render` needs `Mesh3d` for its own systems, but `bevy_pbr` has all
of the material logic, and `bevy_render` doesn't depend on it. I feel
like the two crates should be refactored in some way, but I think that's
out of scope for this PR.
- I didn't migrate meshlets to required components yet. That can
probably be done in a follow-up, as this is already a huge PR.
- It is becoming increasingly clear to me that we really, *really* want
to disallow raw asset handles as components. They caused me a *ton* of
headache here already, and it took me a long time to find every place
that queried for them or inserted them directly on entities, since there
were no compiler errors for it. If we don't remove the `Component`
derive, I expect raw asset handles to be a *huge* footgun for users as
we transition to wrapper components, especially as handles as components
have been the norm so far. I personally consider this to be a blocker
for 0.15: we need to migrate to wrapper components for asset handles
everywhere, and remove the `Component` derive. Also see
https://github.com/bevyengine/bevy/issues/14124.
---
## Migration Guide
Asset handles for meshes and mesh materials must now be wrapped in the
`Mesh2d` and `MeshMaterial2d` or `Mesh3d` and `MeshMaterial3d`
components for 2D and 3D respectively. Raw handles as components no
longer render meshes.
Additionally, `MaterialMesh2dBundle`, `MaterialMeshBundle`, and
`PbrBundle` have been deprecated. Instead, use the mesh and material
components directly.
Previously:
```rust
commands.spawn(MaterialMesh2dBundle {
mesh: meshes.add(Circle::new(100.0)).into(),
material: materials.add(Color::srgb(7.5, 0.0, 7.5)),
transform: Transform::from_translation(Vec3::new(-200., 0., 0.)),
..default()
});
```
Now:
```rust
commands.spawn((
Mesh2d(meshes.add(Circle::new(100.0))),
MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))),
Transform::from_translation(Vec3::new(-200., 0., 0.)),
));
```
If the mesh material is missing, a white default material is now used.
Previously, nothing was rendered if the material was missing.
The `WithMesh2d` and `WithMesh3d` query filter type aliases have also
been removed. Simply use `With<Mesh2d>` or `With<Mesh3d>`.
---------
Co-authored-by: Tim Blackbird <justthecooldude@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-10-01 21:33:17 +00:00
|
|
|
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
|
|
|
|
MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))),
|
|
|
|
Transform::from_xyz(0.0, 0.5, 0.0),
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
Cube(1.0),
|
|
|
|
));
|
|
|
|
|
|
|
|
// light
|
2024-10-01 03:20:43 +00:00
|
|
|
commands.spawn((
|
|
|
|
PointLight {
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
shadows_enabled: true,
|
|
|
|
..default()
|
|
|
|
},
|
2024-10-01 03:20:43 +00:00
|
|
|
Transform::from_xyz(4.0, 8.0, 4.0),
|
|
|
|
));
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
|
|
|
|
// camera
|
2024-10-05 01:59:52 +00:00
|
|
|
commands.spawn((
|
|
|
|
Camera3d::default(),
|
|
|
|
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
|
|
));
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
}
|
|
|
|
|
2024-10-08 16:21:46 +00:00
|
|
|
fn move_cube(mut query: Query<&mut Transform, With<Cube>>, time: Res<Time>) {
|
|
|
|
for mut transform in &mut query {
|
2024-10-16 21:09:32 +00:00
|
|
|
transform.translation.y = -cos(time.elapsed_secs()) + 1.5;
|
2024-10-08 16:21:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-13 20:32:06 +00:00
|
|
|
fn remove(mut commands: Commands, cube_entity: Single<Entity, With<Cube>>) {
|
|
|
|
commands.entity(*cube_entity).remove::<Cube>();
|
2024-10-08 16:21:46 +00:00
|
|
|
}
|
|
|
|
|
Initial implementation of the Bevy Remote Protocol (Adopted) (#14880)
# Objective
Adopted from #13563.
The goal is to implement the Bevy Remote Protocol over HTTP/JSON,
allowing the ECS to be interacted with remotely.
## Solution
At a high level, there are really two separate things that have been
undertaken here:
1. First, `RemotePlugin` has been created, which has the effect of
embedding a [JSON-RPC](https://www.jsonrpc.org/specification) endpoint
into a Bevy application.
2. Second, the [Bevy Remote Protocol
verbs](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md)
(excluding `POLL`) have been implemented as remote methods for that
JSON-RPC endpoint under a Bevy-exclusive namespace (e.g. `bevy/get`,
`bevy/list`, etc.).
To avoid some repetition, here is the crate-level documentation, which
explains the request/response structure, built-in-methods, and custom
method configuration:
<details>
<summary>Click to view crate-level docs</summary>
```rust
//! An implementation of the Bevy Remote Protocol over HTTP and JSON, to allow
//! for remote control of a Bevy app.
//!
//! Adding the [`RemotePlugin`] to your [`App`] causes Bevy to accept
//! connections over HTTP (by default, on port 15702) while your app is running.
//! These *remote clients* can inspect and alter the state of the
//! entity-component system. Clients are expected to `POST` JSON requests to the
//! root URL; see the `client` example for a trivial example of use.
//!
//! The Bevy Remote Protocol is based on the JSON-RPC 2.0 protocol.
//!
//! ## Request objects
//!
//! A typical client request might look like this:
//!
//! ```json
//! {
//! "method": "bevy/get",
//! "id": 0,
//! "params": {
//! "entity": 4294967298,
//! "components": [
//! "bevy_transform::components::transform::Transform"
//! ]
//! }
//! }
//! ```
//!
//! The `id` and `method` fields are required. The `param` field may be omitted
//! for certain methods:
//!
//! * `id` is arbitrary JSON data. The server completely ignores its contents,
//! and the client may use it for any purpose. It will be copied via
//! serialization and deserialization (so object property order, etc. can't be
//! relied upon to be identical) and sent back to the client as part of the
//! response.
//!
//! * `method` is a string that specifies one of the possible [`BrpRequest`]
//! variants: `bevy/query`, `bevy/get`, `bevy/insert`, etc. It's case-sensitive.
//!
//! * `params` is parameter data specific to the request.
//!
//! For more information, see the documentation for [`BrpRequest`].
//! [`BrpRequest`] is serialized to JSON via `serde`, so [the `serde`
//! documentation] may be useful to clarify the correspondence between the Rust
//! structure and the JSON format.
//!
//! ## Response objects
//!
//! A response from the server to the client might look like this:
//!
//! ```json
//! {
//! "jsonrpc": "2.0",
//! "id": 0,
//! "result": {
//! "bevy_transform::components::transform::Transform": {
//! "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
//! "scale": { "x": 1.0, "y": 1.0, "z": 1.0 },
//! "translation": { "x": 0.0, "y": 0.5, "z": 0.0 }
//! }
//! }
//! }
//! ```
//!
//! The `id` field will always be present. The `result` field will be present if the
//! request was successful. Otherwise, an `error` field will replace it.
//!
//! * `id` is the arbitrary JSON data that was sent as part of the request. It
//! will be identical to the `id` data sent during the request, modulo
//! serialization and deserialization. If there's an error reading the `id` field,
//! it will be `null`.
//!
//! * `result` will be present if the request succeeded and will contain the response
//! specific to the request.
//!
//! * `error` will be present if the request failed and will contain an error object
//! with more information about the cause of failure.
//!
//! ## Error objects
//!
//! An error object might look like this:
//!
//! ```json
//! {
//! "code": -32602,
//! "message": "Missing \"entity\" field"
//! }
//! ```
//!
//! The `code` and `message` fields will always be present. There may also be a `data` field.
//!
//! * `code` is an integer representing the kind of an error that happened. Error codes documented
//! in the [`error_codes`] module.
//!
//! * `message` is a short, one-sentence human-readable description of the error.
//!
//! * `data` is an optional field of arbitrary type containing additional information about the error.
//!
//! ## Built-in methods
//!
//! The Bevy Remote Protocol includes a number of built-in methods for accessing and modifying data
//! in the ECS. Each of these methods uses the `bevy/` prefix, which is a namespace reserved for
//! BRP built-in methods.
//!
//! ### bevy/get
//!
//! Retrieve the values of one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components will be fetched.
//! - `components`: An array of fully-qualified type names of components to fetch.
//!
//! `result`: A map associating each type name to its value on the requested entity.
//!
//! ### bevy/query
//!
//! Perform a query over components in the ECS, returning all matching entities and their associated
//! component values.
//!
//! All of the arrays that comprise this request are optional, and when they are not provided, they
//! will be treated as if they were empty.
//!
//! `params`:
//! `params`:
//! - `data`:
//! - `components` (optional): An array of fully-qualified type names of components to fetch.
//! - `option` (optional): An array of fully-qualified type names of components to fetch optionally.
//! - `has` (optional): An array of fully-qualified type names of components whose presence will be
//! reported as boolean values.
//! - `filter` (optional):
//! - `with` (optional): An array of fully-qualified type names of components that must be present
//! on entities in order for them to be included in results.
//! - `without` (optional): An array of fully-qualified type names of components that must *not* be
//! present on entities in order for them to be included in results.
//!
//! `result`: An array, each of which is an object containing:
//! - `entity`: The ID of a query-matching entity.
//! - `components`: A map associating each type name from `components`/`option` to its value on the matching
//! entity if the component is present.
//! - `has`: A map associating each type name from `has` to a boolean value indicating whether or not the
//! entity has that component. If `has` was empty or omitted, this key will be omitted in the response.
//!
//! ### bevy/spawn
//!
//! Create a new entity with the provided components and return the resulting entity ID.
//!
//! `params`:
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`:
//! - `entity`: The ID of the newly spawned entity.
//!
//! ### bevy/destroy
//!
//! Despawn the entity with the given ID.
//!
//! `params`:
//! - `entity`: The ID of the entity to be despawned.
//!
//! `result`: null.
//!
//! ### bevy/remove
//!
//! Delete one or more components from an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity whose components should be removed.
//! - `components`: An array of fully-qualified type names of components to be removed.
//!
//! `result`: null.
//!
//! ### bevy/insert
//!
//! Insert one or more components into an entity.
//!
//! `params`:
//! - `entity`: The ID of the entity to insert components into.
//! - `components`: A map associating each component's fully-qualified type name with its value.
//!
//! `result`: null.
//!
//! ### bevy/reparent
//!
//! Assign a new parent to one or more entities.
//!
//! `params`:
//! - `entities`: An array of entity IDs of entities that will be made children of the `parent`.
//! - `parent` (optional): The entity ID of the parent to which the child entities will be assigned.
//! If excluded, the given entities will be removed from their parents.
//!
//! `result`: null.
//!
//! ### bevy/list
//!
//! List all registered components or all components present on an entity.
//!
//! When `params` is not provided, this lists all registered components. If `params` is provided,
//! this lists only those components present on the provided entity.
//!
//! `params` (optional):
//! - `entity`: The ID of the entity whose components will be listed.
//!
//! `result`: An array of fully-qualified type names of components.
//!
//! ## Custom methods
//!
//! In addition to the provided methods, the Bevy Remote Protocol can be extended to include custom
//! methods. This is primarily done during the initialization of [`RemotePlugin`], although the
//! methods may also be extended at runtime using the [`RemoteMethods`] resource.
//!
//! ### Example
//! ```ignore
//! fn main() {
//! App::new()
//! .add_plugins(DefaultPlugins)
//! .add_plugins(
//! // `default` adds all of the built-in methods, while `with_method` extends them
//! RemotePlugin::default()
//! .with_method("super_user/cool_method".to_owned(), path::to::my::cool::handler)
//! // ... more methods can be added by chaining `with_method`
//! )
//! .add_systems(
//! // ... standard application setup
//! )
//! .run();
//! }
//! ```
//!
//! The handler is expected to be a system-convertible function which takes optional JSON parameters
//! as input and returns a [`BrpResult`]. This means that it should have a type signature which looks
//! something like this:
//! ```
//! # use serde_json::Value;
//! # use bevy_ecs::prelude::{In, World};
//! # use bevy_remote::BrpResult;
//! fn handler(In(params): In<Option<Value>>, world: &mut World) -> BrpResult {
//! todo!()
//! }
//! ```
//!
//! Arbitrary system parameters can be used in conjunction with the optional `Value` input. The
//! handler system will always run with exclusive `World` access.
//!
//! [the `serde` documentation]: https://serde.rs/
```
</details>
### Message lifecycle
At a high level, the lifecycle of client-server interactions is
something like this:
1. The client sends one or more `BrpRequest`s. The deserialized version
of that is just the Rust representation of a JSON-RPC request, and it
looks like this:
```rust
pub struct BrpRequest {
/// The action to be performed. Parsing is deferred for the sake of error reporting.
pub method: Option<Value>,
/// Arbitrary data that will be returned verbatim to the client as part of
/// the response.
pub id: Option<Value>,
/// The parameters, specific to each method.
///
/// These are passed as the first argument to the method handler.
/// Sometimes params can be omitted.
pub params: Option<Value>,
}
```
2. These requests are accumulated in a mailbox resource (small lie but
close enough).
3. Each update, the mailbox is drained by a system
`process_remote_requests`, where each request is processed according to
its `method`, which has an associated handler. Each handler is a Bevy
system that runs with exclusive world access and returns a result; e.g.:
```rust
pub fn process_remote_get_request(In(params): In<Option<Value>>, world: &World) -> BrpResult { // ... }
```
4. The result (or an error) is reported back to the client.
## Testing
This can be tested by using the `server` and `client` examples. The
`client` example is not particularly exhaustive at the moment (it only
creates barebones `bevy/query` requests) but is still informative. Other
queries can be made using `curl` with the `server` example running.
For example, to make a `bevy/list` request and list all registered
components:
```bash
curl -X POST -d '{ "jsonrpc": "2.0", "id": 1, "method": "bevy/list" }' 127.0.0.1:15702 | jq .
```
---
## Future direction
There were a couple comments on BRP versioning while this was in draft.
I agree that BRP versioning is a good idea, but I think that it requires
some consensus on a couple fronts:
- First of all, what does the version actually mean? Is it a version for
the protocol itself or for the `bevy/*` methods implemented using it?
Both?
- Where does the version actually live? The most natural place is just
where we have `"jsonrpc"` right now (at least if it's versioning the
protocol itself), but this means we're not actually conforming to
JSON-RPC any more (so, for example, any client library used to construct
JSON-RPC requests would stop working). I'm not really against that, but
it's at least a real decision.
- What do we actually do when we encounter mismatched versions? Adding
handling for this would be actual scope creep instead of just a little
add-on in my opinion.
Another thing that would be nice is making the internal structure of the
implementation less JSON-specific. Right now, for example, component
values that will appear in server responses are quite eagerly converted
to JSON `Value`s, which prevents disentangling the handler logic from
the communication medium, but it can probably be done in principle and I
imagine it would enable more code reuse (e.g. for custom method
handlers) in addition to making the internals more readily usable for
other formats.
---------
Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
Co-authored-by: DragonGamesStudios <margos.michal@gmail.com>
Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-23 18:36:16 +00:00
|
|
|
#[derive(Component, Reflect, Serialize, Deserialize)]
|
|
|
|
#[reflect(Component, Serialize, Deserialize)]
|
|
|
|
struct Cube(f32);
|