From 232094cb43789bfcbc51c976fa78fbb4f622379a Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 21 Mar 2026 17:29:14 +0800 Subject: [PATCH 01/23] Add BRP to render app --- Cargo.toml | 12 ++ crates/bevy_internal/Cargo.toml | 3 + crates/bevy_remote/Cargo.toml | 6 +- crates/bevy_remote/src/http.rs | 27 ++++ crates/bevy_remote/src/lib.rs | 254 +++++++++++++++++++++---------- crates/bevy_render/Cargo.toml | 5 + crates/bevy_render/src/lib.rs | 9 ++ examples/remote/render_client.rs | 125 +++++++++++++++ 8 files changed, 357 insertions(+), 84 deletions(-) create mode 100644 examples/remote/render_client.rs diff --git a/Cargo.toml b/Cargo.toml index 748314db2ec88..8a546d2615065 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4748,6 +4748,18 @@ description = "A simple command line client that can control Bevy apps via the B category = "Remote Protocol" wasm = false +[[example]] +name = "render_client" +path = "examples/remote/render_client.rs" +doc-scrape-examples = true +required-features = ["bevy_remote"] + +[package.metadata.example.render_client] +name = "render_client" +description = "A simple command line client that can control Bevy render subapp via the BRP" +category = "Remote Protocol" +wasm = false + [[example]] name = "server" path = "examples/remote/server.rs" diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 9840056b62ecb..3391516c35b68 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -362,6 +362,7 @@ reflect_functions = [ "bevy_reflect/functions", "bevy_app/reflect_functions", "bevy_ecs/reflect_functions", + "bevy_render?/reflect_functions", ] # Enable automatic reflect registration using inventory. @@ -369,6 +370,7 @@ reflect_auto_register = [ "bevy_reflect/auto_register_inventory", "bevy_app/reflect_auto_register", "bevy_ecs/reflect_auto_register", + "bevy_render?/reflect_auto_register", ] # Enable automatic reflect registration without inventory. See `reflect::load_type_registrations` for more info. @@ -376,6 +378,7 @@ reflect_auto_register_static = [ "bevy_reflect/auto_register_static", "bevy_app/reflect_auto_register", "bevy_ecs/reflect_auto_register", + "bevy_render?/reflect_auto_register", ] # Enables bevy_reflect to access documentation comments of rust code at runtime diff --git a/crates/bevy_remote/Cargo.toml b/crates/bevy_remote/Cargo.toml index ab86d97edea76..2a503ea3f4bbc 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -9,18 +9,21 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["http", "bevy_asset"] +default = ["http", "bevy_asset", "bevy_render"] http = ["dep:async-io", "dep:smol-hyper", "bevy_tasks/async-io"] bevy_asset = ["dep:bevy_asset"] +bevy_render = ["dep:bevy_render"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.19.0-dev" } +bevy_color = { path = "../bevy_color", version = "0.19.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev", features = [ "serialize", ] } bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev" } +bevy_render = { path = "../bevy_render", version = "0.19.0-dev", optional = true } bevy_tasks = { path = "../bevy_tasks", version = "0.19.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.19.0-dev", features = [ "debug", @@ -34,6 +37,7 @@ bevy_log = { path = "../bevy_log", version = "0.19.0-dev" } # other anyhow = "1" +disqualified = "1.0" hyper = { version = "1", features = ["server", "http1"] } serde = { version = "1", features = ["derive"] } serde_json = "1.0.140" diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 24edd3d11ec94..182c99220976d 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -8,6 +8,8 @@ #![cfg(not(target_family = "wasm"))] +#[cfg(feature = "bevy_render")] +use crate::setup_mailbox_channel; use crate::{ error_codes, BrpBatch, BrpError, BrpMessage, BrpRequest, BrpResponse, BrpResult, BrpSender, }; @@ -16,7 +18,11 @@ use async_channel::{Receiver, Sender}; use async_io::Async; use bevy_app::{App, Plugin, Startup}; use bevy_ecs::resource::Resource; +#[cfg(feature = "bevy_render")] +use bevy_ecs::schedule::IntoScheduleConfigs as _; use bevy_ecs::system::Res; +#[cfg(feature = "bevy_render")] +use bevy_render::{RenderApp, RenderStartup}; use bevy_tasks::{futures_lite::StreamExt, IoTaskPool}; use core::{ convert::Infallible, @@ -43,6 +49,9 @@ use std::{ /// This value was chosen randomly. pub const DEFAULT_PORT: u16 = 15702; +/// The default port that Bevy will listen on for render subapp. +pub const DEFAULT_RENDER_PORT: u16 = 15703; + /// The default host address that Bevy will use for its server. pub const DEFAULT_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); @@ -101,6 +110,8 @@ pub struct RemoteHttpPlugin { address: IpAddr, /// The port that Bevy will listen on. port: u16, + /// The port that Bevy will listen on for render subapp. + render_port: u16, /// The headers that Bevy will include in its HTTP responses headers: Headers, } @@ -110,6 +121,7 @@ impl Default for RemoteHttpPlugin { Self { address: DEFAULT_ADDR, port: DEFAULT_PORT, + render_port: DEFAULT_RENDER_PORT, headers: Headers::new(), } } @@ -121,6 +133,21 @@ impl Plugin for RemoteHttpPlugin { .insert_resource(HostPort(self.port)) .insert_resource(HostHeaders(self.headers.clone())) .add_systems(Startup, start_http_server); + + #[cfg(feature = "bevy_render")] + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + #[cfg(feature = "bevy_render")] + render_app + .insert_resource(HostAddress(self.address)) + .insert_resource(HostPort(self.render_port)) + .insert_resource(HostHeaders(self.headers.clone())) + .add_systems( + RenderStartup, + start_http_server.after(setup_mailbox_channel), + ); } } diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 7a338461c6c8c..aeac741372eb5 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -545,6 +545,8 @@ use bevy_ecs::{ world::World, }; use bevy_platform::collections::HashMap; +#[cfg(feature = "bevy_render")] +use bevy_render::{Render, RenderApp, RenderStartup}; use bevy_utils::prelude::default; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_json::Value; @@ -566,6 +568,7 @@ const CHANNEL_SIZE: usize = 16; pub struct RemotePlugin { /// The verbs that the server will recognize and respond to. methods: RwLock>, + remote_methods: RwLock>, } impl RemotePlugin { @@ -574,6 +577,7 @@ impl RemotePlugin { fn empty() -> Self { Self { methods: RwLock::new(vec![]), + remote_methods: RwLock::new(vec![]), } } @@ -583,8 +587,15 @@ impl RemotePlugin { mut self, name: impl Into, handler: impl IntoSystem>, BrpResult, M>, + to_main: bool, ) -> Self { - self.methods.get_mut().unwrap().push(( + (if to_main { + self.methods.get_mut() + } else { + self.remote_methods.get_mut() + }) + .unwrap() + .push(( name.into(), RemoteMethodHandler::Instant(Box::new(IntoSystem::into_system(handler))), )); @@ -597,98 +608,131 @@ impl RemotePlugin { mut self, name: impl Into, handler: impl IntoSystem>, BrpResult>, M>, + to_main: bool, ) -> Self { - self.methods.get_mut().unwrap().push(( + (if to_main { + self.methods.get_mut() + } else { + self.remote_methods.get_mut() + }) + .unwrap() + .push(( name.into(), RemoteMethodHandler::Watching(Box::new(IntoSystem::into_system(handler))), )); self } + + /// Create the default list of BRP methods + pub fn add_default_methods(self, to_main: bool) -> Self { + self.with_method( + builtin_methods::BRP_GET_COMPONENTS_METHOD, + builtin_methods::process_remote_get_components_request, + to_main, + ) + .with_method( + builtin_methods::BRP_QUERY_METHOD, + builtin_methods::process_remote_query_request, + to_main, + ) + .with_method( + builtin_methods::BRP_SPAWN_ENTITY_METHOD, + builtin_methods::process_remote_spawn_entity_request, + to_main, + ) + .with_method( + builtin_methods::BRP_INSERT_COMPONENTS_METHOD, + builtin_methods::process_remote_insert_components_request, + to_main, + ) + .with_method( + builtin_methods::BRP_REMOVE_COMPONENTS_METHOD, + builtin_methods::process_remote_remove_components_request, + to_main, + ) + .with_method( + builtin_methods::BRP_DESPAWN_COMPONENTS_METHOD, + builtin_methods::process_remote_despawn_entity_request, + to_main, + ) + .with_method( + builtin_methods::BRP_REPARENT_ENTITIES_METHOD, + builtin_methods::process_remote_reparent_entities_request, + to_main, + ) + .with_method( + builtin_methods::BRP_LIST_COMPONENTS_METHOD, + builtin_methods::process_remote_list_components_request, + to_main, + ) + .with_method( + builtin_methods::BRP_MUTATE_COMPONENTS_METHOD, + builtin_methods::process_remote_mutate_components_request, + to_main, + ) + .with_method( + builtin_methods::RPC_DISCOVER_METHOD, + builtin_methods::process_remote_list_methods_request, + to_main, + ) + .with_watching_method( + builtin_methods::BRP_GET_COMPONENTS_AND_WATCH_METHOD, + builtin_methods::process_remote_get_components_watching_request, + to_main, + ) + .with_watching_method( + builtin_methods::BRP_LIST_COMPONENTS_AND_WATCH_METHOD, + builtin_methods::process_remote_list_components_watching_request, + to_main, + ) + .with_method( + builtin_methods::BRP_GET_RESOURCE_METHOD, + builtin_methods::process_remote_get_resources_request, + to_main, + ) + .with_method( + builtin_methods::BRP_INSERT_RESOURCE_METHOD, + builtin_methods::process_remote_insert_resources_request, + to_main, + ) + .with_method( + builtin_methods::BRP_REMOVE_RESOURCE_METHOD, + builtin_methods::process_remote_remove_resources_request, + to_main, + ) + .with_method( + builtin_methods::BRP_MUTATE_RESOURCE_METHOD, + builtin_methods::process_remote_mutate_resources_request, + to_main, + ) + .with_method( + builtin_methods::BRP_LIST_RESOURCES_METHOD, + builtin_methods::process_remote_list_resources_request, + to_main, + ) + .with_method( + builtin_methods::BRP_TRIGGER_EVENT_METHOD, + builtin_methods::process_remote_trigger_event_request, + to_main, + ) + .with_method( + builtin_methods::BRP_WRITE_MESSAGE_METHOD, + builtin_methods::process_remote_write_message_request, + to_main, + ) + .with_method( + builtin_methods::BRP_REGISTRY_SCHEMA_METHOD, + builtin_methods::export_registry_types, + to_main, + ) + } } impl Default for RemotePlugin { fn default() -> Self { Self::empty() - .with_method( - builtin_methods::BRP_GET_COMPONENTS_METHOD, - builtin_methods::process_remote_get_components_request, - ) - .with_method( - builtin_methods::BRP_QUERY_METHOD, - builtin_methods::process_remote_query_request, - ) - .with_method( - builtin_methods::BRP_SPAWN_ENTITY_METHOD, - builtin_methods::process_remote_spawn_entity_request, - ) - .with_method( - builtin_methods::BRP_INSERT_COMPONENTS_METHOD, - builtin_methods::process_remote_insert_components_request, - ) - .with_method( - builtin_methods::BRP_REMOVE_COMPONENTS_METHOD, - builtin_methods::process_remote_remove_components_request, - ) - .with_method( - builtin_methods::BRP_DESPAWN_COMPONENTS_METHOD, - builtin_methods::process_remote_despawn_entity_request, - ) - .with_method( - builtin_methods::BRP_REPARENT_ENTITIES_METHOD, - builtin_methods::process_remote_reparent_entities_request, - ) - .with_method( - builtin_methods::BRP_LIST_COMPONENTS_METHOD, - builtin_methods::process_remote_list_components_request, - ) - .with_method( - builtin_methods::BRP_MUTATE_COMPONENTS_METHOD, - builtin_methods::process_remote_mutate_components_request, - ) - .with_method( - builtin_methods::RPC_DISCOVER_METHOD, - builtin_methods::process_remote_list_methods_request, - ) - .with_watching_method( - builtin_methods::BRP_GET_COMPONENTS_AND_WATCH_METHOD, - builtin_methods::process_remote_get_components_watching_request, - ) - .with_watching_method( - builtin_methods::BRP_LIST_COMPONENTS_AND_WATCH_METHOD, - builtin_methods::process_remote_list_components_watching_request, - ) - .with_method( - builtin_methods::BRP_GET_RESOURCE_METHOD, - builtin_methods::process_remote_get_resources_request, - ) - .with_method( - builtin_methods::BRP_INSERT_RESOURCE_METHOD, - builtin_methods::process_remote_insert_resources_request, - ) - .with_method( - builtin_methods::BRP_REMOVE_RESOURCE_METHOD, - builtin_methods::process_remote_remove_resources_request, - ) - .with_method( - builtin_methods::BRP_MUTATE_RESOURCE_METHOD, - builtin_methods::process_remote_mutate_resources_request, - ) - .with_method( - builtin_methods::BRP_LIST_RESOURCES_METHOD, - builtin_methods::process_remote_list_resources_request, - ) - .with_method( - builtin_methods::BRP_TRIGGER_EVENT_METHOD, - builtin_methods::process_remote_trigger_event_request, - ) - .with_method( - builtin_methods::BRP_WRITE_MESSAGE_METHOD, - builtin_methods::process_remote_write_message_request, - ) - .with_method( - builtin_methods::BRP_REGISTRY_SCHEMA_METHOD, - builtin_methods::export_registry_types, - ) + .add_default_methods(true) + .add_default_methods(false) } } @@ -699,7 +743,7 @@ impl Plugin for RemotePlugin { let plugin_methods = &mut *self.methods.write().unwrap(); for (name, handler) in plugin_methods.drain(..) { remote_methods.insert( - name, + name.clone(), match handler { RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant( app.main_mut().world_mut().register_boxed_system(system), @@ -733,6 +777,50 @@ impl Plugin for RemotePlugin { remove_closed_watching_requests.in_set(RemoteSystems::Cleanup), ), ); + + #[cfg(feature = "bevy_render")] + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + #[cfg(feature = "bevy_render")] + let mut render_remote_methods = RemoteMethods::new(); + + let render_plugin_methods = &mut *self.remote_methods.write().unwrap(); + for (name, handler) in render_plugin_methods.drain(..) { + #[cfg(feature = "bevy_render")] + render_remote_methods.insert( + name, + match handler { + RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant( + render_app.world_mut().register_boxed_system(system), + ), + RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching( + render_app.world_mut().register_boxed_system(system), + ), + }, + ); + } + + #[cfg(feature = "bevy_render")] + render_app + .insert_resource(render_remote_methods) + .init_resource::() + .init_resource::() + .add_systems(RenderStartup, setup_mailbox_channel) + .configure_sets( + Render, + (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), + ) + .add_systems( + Render, + ( + (process_remote_requests, process_ongoing_watching_requests) + .chain() + .in_set(RemoteSystems::ProcessRequests), + remove_closed_watching_requests.in_set(RemoteSystems::Cleanup), + ), + ); } } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index d2e3a1e2954ed..c8d376b243550 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -52,6 +52,11 @@ debug = ["type_label_buffers", "bevy_utils/debug"] ## Adds serialization support through `serde`. serialize = ["bevy_mesh/serialize"] +reflect_auto_register = ["bevy_app/reflect_auto_register"] +reflect_functions = ["bevy_app/reflect_functions"] + +default = ["reflect_auto_register", "reflect_functions"] + [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.19.0-dev" } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 94ee1cf8be67b..da1b208332506 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -353,6 +353,15 @@ impl Plugin for RenderPlugin { ), ); + #[cfg(not(feature = "reflect_auto_register"))] + render_app.init_resource::(); + + #[cfg(feature = "reflect_auto_register")] + render_app.insert_resource(AppTypeRegistry::new_with_derived_types()); + + #[cfg(feature = "reflect_functions")] + render_app.init_resource::(); + render_app.add_schedule(RenderGraph::base_schedule()); render_app.init_schedule(RenderStartup); diff --git a/examples/remote/render_client.rs b/examples/remote/render_client.rs new file mode 100644 index 0000000000000..6fe0a4de86492 --- /dev/null +++ b/examples/remote/render_client.rs @@ -0,0 +1,125 @@ +//! A simple command line client that allows issuing queries to a remote Bevy +//! app via the BRP. +//! This example requires the `bevy_remote` feature to be enabled. +//! You can run it with the following command: +//! ```text +//! cargo run --example client --features="bevy_remote" +//! ``` +//! This example assumes that the `server` example is running on the same machine. + +use std::any::type_name; + +use anyhow::Result as AnyhowResult; +use bevy::{ + ecs::hierarchy::ChildOf, + prelude::info, + remote::{ + builtin_methods::{ + BrpQuery, BrpQueryFilter, BrpQueryParams, ComponentSelector, BRP_QUERY_METHOD, + }, + http::{DEFAULT_ADDR, DEFAULT_RENDER_PORT}, + BrpRequest, + }, + transform::components::Transform, +}; + +/// The application entry point. +fn main() -> AnyhowResult<()> { + // Create the URL. We're going to need it to issue the HTTP request. + let host_part = format!("{DEFAULT_ADDR}:{DEFAULT_RENDER_PORT}"); + let url = format!("http://{host_part}/"); + // Creates a request to get all Transform components from the remote Bevy app. + // This request will return all entities that have a Transform component. + run_transform_only_query(&url)?; + + // Create a query that only returns root entities - ie, entities that do not + // have a parent. + run_query_root_entities(&url)?; + + // Create a query all request to send to the remote Bevy app. + // This request will return all entities in the app, their components, and their + // component values. + run_query_all_components_and_entities(&url)?; + + Ok(()) +} + +fn run_query_all_components_and_entities(url: &str) -> Result<(), anyhow::Error> { + let query_all_req = BrpRequest { + method: String::from(BRP_QUERY_METHOD), + id: Some(serde_json::to_value(1)?), + params: Some( + serde_json::to_value(BrpQueryParams { + data: BrpQuery { + components: Vec::default(), + option: ComponentSelector::All, + has: Vec::default(), + }, + strict: false, + filter: BrpQueryFilter::default(), + }) + .expect("Unable to convert query parameters to a valid JSON value"), + ), + }; + info!("query_all req: {query_all_req:#?}"); + let query_all_res = ureq::post(url) + .send_json(query_all_req)? + .body_mut() + .read_json::()?; + info!("{query_all_res:#}"); + Ok(()) +} + +fn run_transform_only_query(url: &str) -> Result<(), anyhow::Error> { + let get_transform_request = BrpRequest { + method: String::from(BRP_QUERY_METHOD), + id: Some(serde_json::to_value(1)?), + params: Some( + serde_json::to_value(BrpQueryParams { + data: BrpQuery { + components: vec![type_name::().to_string()], + ..Default::default() + }, + strict: false, + filter: BrpQueryFilter::default(), + }) + .expect("Unable to convert query parameters to a valid JSON value"), + ), + }; + info!("transform request: {get_transform_request:#?}"); + let res = ureq::post(url) + .send_json(get_transform_request)? + .body_mut() + .read_json::()?; + info!("{res:#}"); + Ok(()) +} + +fn run_query_root_entities(url: &str) -> Result<(), anyhow::Error> { + let get_transform_request = BrpRequest { + method: String::from(BRP_QUERY_METHOD), + id: Some(serde_json::to_value(1)?), + params: Some( + serde_json::to_value(BrpQueryParams { + data: BrpQuery { + components: Vec::default(), + option: ComponentSelector::All, + has: Vec::default(), + }, + strict: false, + filter: BrpQueryFilter { + without: vec![type_name::().to_string()], + with: Vec::default(), + }, + }) + .expect("Unable to convert query parameters to a valid JSON value"), + ), + }; + info!("transform request: {get_transform_request:#?}"); + let res = ureq::post(url) + .send_json(get_transform_request)? + .body_mut() + .read_json::()?; + info!("{res:#}"); + Ok(()) +} From 2c6903f1f3a9fb3e5cd94e8ca1289e5a8a7ea737 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 21 Mar 2026 23:23:19 +0800 Subject: [PATCH 02/23] Cleanup --- crates/bevy_remote/Cargo.toml | 1 - crates/bevy_remote/src/lib.rs | 13 +++++++------ examples/README.md | 1 + examples/remote/render_client.rs | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/bevy_remote/Cargo.toml b/crates/bevy_remote/Cargo.toml index 2a503ea3f4bbc..c569df59078c0 100644 --- a/crates/bevy_remote/Cargo.toml +++ b/crates/bevy_remote/Cargo.toml @@ -37,7 +37,6 @@ bevy_log = { path = "../bevy_log", version = "0.19.0-dev" } # other anyhow = "1" -disqualified = "1.0" hyper = { version = "1", features = ["server", "http1"] } serde = { version = "1", features = ["derive"] } serde_json = "1.0.140" diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index aeac741372eb5..f33cdbb47139a 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -566,9 +566,10 @@ const CHANNEL_SIZE: usize = 16; /// /// [crate-level documentation]: crate pub struct RemotePlugin { - /// The verbs that the server will recognize and respond to. + /// The verbs that the server will recognize and respond to for main app. methods: RwLock>, - remote_methods: RwLock>, + /// The verbs that the server will recognize and respond to for render subapp. + render_methods: RwLock>, } impl RemotePlugin { @@ -577,7 +578,7 @@ impl RemotePlugin { fn empty() -> Self { Self { methods: RwLock::new(vec![]), - remote_methods: RwLock::new(vec![]), + render_methods: RwLock::new(vec![]), } } @@ -592,7 +593,7 @@ impl RemotePlugin { (if to_main { self.methods.get_mut() } else { - self.remote_methods.get_mut() + self.render_methods.get_mut() }) .unwrap() .push(( @@ -613,7 +614,7 @@ impl RemotePlugin { (if to_main { self.methods.get_mut() } else { - self.remote_methods.get_mut() + self.render_methods.get_mut() }) .unwrap() .push(( @@ -786,7 +787,7 @@ impl Plugin for RemotePlugin { #[cfg(feature = "bevy_render")] let mut render_remote_methods = RemoteMethods::new(); - let render_plugin_methods = &mut *self.remote_methods.write().unwrap(); + let render_plugin_methods = &mut *self.render_methods.write().unwrap(); for (name, handler) in render_plugin_methods.drain(..) { #[cfg(feature = "bevy_render")] render_remote_methods.insert( diff --git a/examples/README.md b/examples/README.md index 99c3de12e3019..df69c421259d4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -460,6 +460,7 @@ Example | Description Example | Description --- | --- [client](../examples/remote/client.rs) | A simple command line client that can control Bevy apps via the BRP +[render_client](../examples/remote/render_client.rs) | A simple command line client that can control Bevy render subapp via the BRP [server](../examples/remote/server.rs) | A Bevy app that you can connect to with the BRP and edit ### Scene diff --git a/examples/remote/render_client.rs b/examples/remote/render_client.rs index 6fe0a4de86492..a07f83e1a966d 100644 --- a/examples/remote/render_client.rs +++ b/examples/remote/render_client.rs @@ -1,9 +1,9 @@ //! A simple command line client that allows issuing queries to a remote Bevy -//! app via the BRP. +//! render subapp via the BRP. //! This example requires the `bevy_remote` feature to be enabled. //! You can run it with the following command: //! ```text -//! cargo run --example client --features="bevy_remote" +//! cargo run --example render_client --features="bevy_remote" //! ``` //! This example assumes that the `server` example is running on the same machine. From 28efb21e8a5c63e43a4fddcb62ac38ac6bbbf26e Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sun, 22 Mar 2026 23:24:21 +0800 Subject: [PATCH 03/23] Apply suggestions from code review Co-authored-by: Kevin Chen --- crates/bevy_remote/src/http.rs | 4 +++- crates/bevy_remote/src/lib.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 182c99220976d..005655c3cc3da 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -49,7 +49,9 @@ use std::{ /// This value was chosen randomly. pub const DEFAULT_PORT: u16 = 15702; -/// The default port that Bevy will listen on for render subapp. +/// The default port that Bevy will listen on for the render subapp. +/// +/// The render subapp is available for requests if the `bevy_render` feature is enabled. pub const DEFAULT_RENDER_PORT: u16 = 15703; /// The default host address that Bevy will use for its server. diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index f33cdbb47139a..dcdd23cc1870e 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -566,9 +566,9 @@ const CHANNEL_SIZE: usize = 16; /// /// [crate-level documentation]: crate pub struct RemotePlugin { - /// The verbs that the server will recognize and respond to for main app. + /// The verbs that the server will recognize and respond to for the main app. methods: RwLock>, - /// The verbs that the server will recognize and respond to for render subapp. + /// The verbs that the server will recognize and respond to for the render subapp. render_methods: RwLock>, } From 346add38bab89617123108345acfdf6070ea4843 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sun, 22 Mar 2026 23:29:38 +0800 Subject: [PATCH 04/23] Review --- crates/bevy_remote/src/http.rs | 30 +++++++----- crates/bevy_remote/src/lib.rs | 89 ++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 005655c3cc3da..9c47b0b32eaa3 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -3,6 +3,8 @@ //! Adding the [`RemoteHttpPlugin`] to your [`App`] causes Bevy to accept //! connections over HTTP (by default, on port 15702) while your app is running. //! +//! When `bevy_render` is enabled, a second port is available to query the render subapp. +//! //! Clients are expected to `POST` JSON requests to the root URL; see the `client` //! example for a trivial example of use. @@ -106,6 +108,7 @@ impl Default for Headers { /// The defaults are: /// - [`DEFAULT_ADDR`] : 127.0.0.1. /// - [`DEFAULT_PORT`] : 15702. +/// - [`DEFAULT_RENDER_PORT`] : 15703. (when `bevy_render` is enabled) /// pub struct RemoteHttpPlugin { /// The address that Bevy will bind to. @@ -137,19 +140,20 @@ impl Plugin for RemoteHttpPlugin { .add_systems(Startup, start_http_server); #[cfg(feature = "bevy_render")] - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; - - #[cfg(feature = "bevy_render")] - render_app - .insert_resource(HostAddress(self.address)) - .insert_resource(HostPort(self.render_port)) - .insert_resource(HostHeaders(self.headers.clone())) - .add_systems( - RenderStartup, - start_http_server.after(setup_mailbox_channel), - ); + { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .insert_resource(HostAddress(self.address)) + .insert_resource(HostPort(self.render_port)) + .insert_resource(HostHeaders(self.headers.clone())) + .add_systems( + RenderStartup, + start_http_server.after(setup_mailbox_channel), + ); + } } } diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index dcdd23cc1870e..f9e28ec6286fe 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -625,7 +625,7 @@ impl RemotePlugin { } /// Create the default list of BRP methods - pub fn add_default_methods(self, to_main: bool) -> Self { + fn add_default_methods(self, to_main: bool) -> Self { self.with_method( builtin_methods::BRP_GET_COMPONENTS_METHOD, builtin_methods::process_remote_get_components_request, @@ -731,9 +731,15 @@ impl RemotePlugin { impl Default for RemotePlugin { fn default() -> Self { - Self::empty() - .add_default_methods(true) - .add_default_methods(false) + let mut t = Self::empty(); + t = t.add_default_methods(true); + + #[cfg(feature = "bevy_render")] + { + t = t.add_default_methods(false); + } + + t } } @@ -780,48 +786,47 @@ impl Plugin for RemotePlugin { ); #[cfg(feature = "bevy_render")] - let Some(render_app) = app.get_sub_app_mut(RenderApp) else { - return; - }; + { + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; - #[cfg(feature = "bevy_render")] - let mut render_remote_methods = RemoteMethods::new(); + let mut render_remote_methods = RemoteMethods::new(); + + let render_plugin_methods = &mut *self.render_methods.write().unwrap(); + for (name, handler) in render_plugin_methods.drain(..) { + render_remote_methods.insert( + name, + match handler { + RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant( + render_app.world_mut().register_boxed_system(system), + ), + RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching( + render_app.world_mut().register_boxed_system(system), + ), + }, + ); + } - let render_plugin_methods = &mut *self.render_methods.write().unwrap(); - for (name, handler) in render_plugin_methods.drain(..) { - #[cfg(feature = "bevy_render")] - render_remote_methods.insert( - name, - match handler { - RemoteMethodHandler::Instant(system) => RemoteMethodSystemId::Instant( - render_app.world_mut().register_boxed_system(system), + render_app + .insert_resource(render_remote_methods) + .init_resource::() + .init_resource::() + .add_systems(RenderStartup, setup_mailbox_channel) + .configure_sets( + Render, + (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), + ) + .add_systems( + Render, + ( + (process_remote_requests, process_ongoing_watching_requests) + .chain() + .in_set(RemoteSystems::ProcessRequests), + remove_closed_watching_requests.in_set(RemoteSystems::Cleanup), ), - RemoteMethodHandler::Watching(system) => RemoteMethodSystemId::Watching( - render_app.world_mut().register_boxed_system(system), - ), - }, - ); + ); } - - #[cfg(feature = "bevy_render")] - render_app - .insert_resource(render_remote_methods) - .init_resource::() - .init_resource::() - .add_systems(RenderStartup, setup_mailbox_channel) - .configure_sets( - Render, - (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), - ) - .add_systems( - Render, - ( - (process_remote_requests, process_ongoing_watching_requests) - .chain() - .in_set(RemoteSystems::ProcessRequests), - remove_closed_watching_requests.in_set(RemoteSystems::Cleanup), - ), - ); } } From 5fc8e0ba1ac6d2f521cd49d0becc6e595deb36ed Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Tue, 24 Mar 2026 21:56:05 +0800 Subject: [PATCH 05/23] Merge render_client example back into client example --- Cargo.toml | 12 --- examples/README.md | 1 - examples/remote/client.rs | 51 +++++-------- examples/remote/render_client.rs | 125 ------------------------------- 4 files changed, 17 insertions(+), 172 deletions(-) delete mode 100644 examples/remote/render_client.rs diff --git a/Cargo.toml b/Cargo.toml index 8a546d2615065..748314db2ec88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4748,18 +4748,6 @@ description = "A simple command line client that can control Bevy apps via the B category = "Remote Protocol" wasm = false -[[example]] -name = "render_client" -path = "examples/remote/render_client.rs" -doc-scrape-examples = true -required-features = ["bevy_remote"] - -[package.metadata.example.render_client] -name = "render_client" -description = "A simple command line client that can control Bevy render subapp via the BRP" -category = "Remote Protocol" -wasm = false - [[example]] name = "server" path = "examples/remote/server.rs" diff --git a/examples/README.md b/examples/README.md index df69c421259d4..99c3de12e3019 100644 --- a/examples/README.md +++ b/examples/README.md @@ -460,7 +460,6 @@ Example | Description Example | Description --- | --- [client](../examples/remote/client.rs) | A simple command line client that can control Bevy apps via the BRP -[render_client](../examples/remote/render_client.rs) | A simple command line client that can control Bevy render subapp via the BRP [server](../examples/remote/server.rs) | A Bevy app that you can connect to with the BRP and edit ### Scene diff --git a/examples/remote/client.rs b/examples/remote/client.rs index 2e9bad0b1d018..0722804ec310b 100644 --- a/examples/remote/client.rs +++ b/examples/remote/client.rs @@ -1,5 +1,5 @@ //! A simple command line client that allows issuing queries to a remote Bevy -//! app via the BRP. +//! render subapp via the BRP. //! This example requires the `bevy_remote` feature to be enabled. //! You can run it with the following command: //! ```text @@ -12,12 +12,12 @@ use std::any::type_name; use anyhow::Result as AnyhowResult; use bevy::{ ecs::hierarchy::ChildOf, + prelude::info, remote::{ builtin_methods::{ - BrpQuery, BrpQueryFilter, BrpQueryParams, BrpWriteMessageParams, ComponentSelector, - BRP_QUERY_METHOD, BRP_WRITE_MESSAGE_METHOD, + BrpQuery, BrpQueryFilter, BrpQueryParams, ComponentSelector, BRP_QUERY_METHOD, }, - http::{DEFAULT_ADDR, DEFAULT_PORT}, + http::{DEFAULT_ADDR, DEFAULT_PORT, DEFAULT_RENDER_PORT}, BrpRequest, }, transform::components::Transform, @@ -41,9 +41,13 @@ fn main() -> AnyhowResult<()> { // component values. run_query_all_components_and_entities(&url)?; - // Send an `AppExit::Success` message to the app to the remote Bevy app. - // This will make it quit. - send_app_exit(&url)?; + // Run again against the render port + let host_part2 = format!("{DEFAULT_ADDR}:{DEFAULT_RENDER_PORT}"); + let url2 = format!("http://{host_part2}/"); + + run_transform_only_query(&url)?; + run_query_root_entities(&url)?; + run_query_all_components_and_entities(&url)?; Ok(()) } @@ -65,12 +69,12 @@ fn run_query_all_components_and_entities(url: &str) -> Result<(), anyhow::Error> .expect("Unable to convert query parameters to a valid JSON value"), ), }; - println!("query_all req: {query_all_req:#?}"); + info!("query_all req: {query_all_req:#?}"); let query_all_res = ureq::post(url) .send_json(query_all_req)? .body_mut() .read_json::()?; - println!("{query_all_res:#}"); + info!("{query_all_res:#}"); Ok(()) } @@ -90,12 +94,12 @@ fn run_transform_only_query(url: &str) -> Result<(), anyhow::Error> { .expect("Unable to convert query parameters to a valid JSON value"), ), }; - println!("transform request: {get_transform_request:#?}"); + info!("transform request: {get_transform_request:#?}"); let res = ureq::post(url) .send_json(get_transform_request)? .body_mut() .read_json::()?; - println!("{res:#}"); + info!("{res:#}"); Ok(()) } @@ -119,32 +123,11 @@ fn run_query_root_entities(url: &str) -> Result<(), anyhow::Error> { .expect("Unable to convert query parameters to a valid JSON value"), ), }; - println!("transform request: {get_transform_request:#?}"); + info!("transform request: {get_transform_request:#?}"); let res = ureq::post(url) .send_json(get_transform_request)? .body_mut() .read_json::()?; - println!("{res:#}"); - Ok(()) -} - -fn send_app_exit(url: &str) -> Result<(), anyhow::Error> { - let write_message_request = BrpRequest { - method: String::from(BRP_WRITE_MESSAGE_METHOD), - id: Some(serde_json::to_value(1)?), - params: Some( - serde_json::to_value(BrpWriteMessageParams { - message: "bevy_app::app::AppExit".to_string(), - value: Some("Success".into()), - }) - .expect("Unable to convert write message parameters to a valid JSON value"), - ), - }; - println!("write message request: {write_message_request:#?}"); - let res = ureq::post(url) - .send_json(write_message_request)? - .body_mut() - .read_json::()?; - println!("{res:#}"); + info!("{res:#}"); Ok(()) } diff --git a/examples/remote/render_client.rs b/examples/remote/render_client.rs deleted file mode 100644 index a07f83e1a966d..0000000000000 --- a/examples/remote/render_client.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! A simple command line client that allows issuing queries to a remote Bevy -//! render subapp via the BRP. -//! This example requires the `bevy_remote` feature to be enabled. -//! You can run it with the following command: -//! ```text -//! cargo run --example render_client --features="bevy_remote" -//! ``` -//! This example assumes that the `server` example is running on the same machine. - -use std::any::type_name; - -use anyhow::Result as AnyhowResult; -use bevy::{ - ecs::hierarchy::ChildOf, - prelude::info, - remote::{ - builtin_methods::{ - BrpQuery, BrpQueryFilter, BrpQueryParams, ComponentSelector, BRP_QUERY_METHOD, - }, - http::{DEFAULT_ADDR, DEFAULT_RENDER_PORT}, - BrpRequest, - }, - transform::components::Transform, -}; - -/// The application entry point. -fn main() -> AnyhowResult<()> { - // Create the URL. We're going to need it to issue the HTTP request. - let host_part = format!("{DEFAULT_ADDR}:{DEFAULT_RENDER_PORT}"); - let url = format!("http://{host_part}/"); - // Creates a request to get all Transform components from the remote Bevy app. - // This request will return all entities that have a Transform component. - run_transform_only_query(&url)?; - - // Create a query that only returns root entities - ie, entities that do not - // have a parent. - run_query_root_entities(&url)?; - - // Create a query all request to send to the remote Bevy app. - // This request will return all entities in the app, their components, and their - // component values. - run_query_all_components_and_entities(&url)?; - - Ok(()) -} - -fn run_query_all_components_and_entities(url: &str) -> Result<(), anyhow::Error> { - let query_all_req = BrpRequest { - method: String::from(BRP_QUERY_METHOD), - id: Some(serde_json::to_value(1)?), - params: Some( - serde_json::to_value(BrpQueryParams { - data: BrpQuery { - components: Vec::default(), - option: ComponentSelector::All, - has: Vec::default(), - }, - strict: false, - filter: BrpQueryFilter::default(), - }) - .expect("Unable to convert query parameters to a valid JSON value"), - ), - }; - info!("query_all req: {query_all_req:#?}"); - let query_all_res = ureq::post(url) - .send_json(query_all_req)? - .body_mut() - .read_json::()?; - info!("{query_all_res:#}"); - Ok(()) -} - -fn run_transform_only_query(url: &str) -> Result<(), anyhow::Error> { - let get_transform_request = BrpRequest { - method: String::from(BRP_QUERY_METHOD), - id: Some(serde_json::to_value(1)?), - params: Some( - serde_json::to_value(BrpQueryParams { - data: BrpQuery { - components: vec![type_name::().to_string()], - ..Default::default() - }, - strict: false, - filter: BrpQueryFilter::default(), - }) - .expect("Unable to convert query parameters to a valid JSON value"), - ), - }; - info!("transform request: {get_transform_request:#?}"); - let res = ureq::post(url) - .send_json(get_transform_request)? - .body_mut() - .read_json::()?; - info!("{res:#}"); - Ok(()) -} - -fn run_query_root_entities(url: &str) -> Result<(), anyhow::Error> { - let get_transform_request = BrpRequest { - method: String::from(BRP_QUERY_METHOD), - id: Some(serde_json::to_value(1)?), - params: Some( - serde_json::to_value(BrpQueryParams { - data: BrpQuery { - components: Vec::default(), - option: ComponentSelector::All, - has: Vec::default(), - }, - strict: false, - filter: BrpQueryFilter { - without: vec![type_name::().to_string()], - with: Vec::default(), - }, - }) - .expect("Unable to convert query parameters to a valid JSON value"), - ), - }; - info!("transform request: {get_transform_request:#?}"); - let res = ureq::post(url) - .send_json(get_transform_request)? - .body_mut() - .read_json::()?; - info!("{res:#}"); - Ok(()) -} From 5caf2f0e27f44c0ea40ce580be10683a4f16b7a4 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Tue, 24 Mar 2026 22:19:28 +0800 Subject: [PATCH 06/23] Fix url --- examples/remote/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/remote/client.rs b/examples/remote/client.rs index 0722804ec310b..26623bbd08f1b 100644 --- a/examples/remote/client.rs +++ b/examples/remote/client.rs @@ -45,9 +45,9 @@ fn main() -> AnyhowResult<()> { let host_part2 = format!("{DEFAULT_ADDR}:{DEFAULT_RENDER_PORT}"); let url2 = format!("http://{host_part2}/"); - run_transform_only_query(&url)?; - run_query_root_entities(&url)?; - run_query_all_components_and_entities(&url)?; + run_transform_only_query(&url2)?; + run_query_root_entities(&url2)?; + run_query_all_components_and_entities(&url2)?; Ok(()) } From 63725b1c4704aed9544ffd632fc3b2a31c6347f0 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 21:34:09 +0800 Subject: [PATCH 07/23] Add PreRenderStartup, PreRender and PostRender schedules --- crates/bevy_render/src/error_handler.rs | 11 ++++++++++- crates/bevy_render/src/lib.rs | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/error_handler.rs b/crates/bevy_render/src/error_handler.rs index 41fb3e4a2cd4d..61479a06a0eaa 100644 --- a/crates/bevy_render/src/error_handler.rs +++ b/crates/bevy_render/src/error_handler.rs @@ -12,7 +12,7 @@ use crate::{ render_resource::PipelineCache, renderer::{RenderDevice, WgpuWrapper}, settings::RenderCreation, - FutureRenderResources, RenderStartup, + FutureRenderResources, PreRenderStartup, RenderStartup, }; /// Resource to indicate renderer behavior upon error. @@ -66,6 +66,10 @@ impl Default for RenderErrorHandler { } } +/// Exists in the render world if [`PreRenderStartup`] has run. +#[derive(Resource, Debug)] +pub(crate) struct FirstRenderRun; + /// An error encountered during rendering. #[derive(Debug)] pub struct RenderError { @@ -165,6 +169,11 @@ impl DeviceErrorHandler { /// /// We need both the main and render world to properly handle errors, so we wedge ourselves into [extract](bevy_app::SubApp::set_extract). pub(crate) fn update_state(main_world: &mut World, render_world: &mut World) { + if render_world.get_resource::().is_none() { + render_world.run_schedule(PreRenderStartup); + render_world.insert_resource(FirstRenderRun); + } + if let Some(error) = render_world.resource::().poll() { render_world.insert_resource(RenderState::Errored(error)); }; diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 9f537cdf2113f..0ebc928237625 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -201,6 +201,11 @@ pub enum RenderSystems { PostCleanup, } +/// The prestartup schedule of the [`RenderApp`]. +/// This runs only once, and runs before [`RenderStartup`] +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct PreRenderStartup; + /// The startup schedule of the [`RenderApp`]. /// This can potentially run multiple times, and not on a fresh render world. /// Every time a new [`RenderDevice`](renderer::RenderDevice) is acquired, @@ -238,6 +243,14 @@ impl GpuResourceAppExt for SubApp { #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)] struct RenderRecovery; +/// Runs immediately before the main render schedule. +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct PreRender; + +/// Runs immediately after the main render schedule. +#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct PostRender; + /// The main render schedule. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct Render; @@ -363,7 +376,11 @@ impl Plugin for RenderPlugin { #[cfg(feature = "reflect_functions")] render_app.init_resource::(); + render_app.init_schedule(PreRender); render_app.add_schedule(RenderGraph::base_schedule()); + render_app.init_schedule(PostRender); + + render_app.init_schedule(PreRenderStartup); render_app.init_schedule(RenderStartup); render_app @@ -424,7 +441,9 @@ fn renderer_is_ready(state: Res) -> bool { } fn run_render_schedule(world: &mut World) { + world.run_schedule(PreRender); world.run_schedule(Render); + world.run_schedule(PostRender); } fn send_time(time_sender: Res) { From 25f8dd1c7400d76956582c3817e1ad3678a6b777 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 21:37:46 +0800 Subject: [PATCH 08/23] Use PreRenderStartup and PostRender schedules --- crates/bevy_remote/src/http.rs | 4 ++-- crates/bevy_remote/src/lib.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 9c47b0b32eaa3..3fba47141b995 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -24,7 +24,7 @@ use bevy_ecs::resource::Resource; use bevy_ecs::schedule::IntoScheduleConfigs as _; use bevy_ecs::system::Res; #[cfg(feature = "bevy_render")] -use bevy_render::{RenderApp, RenderStartup}; +use bevy_render::{RenderApp, PreRenderStartup}; use bevy_tasks::{futures_lite::StreamExt, IoTaskPool}; use core::{ convert::Infallible, @@ -150,7 +150,7 @@ impl Plugin for RemoteHttpPlugin { .insert_resource(HostPort(self.render_port)) .insert_resource(HostHeaders(self.headers.clone())) .add_systems( - RenderStartup, + PreRenderStartup, start_http_server.after(setup_mailbox_channel), ); } diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index f9e28ec6286fe..cba3e56654f1a 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -546,7 +546,7 @@ use bevy_ecs::{ }; use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_render")] -use bevy_render::{Render, RenderApp, RenderStartup}; +use bevy_render::{PostRender, RenderApp, PreRenderStartup}; use bevy_utils::prelude::default; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_json::Value; @@ -812,13 +812,13 @@ impl Plugin for RemotePlugin { .insert_resource(render_remote_methods) .init_resource::() .init_resource::() - .add_systems(RenderStartup, setup_mailbox_channel) + .add_systems(PreRenderStartup, setup_mailbox_channel) .configure_sets( - Render, + PostRender, (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), ) .add_systems( - Render, + PostRender, ( (process_remote_requests, process_ongoing_watching_requests) .chain() From 2733bf296c90e5e3eb11f3e81f3e8c80247ac15a Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 21:45:21 +0800 Subject: [PATCH 09/23] fmt --- crates/bevy_remote/src/http.rs | 2 +- crates/bevy_remote/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 3fba47141b995..271578ebd4c6e 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -24,7 +24,7 @@ use bevy_ecs::resource::Resource; use bevy_ecs::schedule::IntoScheduleConfigs as _; use bevy_ecs::system::Res; #[cfg(feature = "bevy_render")] -use bevy_render::{RenderApp, PreRenderStartup}; +use bevy_render::{PreRenderStartup, RenderApp}; use bevy_tasks::{futures_lite::StreamExt, IoTaskPool}; use core::{ convert::Infallible, diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index cba3e56654f1a..74d9536a0b93d 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -546,7 +546,7 @@ use bevy_ecs::{ }; use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_render")] -use bevy_render::{PostRender, RenderApp, PreRenderStartup}; +use bevy_render::{PostRender, PreRenderStartup, RenderApp}; use bevy_utils::prelude::default; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_json::Value; From 6c62e42dcbc2dcd7f6947f9601df83dcaf580d6d Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 22:19:35 +0800 Subject: [PATCH 10/23] with_method_main/with_method_render --- crates/bevy_remote/src/lib.rs | 48 ++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 74d9536a0b93d..36e4ae170293a 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -582,9 +582,29 @@ impl RemotePlugin { } } - /// Add a remote method to the plugin using the given `name` and `handler`. + /// Add a remote method to the plugin using the given `name` and `handler` to main app. + #[inline] + pub fn with_method_main( + self, + name: impl Into, + handler: impl IntoSystem>, BrpResult, M>, + ) -> Self { + self.with_method(name, handler, true) + } + + /// Add a remote method to the plugin using the given `name` and `handler` to render app. + #[inline] + pub fn with_method_render( + self, + name: impl Into, + handler: impl IntoSystem>, BrpResult, M>, + ) -> Self { + self.with_method(name, handler, false) + } + + /// Add a remote method to the plugin using the given `name` and `handler` to given app. #[must_use] - pub fn with_method( + fn with_method( mut self, name: impl Into, handler: impl IntoSystem>, BrpResult, M>, @@ -603,9 +623,29 @@ impl RemotePlugin { self } - /// Add a remote method with a watching handler to the plugin using the given `name`. + /// Add a remote method with a watching handler to the plugin using the given `name` to main app. + #[inline] + pub fn with_watching_method_main( + self, + name: impl Into, + handler: impl IntoSystem>, BrpResult>, M>, + ) -> Self { + self.with_watching_method(name, handler, true) + } + + /// Add a remote method with a watching handler to the plugin using the given `name` to render app. + #[inline] + pub fn with_watching_method_render( + self, + name: impl Into, + handler: impl IntoSystem>, BrpResult>, M>, + ) -> Self { + self.with_watching_method(name, handler, false) + } + + /// Add a remote method with a watching handler to the plugin using the given `name` to given app. #[must_use] - pub fn with_watching_method( + fn with_watching_method( mut self, name: impl Into, handler: impl IntoSystem>, BrpResult>, M>, From 508bd1aabe9b87236ce15c23819f16ca87cadaf3 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 22:25:44 +0800 Subject: [PATCH 11/23] Add RenderMainScheduleOrder resource to manage render schedules --- crates/bevy_render/src/lib.rs | 54 +++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0ebc928237625..a81e9dce1bf32 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -99,7 +99,10 @@ use batching::gpu_preprocessing::BatchingPlugin; use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_asset::{AssetApp, AssetServer}; use bevy_derive::Deref; -use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; +use bevy_ecs::{ + prelude::*, + schedule::{InternedScheduleLabel, ScheduleLabel}, +}; use bevy_platform::time::Instant; use bevy_shader::{load_shader_library, Shader, ShaderLoader}; use bevy_time::TimeSender; @@ -238,11 +241,48 @@ impl GpuResourceAppExt for SubApp { } } -/// The render recovery schedule. This schedule runs the [`Render`] schedule if +/// The render recovery schedule. This schedule runs the [`RenderMainScheduleOrder`] schedules if /// we are in [`RenderState::Ready`], and is otherwise hidden from users. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)] struct RenderRecovery; +/// Defines the schedules to be run for the main rendering, including their order. +#[derive(Resource, Debug)] +pub struct RenderMainScheduleOrder { + /// The labels to run for the main rendering schedule (in the order they will be run). + pub labels: Vec, +} + +impl Default for RenderMainScheduleOrder { + fn default() -> Self { + Self { + labels: vec![PreRender.intern(), Render.intern(), PostRender.intern()], + } + } +} + +impl RenderMainScheduleOrder { + /// Adds the given `schedule` after the `after` schedule + pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) { + let index = self + .labels + .iter() + .position(|current| (**current).eq(&after)) + .unwrap_or_else(|| panic!("Expected {after:?} to exist")); + self.labels.insert(index + 1, schedule.intern()); + } + + /// Adds the given `schedule` before the `before` schedule + pub fn insert_before(&mut self, before: impl ScheduleLabel, schedule: impl ScheduleLabel) { + let index = self + .labels + .iter() + .position(|current| (**current).eq(&before)) + .unwrap_or_else(|| panic!("Expected {before:?} to exist")); + self.labels.insert(index, schedule.intern()); + } +} + /// Runs immediately before the main render schedule. #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] pub struct PreRender; @@ -376,6 +416,8 @@ impl Plugin for RenderPlugin { #[cfg(feature = "reflect_functions")] render_app.init_resource::(); + render_app.init_resource::(); + render_app.init_schedule(PreRender); render_app.add_schedule(RenderGraph::base_schedule()); render_app.init_schedule(PostRender); @@ -441,9 +483,11 @@ fn renderer_is_ready(state: Res) -> bool { } fn run_render_schedule(world: &mut World) { - world.run_schedule(PreRender); - world.run_schedule(Render); - world.run_schedule(PostRender); + world.resource_scope(|world, order: Mut| { + for &label in &order.labels { + let _ = world.try_run_schedule(label); + } + }); } fn send_time(time_sender: Res) { From 9e6fbf9a23ec8ce1164ce68952dbfc2f6b2a1943 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 25 Mar 2026 22:36:31 +0800 Subject: [PATCH 12/23] Use RenderMainScheduleOrder --- crates/bevy_remote/src/lib.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 36e4ae170293a..e1f8e3d325fb9 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -546,7 +546,7 @@ use bevy_ecs::{ }; use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_render")] -use bevy_render::{PostRender, PreRenderStartup, RenderApp}; +use bevy_render::{PostRender, PreRenderStartup, RenderApp, RenderMainScheduleOrder}; use bevy_utils::prelude::default; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_json::Value; @@ -848,17 +848,23 @@ impl Plugin for RemotePlugin { ); } + render_app + .init_schedule(RemoteLast) + .world_mut() + .resource_mut::() + .insert_after(PostRender, RemoteLast); + render_app .insert_resource(render_remote_methods) .init_resource::() .init_resource::() .add_systems(PreRenderStartup, setup_mailbox_channel) .configure_sets( - PostRender, + RemoteLast, (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), ) .add_systems( - PostRender, + RemoteLast, ( (process_remote_requests, process_ongoing_watching_requests) .chain() From 1c4b55288f93fca0b1a18578c28659e08df26396 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Thu, 26 Mar 2026 06:19:36 +0800 Subject: [PATCH 13/23] Rm PreRenderStartup --- crates/bevy_render/src/error_handler.rs | 11 +---------- crates/bevy_render/src/lib.rs | 7 ------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/crates/bevy_render/src/error_handler.rs b/crates/bevy_render/src/error_handler.rs index 61479a06a0eaa..41fb3e4a2cd4d 100644 --- a/crates/bevy_render/src/error_handler.rs +++ b/crates/bevy_render/src/error_handler.rs @@ -12,7 +12,7 @@ use crate::{ render_resource::PipelineCache, renderer::{RenderDevice, WgpuWrapper}, settings::RenderCreation, - FutureRenderResources, PreRenderStartup, RenderStartup, + FutureRenderResources, RenderStartup, }; /// Resource to indicate renderer behavior upon error. @@ -66,10 +66,6 @@ impl Default for RenderErrorHandler { } } -/// Exists in the render world if [`PreRenderStartup`] has run. -#[derive(Resource, Debug)] -pub(crate) struct FirstRenderRun; - /// An error encountered during rendering. #[derive(Debug)] pub struct RenderError { @@ -169,11 +165,6 @@ impl DeviceErrorHandler { /// /// We need both the main and render world to properly handle errors, so we wedge ourselves into [extract](bevy_app::SubApp::set_extract). pub(crate) fn update_state(main_world: &mut World, render_world: &mut World) { - if render_world.get_resource::().is_none() { - render_world.run_schedule(PreRenderStartup); - render_world.insert_resource(FirstRenderRun); - } - if let Some(error) = render_world.resource::().poll() { render_world.insert_resource(RenderState::Errored(error)); }; diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index a81e9dce1bf32..472defcf0c506 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -204,11 +204,6 @@ pub enum RenderSystems { PostCleanup, } -/// The prestartup schedule of the [`RenderApp`]. -/// This runs only once, and runs before [`RenderStartup`] -#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] -pub struct PreRenderStartup; - /// The startup schedule of the [`RenderApp`]. /// This can potentially run multiple times, and not on a fresh render world. /// Every time a new [`RenderDevice`](renderer::RenderDevice) is acquired, @@ -422,8 +417,6 @@ impl Plugin for RenderPlugin { render_app.add_schedule(RenderGraph::base_schedule()); render_app.init_schedule(PostRender); - render_app.init_schedule(PreRenderStartup); - render_app.init_schedule(RenderStartup); render_app .get_schedule_mut(RenderStartup) From 2205cb8d87e3d1c101e821ab4fa197f208c40d25 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Thu, 26 Mar 2026 06:32:13 +0800 Subject: [PATCH 14/23] run_if run_once on RenderStartup --- crates/bevy_remote/src/http.rs | 10 +++++++--- crates/bevy_remote/src/lib.rs | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 271578ebd4c6e..092cc2f1b7872 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -24,7 +24,7 @@ use bevy_ecs::resource::Resource; use bevy_ecs::schedule::IntoScheduleConfigs as _; use bevy_ecs::system::Res; #[cfg(feature = "bevy_render")] -use bevy_render::{PreRenderStartup, RenderApp}; +use bevy_render::{RenderApp, RenderStartup}; use bevy_tasks::{futures_lite::StreamExt, IoTaskPool}; use core::{ convert::Infallible, @@ -141,6 +141,8 @@ impl Plugin for RemoteHttpPlugin { #[cfg(feature = "bevy_render")] { + use bevy_ecs::schedule::common_conditions::run_once; + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; @@ -150,8 +152,10 @@ impl Plugin for RemoteHttpPlugin { .insert_resource(HostPort(self.render_port)) .insert_resource(HostHeaders(self.headers.clone())) .add_systems( - PreRenderStartup, - start_http_server.after(setup_mailbox_channel), + RenderStartup, + start_http_server + .run_if(run_once) + .after(setup_mailbox_channel), ); } } diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index e1f8e3d325fb9..7f83fe628f975 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -546,7 +546,7 @@ use bevy_ecs::{ }; use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_render")] -use bevy_render::{PostRender, PreRenderStartup, RenderApp, RenderMainScheduleOrder}; +use bevy_render::{PostRender, RenderApp, RenderMainScheduleOrder, RenderStartup}; use bevy_utils::prelude::default; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_json::Value; @@ -827,6 +827,8 @@ impl Plugin for RemotePlugin { #[cfg(feature = "bevy_render")] { + use bevy_ecs::schedule::common_conditions::run_once; + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; @@ -858,7 +860,7 @@ impl Plugin for RemotePlugin { .insert_resource(render_remote_methods) .init_resource::() .init_resource::() - .add_systems(PreRenderStartup, setup_mailbox_channel) + .add_systems(RenderStartup, setup_mailbox_channel.run_if(run_once)) .configure_sets( RemoteLast, (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), From 1aac36d0afe812a19c6428e98db284ea963194e6 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 27 Mar 2026 07:48:43 +0800 Subject: [PATCH 15/23] Rever example to main --- examples/remote/client.rs | 51 ++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/examples/remote/client.rs b/examples/remote/client.rs index 26623bbd08f1b..2e9bad0b1d018 100644 --- a/examples/remote/client.rs +++ b/examples/remote/client.rs @@ -1,5 +1,5 @@ //! A simple command line client that allows issuing queries to a remote Bevy -//! render subapp via the BRP. +//! app via the BRP. //! This example requires the `bevy_remote` feature to be enabled. //! You can run it with the following command: //! ```text @@ -12,12 +12,12 @@ use std::any::type_name; use anyhow::Result as AnyhowResult; use bevy::{ ecs::hierarchy::ChildOf, - prelude::info, remote::{ builtin_methods::{ - BrpQuery, BrpQueryFilter, BrpQueryParams, ComponentSelector, BRP_QUERY_METHOD, + BrpQuery, BrpQueryFilter, BrpQueryParams, BrpWriteMessageParams, ComponentSelector, + BRP_QUERY_METHOD, BRP_WRITE_MESSAGE_METHOD, }, - http::{DEFAULT_ADDR, DEFAULT_PORT, DEFAULT_RENDER_PORT}, + http::{DEFAULT_ADDR, DEFAULT_PORT}, BrpRequest, }, transform::components::Transform, @@ -41,13 +41,9 @@ fn main() -> AnyhowResult<()> { // component values. run_query_all_components_and_entities(&url)?; - // Run again against the render port - let host_part2 = format!("{DEFAULT_ADDR}:{DEFAULT_RENDER_PORT}"); - let url2 = format!("http://{host_part2}/"); - - run_transform_only_query(&url2)?; - run_query_root_entities(&url2)?; - run_query_all_components_and_entities(&url2)?; + // Send an `AppExit::Success` message to the app to the remote Bevy app. + // This will make it quit. + send_app_exit(&url)?; Ok(()) } @@ -69,12 +65,12 @@ fn run_query_all_components_and_entities(url: &str) -> Result<(), anyhow::Error> .expect("Unable to convert query parameters to a valid JSON value"), ), }; - info!("query_all req: {query_all_req:#?}"); + println!("query_all req: {query_all_req:#?}"); let query_all_res = ureq::post(url) .send_json(query_all_req)? .body_mut() .read_json::()?; - info!("{query_all_res:#}"); + println!("{query_all_res:#}"); Ok(()) } @@ -94,12 +90,12 @@ fn run_transform_only_query(url: &str) -> Result<(), anyhow::Error> { .expect("Unable to convert query parameters to a valid JSON value"), ), }; - info!("transform request: {get_transform_request:#?}"); + println!("transform request: {get_transform_request:#?}"); let res = ureq::post(url) .send_json(get_transform_request)? .body_mut() .read_json::()?; - info!("{res:#}"); + println!("{res:#}"); Ok(()) } @@ -123,11 +119,32 @@ fn run_query_root_entities(url: &str) -> Result<(), anyhow::Error> { .expect("Unable to convert query parameters to a valid JSON value"), ), }; - info!("transform request: {get_transform_request:#?}"); + println!("transform request: {get_transform_request:#?}"); let res = ureq::post(url) .send_json(get_transform_request)? .body_mut() .read_json::()?; - info!("{res:#}"); + println!("{res:#}"); + Ok(()) +} + +fn send_app_exit(url: &str) -> Result<(), anyhow::Error> { + let write_message_request = BrpRequest { + method: String::from(BRP_WRITE_MESSAGE_METHOD), + id: Some(serde_json::to_value(1)?), + params: Some( + serde_json::to_value(BrpWriteMessageParams { + message: "bevy_app::app::AppExit".to_string(), + value: Some("Success".into()), + }) + .expect("Unable to convert write message parameters to a valid JSON value"), + ), + }; + println!("write message request: {write_message_request:#?}"); + let res = ureq::post(url) + .send_json(write_message_request)? + .body_mut() + .read_json::()?; + println!("{res:#}"); Ok(()) } From 6def24d081577da0ef96d7e6582152fccbc8d8d1 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 27 Mar 2026 07:49:28 +0800 Subject: [PATCH 16/23] Re-add example changes --- examples/remote/client.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/remote/client.rs b/examples/remote/client.rs index 2e9bad0b1d018..7d82ef14174a8 100644 --- a/examples/remote/client.rs +++ b/examples/remote/client.rs @@ -17,7 +17,7 @@ use bevy::{ BrpQuery, BrpQueryFilter, BrpQueryParams, BrpWriteMessageParams, ComponentSelector, BRP_QUERY_METHOD, BRP_WRITE_MESSAGE_METHOD, }, - http::{DEFAULT_ADDR, DEFAULT_PORT}, + http::{DEFAULT_ADDR, DEFAULT_PORT, DEFAULT_RENDER_PORT}, BrpRequest, }, transform::components::Transform, @@ -41,6 +41,14 @@ fn main() -> AnyhowResult<()> { // component values. run_query_all_components_and_entities(&url)?; + // Run again against the render port + let host_part2 = format!("{DEFAULT_ADDR}:{DEFAULT_RENDER_PORT}"); + let url2 = format!("http://{host_part2}/"); + + run_transform_only_query(&url2)?; + run_query_root_entities(&url2)?; + run_query_all_components_and_entities(&url2)?; + // Send an `AppExit::Success` message to the app to the remote Bevy app. // This will make it quit. send_app_exit(&url)?; From aea68dbb30d8330945202a105b5e1a9a79833ddc Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 28 Mar 2026 10:41:39 +0800 Subject: [PATCH 17/23] Fix --- crates/bevy_remote/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index be49af0943206..5be419df2ba6e 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -546,7 +546,7 @@ use bevy_ecs::{ }; use bevy_platform::collections::HashMap; #[cfg(feature = "bevy_render")] -use bevy_render::{PostRender, RenderApp, RenderMainScheduleOrder, RenderStartup}; +use bevy_render::{Render, RenderApp, RenderScheduleOrder, RenderStartup}; use bevy_utils::prelude::default; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_json::Value; @@ -863,8 +863,8 @@ impl Plugin for RemotePlugin { render_app .init_schedule(RemoteLast) .world_mut() - .resource_mut::() - .insert_after(PostRender, RemoteLast); + .resource_mut::() + .insert_after(Render, RemoteLast); render_app .insert_resource(render_remote_methods) From 50d83f73d235ca1cdd25ed76d400d250630c57d5 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 28 Mar 2026 10:52:40 +0800 Subject: [PATCH 18/23] Add release notes for brp --- _release-content/release-notes/brp.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 _release-content/release-notes/brp.md diff --git a/_release-content/release-notes/brp.md b/_release-content/release-notes/brp.md new file mode 100644 index 0000000000000..1da3ddb6edc8b --- /dev/null +++ b/_release-content/release-notes/brp.md @@ -0,0 +1,14 @@ +--- +title: Bevy Remote Protocol, schedules and `RenderApp` +authors: ["@zeophlite"] +pull_requests: [23446, 23447, 23452] +--- + +BRP now has methods for schedule introspection: + +- `schedule.list` lists all schedules +- `schedule.graph` gives the system sets and their dependencies in a schedule + +Check the PR's for details on these methods. + +BRP now also runs in the Render World. This has all the same methods, and runs on port `15703` by default. From 9ada7e31c06a154a5c6aa94d05cfa6ddd70e29b2 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 28 Mar 2026 12:25:24 +0800 Subject: [PATCH 19/23] Docs --- crates/bevy_ecs/src/schedule/schedule.rs | 2 ++ crates/bevy_render/src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 4ad0792b5fe78..5e860ea93c9a0 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -46,7 +46,9 @@ pub struct Schedules { inner: HashMap, /// List of [`ComponentId`]s to ignore when reporting system order ambiguity conflicts pub ignored_scheduling_ambiguities: BTreeSet, + /// Set of schedule labels that have been removed to execute in [`World::try_schedule_scope`]. temporarily_removed: HashSet, + /// Set of schedule labels that have attempted to be read in [`World::try_schedule_scope`]. empty_labels: HashSet, } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 5f042e8590b31..36571573794b1 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -242,6 +242,8 @@ impl GpuResourceAppExt for SubApp { struct RenderRecovery; /// Defines the schedules to be run for the rendering, including their order. +/// +/// This is the same approach as [`MainScheduleOrder`](`bevy_app::main_schedule::MainScheduleOrder`). #[derive(Resource, Debug)] pub struct RenderScheduleOrder { /// The labels to run for the rendering schedule (in the order they will be run). From af92b1d29947e33946fbd3ae94c65e02e15e6a9e Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 28 Mar 2026 12:27:42 +0800 Subject: [PATCH 20/23] rm release notes --- _release-content/release-notes/brp.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 _release-content/release-notes/brp.md diff --git a/_release-content/release-notes/brp.md b/_release-content/release-notes/brp.md deleted file mode 100644 index 1da3ddb6edc8b..0000000000000 --- a/_release-content/release-notes/brp.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: Bevy Remote Protocol, schedules and `RenderApp` -authors: ["@zeophlite"] -pull_requests: [23446, 23447, 23452] ---- - -BRP now has methods for schedule introspection: - -- `schedule.list` lists all schedules -- `schedule.graph` gives the system sets and their dependencies in a schedule - -Check the PR's for details on these methods. - -BRP now also runs in the Render World. This has all the same methods, and runs on port `15703` by default. From 6e021acbc11f7dfa0bbfe4b9f8b6d8de23b008d8 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 28 Mar 2026 12:29:28 +0800 Subject: [PATCH 21/23] fmt --- crates/bevy_render/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 36571573794b1..d416a1d1cabd2 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -242,7 +242,7 @@ impl GpuResourceAppExt for SubApp { struct RenderRecovery; /// Defines the schedules to be run for the rendering, including their order. -/// +/// /// This is the same approach as [`MainScheduleOrder`](`bevy_app::main_schedule::MainScheduleOrder`). #[derive(Resource, Debug)] pub struct RenderScheduleOrder { From 9420da44b2915829f5898ac753d6b3540a567d10 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sat, 28 Mar 2026 12:52:37 +0800 Subject: [PATCH 22/23] Doc --- crates/bevy_render/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index d416a1d1cabd2..134a2b898dc19 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -243,7 +243,7 @@ struct RenderRecovery; /// Defines the schedules to be run for the rendering, including their order. /// -/// This is the same approach as [`MainScheduleOrder`](`bevy_app::main_schedule::MainScheduleOrder`). +/// This is the same approach as [`MainScheduleOrder`](`bevy_app::MainScheduleOrder`). #[derive(Resource, Debug)] pub struct RenderScheduleOrder { /// The labels to run for the rendering schedule (in the order they will be run). From b77e4fd2a22313ea9d65900701bb90778b99b868 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sun, 29 Mar 2026 06:56:30 +0800 Subject: [PATCH 23/23] Update crates/bevy_ecs/src/schedule/schedule.rs Co-authored-by: Kevin Chen --- crates/bevy_ecs/src/schedule/schedule.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 5e860ea93c9a0..fc8f958bf865b 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -48,7 +48,8 @@ pub struct Schedules { pub ignored_scheduling_ambiguities: BTreeSet, /// Set of schedule labels that have been removed to execute in [`World::try_schedule_scope`]. temporarily_removed: HashSet, - /// Set of schedule labels that have attempted to be read in [`World::try_schedule_scope`]. + /// Set of schedule labels that have attempted to be read in [`World::try_schedule_scope`], + /// but have no associated [`Schedule`] in `inner` empty_labels: HashSet, }