diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 7ea8c04706d9e..68d5dcadf6ce1 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -67,7 +67,7 @@ pub struct App { /// the application's event loop and advancing the [`Schedule`]. /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). - pub runner: Box, // send/sync bound is only required to make App Send/Sync + pub runner: Box, // Send + Sync bound is only required to make App Send + Sync /// A container of [`Stage`]s set to be run in a linear order. pub schedule: Schedule, sub_apps: HashMap, @@ -89,7 +89,7 @@ impl Debug for App { pub struct SubApp { app: App, extract: Box, // Send + Sync bound is only required to make SubApp send sync - runner: Box, // this send sync bound is required since we're actually sending this function to another thread + runner: Box, // this Send + Sync bound is required since we're running this function on another thread } impl SubApp { @@ -161,7 +161,7 @@ impl App { /// See [`add_sub_app`](Self::add_sub_app) and [`run_once`](Schedule::run_once) for more details. pub fn update(&mut self) { #[cfg(feature = "trace")] - let _bevy_frame_update_span = info_span!("main_app").entered(); + let _bevy_frame_update_span = info_span!("main app").entered(); self.schedule.run(&mut self.world); for sub_app in self.sub_apps.values_mut() { sub_app.extract(&mut self.world); @@ -997,15 +997,15 @@ impl App { /// Adds an [`App`] as a child of the current one. /// - /// The provided function `f` is called by the [`update`](Self::update) method. The [`World`] + /// The provided functions `extract` and `runner` are normally called by the [`update`](Self::update) method. The [`World`] /// parameter represents the main app world, while the [`App`] parameter is just a mutable /// reference to the `SubApp` itself. pub fn add_sub_app( &mut self, label: impl AppLabel, mut app: App, - sub_app_extract: impl Fn(&mut World, &mut App) + 'static + Send + Sync, - sub_app_runner: impl Fn(&mut App) + 'static + Send + Sync, + extract: impl Fn(&mut World, &mut App) + 'static + Send + Sync, + runner: impl Fn(&mut App) + 'static + Send + Sync, ) -> &mut Self { if let Some(executor) = self.world.get_resource::() { app.world.insert_resource(executor.clone()); @@ -1014,8 +1014,8 @@ impl App { label.as_label(), SubApp { app, - extract: Box::new(sub_app_extract), - runner: Box::new(sub_app_runner), + extract: Box::new(extract), + runner: Box::new(runner), }, ); self @@ -1055,12 +1055,12 @@ impl App { } } - /// inserts an existing sub app into the app + /// Inserts an existing sub app into the app pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) { self.sub_apps.insert(label.as_label(), sub_app); } - /// remove a sub app from the app + /// Removes a sub app from the app. Returns None if the label doesn't exist. pub fn remove_sub_app(&mut self, label: impl AppLabel) -> Option { self.sub_apps.remove(&label.as_label()) } diff --git a/crates/bevy_ecs/src/schedule/executor_parallel.rs b/crates/bevy_ecs/src/schedule/executor_parallel.rs index 5a80aea31307a..05df3411bf1c2 100644 --- a/crates/bevy_ecs/src/schedule/executor_parallel.rs +++ b/crates/bevy_ecs/src/schedule/executor_parallel.rs @@ -18,7 +18,7 @@ use fixedbitset::FixedBitSet; #[cfg(test)] use scheduling_event::*; -/// +/// Newtyped [`ThreadExecutor`] [`Resource`] that is used to run systems on the main thread #[derive(Resource, Default)] pub struct MainThreadExecutor(pub Arc); diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 4d3ad435cff7a..0c35a96591924 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -76,4 +76,4 @@ basis-universal = { version = "0.2.0", optional = true } encase = { version = "0.4", features = ["glam"] } # For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans. profiling = { version = "1", features = ["profile-with-tracing"], optional = true } -async-channel = "1.4" \ No newline at end of file +async-channel = "1.4" diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index da74e7782797f..04b4357e420d5 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -63,6 +63,9 @@ use std::{ /// Contains the default Bevy rendering backend based on wgpu. pub struct RenderPlugin { + /// Pipelined rendering runs the rendering simultaneously with the main app. + /// Use this to turn pipelined rendering on or off. By default it's on in native + /// environments and off in wasm. pub use_pipelined_rendering: bool, } @@ -237,7 +240,7 @@ impl Plugin for RenderPlugin { app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { #[cfg(feature = "trace")] - let _render_span = bevy_utils::tracing::info_span!("extract").entered(); + let _render_span = bevy_utils::tracing::info_span!("extract main to render app").entered(); { #[cfg(feature = "trace")] let _stage_span = @@ -272,6 +275,8 @@ impl Plugin for RenderPlugin { extract(app_world, render_app); } }, |render_app| { + #[cfg(feature = "trace")] + let _render_span = bevy_utils::tracing::info_span!("render app").entered(); { #[cfg(feature = "trace")] let _stage_span = diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index 3227cb014adb5..0f71b58cb955e 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -1,4 +1,5 @@ use async_channel::{Receiver, Sender}; + use bevy_app::{App, SubApp}; use bevy_ecs::{ schedule::MainThreadExecutor, @@ -7,22 +8,19 @@ use bevy_ecs::{ }; use bevy_tasks::ComputeTaskPool; -#[cfg(feature = "trace")] -use bevy_utils::tracing::Instrument; - use crate::{PipelinedRenderingApp, RenderApp}; -/// Resource to be used for pipelined rendering for sending the render app from the main thread to the rendering thread +/// Resource for pipelined rendering to send the render app from the main thread to the rendering thread #[derive(Resource)] pub struct MainToRenderAppSender(pub Sender); -/// Resource used by pipelined rendering to send the render app from the render thread to the main thread +/// Resource for pipelined rendering to send the render app from the render thread to the main thread #[derive(Resource)] pub struct RenderToMainAppReceiver(pub Receiver); -/// sets up the render thread and insert resource into the main app for controlling the render thread +/// Sets up the render thread and inserts resources into the main app used for controlling the render thread +/// This does nothing if pipelined rendering is not enabled. pub fn setup_rendering(app: &mut App) { - // skip this if pipelined rendering is not enabled if app.get_sub_app(PipelinedRenderingApp).is_err() { return; } @@ -36,42 +34,38 @@ pub fn setup_rendering(app: &mut App) { app.insert_resource(MainToRenderAppSender(app_to_render_sender)); app.insert_resource(RenderToMainAppReceiver(render_to_app_receiver)); - let render_task = async move { - loop { - // TODO: exit loop when app is exited - let recv_task = app_to_render_receiver.recv(); - let mut sub_app = recv_task.await.unwrap(); - sub_app.run(); - render_to_app_sender.send(sub_app).await.unwrap(); - } - }; - #[cfg(feature = "trace")] - let span = bevy_utils::tracing::info_span!("render app"); - #[cfg(feature = "trace")] - let render_task = render_task.instrument(span); - ComputeTaskPool::get().spawn(render_task).detach(); + ComputeTaskPool::get() + .spawn(async move { + loop { + // TODO: exit loop when app is exited + let recv_task = app_to_render_receiver.recv(); + let mut sub_app = recv_task.await.unwrap(); + sub_app.run(); + render_to_app_sender.send(sub_app).await.unwrap(); + } + }) + .detach(); } +/// This function is used for synchronizing the main app with the render world. +/// Do not call this function if pipelined rendering is not setup. pub fn update_rendering(app_world: &mut World) { - // wait to get the render app back to signal that rendering is finished - let mut render_app = app_world - .resource_scope(|world, main_thread_executor: Mut| { - ComputeTaskPool::get() - .scope(Some(main_thread_executor.0.clone()), |s| { - s.spawn(async { - let receiver = world.get_resource::().unwrap(); - let recv = receiver.0.recv(); - recv.await.unwrap() - }); - }) - .pop() - }) - .unwrap(); + app_world.resource_scope(|world, main_thread_executor: Mut| { + // we use a scope here to run any main thread tasks that the render world still needs to run + // while we wait for the render world to be received. + let mut render_app = ComputeTaskPool::get() + .scope(Some(main_thread_executor.0.clone()), |s| { + s.spawn(async { + let receiver = world.get_resource::().unwrap(); + receiver.0.recv().await.unwrap() + }); + }) + .pop() + .unwrap(); - render_app.extract(app_world); + render_app.extract(world); - app_world.resource_scope(|_world, sender: Mut| { + let sender = world.get_resource::().unwrap(); sender.0.send_blocking(render_app).unwrap(); }); - // frame pacing plugin should run here somehow. i.e. after rendering, but before input handling } diff --git a/crates/bevy_tasks/src/main_thread_executor.rs b/crates/bevy_tasks/src/main_thread_executor.rs index eceb1b5d5c884..bb91faaf91626 100644 --- a/crates/bevy_tasks/src/main_thread_executor.rs +++ b/crates/bevy_tasks/src/main_thread_executor.rs @@ -31,17 +31,17 @@ impl ThreadExecutor { /// Gets the `[MainThreadSpawner]` for the thread executor. /// Use this to spawn tasks that run on the thread this was instatiated on. - pub fn spawner(&self) -> MainThreadSpawner<'static> { - MainThreadSpawner(self.executor.clone()) + pub fn spawner(&self) -> ThreadSpawner<'static> { + ThreadSpawner(self.executor.clone()) } /// Gets the `[MainThreadTicker]` for this executor. /// Use this to tick the executor. /// It only returns the ticker if it's on the thread the executor was created on /// and returns `None` otherwise. - pub fn ticker(&self) -> Option { + pub fn ticker(&self) -> Option { if thread::current().id() == self.thread_id { - return Some(MainThreadTicker { + return Some(ThreadTicker { executor: self.executor.clone(), _marker: PhantomData::default(), }); @@ -50,22 +50,24 @@ impl ThreadExecutor { } } +/// Used to spawn on the [`ThreadExecutor`] #[derive(Debug)] -pub struct MainThreadSpawner<'a>(Arc>); -impl<'a> MainThreadSpawner<'a> { +pub struct ThreadSpawner<'a>(Arc>); +impl<'a> ThreadSpawner<'a> { /// Spawn a task on the main thread pub fn spawn(&self, future: impl Future + Send + 'a) -> Task { self.0.spawn(future) } } +/// Used to tick the [`ThreadExecutor`] #[derive(Debug)] -pub struct MainThreadTicker { +pub struct ThreadTicker { executor: Arc>, // make type not send or sync _marker: PhantomData<*const ()>, } -impl MainThreadTicker { +impl ThreadTicker { /// Tick the main thread executor. /// This needs to be called manually on the thread if it is not being used with /// a `[TaskPool::scope]`. diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index e2af8a129d3e4..176c8eac96e0f 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -10,7 +10,7 @@ use concurrent_queue::ConcurrentQueue; use futures_lite::{future, FutureExt}; use crate::Task; -use crate::{main_thread_executor::MainThreadSpawner, ThreadExecutor}; +use crate::{main_thread_executor::ThreadSpawner, ThreadExecutor}; /// Used to create a [`TaskPool`] #[derive(Debug, Default, Clone)] @@ -157,7 +157,7 @@ impl TaskPool { /// /// This is similar to `rayon::scope` and `crossbeam::scope` /// - /// The `thread_executor` optional parameter can be used to pass a `[ThreadExecutor]` to + /// The `thread_executor` optional parameter can be used to pass a [`ThreadExecutor`] to /// spawn tasks on when calling `spawn_on_scope`. This can be useful for spawning tasks that /// must run on the main thread. If `None` is passed then `spawn_on_scope` runs tasks on /// the thread `scope` is run on. @@ -257,7 +257,7 @@ impl TaskPool { Arc::new(ThreadExecutor::new()) }; let thread_spawner = thread_executor.spawner(); - let thread_spawner: MainThreadSpawner<'env> = unsafe { mem::transmute(thread_spawner) }; + let thread_spawner: ThreadSpawner<'env> = unsafe { mem::transmute(thread_spawner) }; let spawned: ConcurrentQueue> = ConcurrentQueue::unbounded(); let spawned_ref: &'env ConcurrentQueue> = unsafe { mem::transmute(&spawned) }; @@ -371,7 +371,7 @@ impl Drop for TaskPool { #[derive(Debug)] pub struct Scope<'scope, 'env: 'scope, T> { executor: &'scope async_executor::Executor<'scope>, - thread_spawner: MainThreadSpawner<'scope>, + thread_spawner: ThreadSpawner<'scope>, spawned: &'scope ConcurrentQueue>, // make `Scope` invariant over 'scope and 'env scope: PhantomData<&'scope mut &'scope ()>,