diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 4ad0792b5fe78..fc8f958bf865b 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -46,7 +46,10 @@ 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`], + /// but have no associated [`Schedule`] in `inner` empty_labels: HashSet, } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index a4f5c3d13d883..ddf270ac01381 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -358,6 +358,7 @@ reflect_functions = [ "bevy_reflect/functions", "bevy_app/reflect_functions", "bevy_ecs/reflect_functions", + "bevy_render?/reflect_functions", ] # Enable automatic reflect registration using inventory. @@ -365,6 +366,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. @@ -372,6 +374,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..c569df59078c0 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", diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 24edd3d11ec94..092cc2f1b7872 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -3,11 +3,15 @@ //! 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. #![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 +20,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 +51,11 @@ use std::{ /// This value was chosen randomly. pub const DEFAULT_PORT: u16 = 15702; +/// 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. pub const DEFAULT_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); @@ -95,12 +108,15 @@ 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. 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 +126,7 @@ impl Default for RemoteHttpPlugin { Self { address: DEFAULT_ADDR, port: DEFAULT_PORT, + render_port: DEFAULT_RENDER_PORT, headers: Headers::new(), } } @@ -121,6 +138,26 @@ 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")] + { + use bevy_ecs::schedule::common_conditions::run_once; + + 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 + .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 384b9512e5db4..5be419df2ba6e 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, RenderScheduleOrder, RenderStartup}; use bevy_utils::prelude::default; use serde::{ser::SerializeMap, Deserialize, Serialize}; use serde_json::Value; @@ -564,8 +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 the main app. methods: RwLock>, + /// The verbs that the server will recognize and respond to for the render subapp. + render_methods: RwLock>, } impl RemotePlugin { @@ -574,129 +578,218 @@ impl RemotePlugin { fn empty() -> Self { Self { methods: RwLock::new(vec![]), + render_methods: RwLock::new(vec![]), } } - /// 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>, + to_main: bool, ) -> Self { - self.methods.get_mut().unwrap().push(( + (if to_main { + self.methods.get_mut() + } else { + self.render_methods.get_mut() + }) + .unwrap() + .push(( name.into(), RemoteMethodHandler::Instant(Box::new(IntoSystem::into_system(handler))), )); 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>, + to_main: bool, ) -> Self { - self.methods.get_mut().unwrap().push(( + (if to_main { + self.methods.get_mut() + } else { + self.render_methods.get_mut() + }) + .unwrap() + .push(( name.into(), RemoteMethodHandler::Watching(Box::new(IntoSystem::into_system(handler))), )); self } + + /// Create the default list of BRP methods + 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, + ) + .with_method( + builtin_methods::BRP_SCHEDULE_LIST, + builtin_methods::schedule_list, + to_main, + ) + .with_method( + builtin_methods::BRP_SCHEDULE_GRAPH, + builtin_methods::schedule_graph, + 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, - ) - .with_method( - builtin_methods::BRP_SCHEDULE_LIST, - builtin_methods::schedule_list, - ) - .with_method( - builtin_methods::BRP_SCHEDULE_GRAPH, - builtin_methods::schedule_graph, - ) + let mut t = Self::empty(); + t = t.add_default_methods(true); + + #[cfg(feature = "bevy_render")] + { + t = t.add_default_methods(false); + } + + t } } @@ -707,7 +800,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), @@ -741,6 +834,57 @@ impl Plugin for RemotePlugin { remove_closed_watching_requests.in_set(RemoteSystems::Cleanup), ), ); + + #[cfg(feature = "bevy_render")] + { + use bevy_ecs::schedule::common_conditions::run_once; + + let Some(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + 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), + ), + }, + ); + } + + render_app + .init_schedule(RemoteLast) + .world_mut() + .resource_mut::() + .insert_after(Render, RemoteLast); + + render_app + .insert_resource(render_remote_methods) + .init_resource::() + .init_resource::() + .add_systems(RenderStartup, setup_mailbox_channel.run_if(run_once)) + .configure_sets( + RemoteLast, + (RemoteSystems::ProcessRequests, RemoteSystems::Cleanup).chain(), + ) + .add_systems( + RemoteLast, + ( + (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 19d52802a57d7..7bc37eff03bfa 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 117876078ede0..134a2b898dc19 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::MainScheduleOrder`). #[derive(Resource, Debug)] pub struct RenderScheduleOrder { /// The labels to run for the rendering schedule (in the order they will be run). @@ -395,6 +397,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/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)?;