-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of the Bevy Remote Protocol (Adopted) #14880
base: main
Are you sure you want to change the base?
Conversation
This commit implements most of the [Bevy Remote Protocol proposal] by @coreh in the form of an HTTP server that's activated when `RemotePlugin` is added to the `App`. Requests and responses are sent via JSON, using `serde` and the reflection capability as appropriate. The server is implemented using Tokio and `hyper`. All of the [proposal] has been implemented, with the exception of the `POLL` request, as it's not needed for the editor and I'm uncertain as to its design. In addition to the verbs in the proposal, an additional verb has been added, `LIST`. It allows the client to enumerate all components attached to an entity, or, if called without an entity, all components that are registered to the `App`. Two new examples have been added, `server` and `client`. The `server` example spawns a test scene and allows connections. The `client` example offers a simple command-line interface to BRP queries. I considered having `client` be more extensive, but it struck me as out of scope for this PR, which is already rather large. [Bevy Remote Protocol proposal]: https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md [proposal]: https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d#file-bevy-remote-protocol-md
to implement the BRP
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for picking this up!
One small thing, I think we want to add protocol versioning. In my opinion, we should start at 1.0.0 and follow semver for all verbs, networking types, and server handlers, just like it was a public rust API. The client should include the protocol version on every request.
This will allow compatibility ranges between the editor and game apps. I would prefer we not move this in lock-step with the bevy release; it would be best if BRP was somewhat more stable than the engine.
i agree protocol versioning is important, but i'm not sure that we should start at 1.0 considering Bevy is not yet 1.0, so i think 0.1.0 is probably a better initial version. |
Move to JSON-RPC, structure errors, code quality
… component to the server example
BRP is mostly intended for editor, can we keep this as "experimental" and start versioning when the editor integrates it? |
yeah thats fine |
hyper = { version = "1", features = ["full"] } | ||
hyper-util = { version = "0.1", features = ["full"] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably scope down to only necessary features
hyper = { version = "1", features = ["full"] } | ||
hyper-util = { version = "0.1", features = ["full"] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
/// The *full paths* of the component types that are to be requested | ||
/// from the entity. | ||
/// | ||
/// Note that these strings must consist of the *full* type paths: e.g. | ||
/// `bevy_transform::components::transform::Transform`, not just | ||
/// `Transform`. | ||
pub components: Vec<String>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doc is duplicated a lot and the exact method of getting the "full" path is not mentioned
Can we newtype string into ComponentPath
that has a new::<C: Component>
constructor?
EDIT: this is used A LOT, and the descriptions differ
.add_systems(Startup, start_server) | ||
.add_systems(Update, process_remote_requests); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not in scope for this PR, but mixing "editor" logic with game logic will cause trouble in debugging, like during system stepping you won't be able to use BRP, because it's just a "game logic" system.
Is there a proposed solution for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably to special-case out the systems that make the editor 'embassy' tools like this work. This discussion might be better suited to discord.
|
||
/// The application entry point. | ||
#[apply(main!)] | ||
async fn main(executor: &Executor<'_>) -> AnyhowResult<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a strong reason to have this example be this manual and not use something like ureq (or any other client). I feel like most people looking to make a request against the server aren't going to be looking for "how to use http1::handshake::
", etc, and the async isn't really necessary either.
I put the relevant diff up in a PR for consideration: mweatherley#2
benefits would be: smaller example to maintain, more directly applicable to likely end-user use case.
downside would be: adding a dev-dep on ureq (or another client)
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:
RemotePlugin
has been created, which has the effect of embedding a JSON-RPC endpoint into a Bevy application.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:
Click to view crate-level docs
Message lifecycle
At a high level, the lifecycle of client-server interactions is something like this:
BrpRequest
s. The deserialized version of that is just the Rust representation of a JSON-RPC request, and it looks like this:process_remote_requests
, where each request is processed according to itsmethod
, which has an associated handler. Each handler is a Bevy system that runs with exclusive world access and returns a result; e.g.:Testing
This can be tested by using the
server
andclient
examples. Theclient
example is not particularly exhaustive at the moment (it only creates barebonesbevy/query
requests) but is still informative. Other queries can be made usingcurl
with theserver
example running.For example, to make a
bevy/list
request and list all registered components: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:
bevy/*
methods implemented using it? Both?"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.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.