From 03e4987048e807cad731b4365ba928fbaea283e8 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Mon, 5 Feb 2024 21:16:52 -0800 Subject: [PATCH 1/4] wait for render app when channels are dropped --- crates/bevy_render/src/pipelined_rendering.rs | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index adde2259a873b..d8da86ac6dddb 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -17,13 +17,34 @@ use crate::RenderApp; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] pub struct RenderExtractApp; -/// Channel to send the render app from the main thread to the rendering thread +/// Channels used by the main app to send and receive the render app. #[derive(Resource)] -pub struct MainToRenderAppSender(pub Sender); +pub struct RenderAppChannels { + app_to_render_sender: Sender, + render_to_app_receiver: Receiver, + render_app_in_render_thread: bool, +} -/// Channel to send the render app from the render thread to the main thread -#[derive(Resource)] -pub struct RenderToMainAppReceiver(pub Receiver); +impl RenderAppChannels { + fn send_blocking(&mut self, render_app: SubApp) { + self.app_to_render_sender.send_blocking(render_app).unwrap(); + self.render_app_in_render_thread = true; + } + + async fn recv(&mut self) -> SubApp { + let render_app = self.render_to_app_receiver.recv().await.unwrap(); + self.render_app_in_render_thread = false; + render_app + } +} + +impl Drop for RenderAppChannels { + fn drop(&mut self) { + if self.render_app_in_render_thread { + self.render_to_app_receiver.recv_blocking().ok(); + } + } +} /// The [`PipelinedRenderingPlugin`] can be added to your application to enable pipelined rendering. /// This moves rendering into a different thread, so that the Nth frame's rendering can @@ -96,8 +117,11 @@ impl Plugin for PipelinedRenderingPlugin { render_to_app_sender.send_blocking(render_app).unwrap(); - app.insert_resource(MainToRenderAppSender(app_to_render_sender)); - app.insert_resource(RenderToMainAppReceiver(render_to_app_receiver)); + app.insert_resource(RenderAppChannels { + app_to_render_sender, + render_to_app_receiver, + render_app_in_render_thread: false, + }); std::thread::spawn(move || { #[cfg(feature = "trace")] @@ -136,21 +160,19 @@ impl Plugin for PipelinedRenderingPlugin { // runs extract, and then sends the rendering world back to the render thread. fn update_rendering(app_world: &mut World, _sub_app: &mut App) { 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_with_executor(true, Some(&*main_thread_executor.0), |s| { - s.spawn(async { - let receiver = world.get_resource::().unwrap(); - receiver.0.recv().await.unwrap() - }); - }) - .pop() - .unwrap(); - - render_app.extract(world); - - let sender = world.resource::(); - sender.0.send_blocking(render_app).unwrap(); + world.resource_scope(|world, mut render_channels: 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_with_executor(true, Some(&*main_thread_executor.0), |s| { + s.spawn(async { render_channels.recv().await }); + }) + .pop() + .unwrap(); + + render_app.extract(world); + + render_channels.send_blocking(render_app); + }); }); } From 2d1db3b83626da0d416aae3c55ff334bf0823c20 Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Tue, 6 Feb 2024 21:53:38 -0800 Subject: [PATCH 2/4] add a comment about blocking on drop --- crates/bevy_render/src/pipelined_rendering.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index d8da86ac6dddb..6afe84cee2d71 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -41,6 +41,9 @@ impl RenderAppChannels { impl Drop for RenderAppChannels { fn drop(&mut self) { if self.render_app_in_render_thread { + // Any non-send data in the render world was initialized on the main thread. + // So on dropping the main world and ending the app, we block and wait for + // the render world to return to drop it. self.render_to_app_receiver.recv_blocking().ok(); } } From ed9271954a25bdb794a16f0113fbd275e312264a Mon Sep 17 00:00:00 2001 From: Mike Hsu Date: Tue, 6 Feb 2024 22:05:35 -0800 Subject: [PATCH 3/4] make a public constructor --- crates/bevy_render/src/pipelined_rendering.rs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index 6afe84cee2d71..08d549522c7fc 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -26,12 +26,26 @@ pub struct RenderAppChannels { } impl RenderAppChannels { - fn send_blocking(&mut self, render_app: SubApp) { + /// Create a `RenderAppChannels` from a [`async_channel::Receiver`] and [`async_channel::Sender`] + pub fn new( + app_to_render_sender: Sender, + render_to_app_receiver: Receiver, + ) -> Self { + Self { + app_to_render_sender, + render_to_app_receiver, + render_app_in_render_thread: false, + } + } + + /// Send the `render_app` to the rendering thread. + pub fn send_blocking(&mut self, render_app: SubApp) { self.app_to_render_sender.send_blocking(render_app).unwrap(); self.render_app_in_render_thread = true; } - async fn recv(&mut self) -> SubApp { + /// Receiver the `render_app` from the rendering thread. + pub async fn recv(&mut self) -> SubApp { let render_app = self.render_to_app_receiver.recv().await.unwrap(); self.render_app_in_render_thread = false; render_app @@ -43,7 +57,8 @@ impl Drop for RenderAppChannels { if self.render_app_in_render_thread { // Any non-send data in the render world was initialized on the main thread. // So on dropping the main world and ending the app, we block and wait for - // the render world to return to drop it. + // the render world to return to drop it. Which allows the non-send data + // drop methods to run on the correct thread. self.render_to_app_receiver.recv_blocking().ok(); } } @@ -120,11 +135,10 @@ impl Plugin for PipelinedRenderingPlugin { render_to_app_sender.send_blocking(render_app).unwrap(); - app.insert_resource(RenderAppChannels { + app.insert_resource(RenderAppChannels::new( app_to_render_sender, render_to_app_receiver, - render_app_in_render_thread: false, - }); + )); std::thread::spawn(move || { #[cfg(feature = "trace")] From 98c8b497179c906e58a549abc68eee96e82bce6d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 7 Feb 2024 22:12:40 -0500 Subject: [PATCH 4/4] Typo Co-authored-by: Friz64 --- crates/bevy_render/src/pipelined_rendering.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index 08d549522c7fc..0688c90095f95 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -44,7 +44,7 @@ impl RenderAppChannels { self.render_app_in_render_thread = true; } - /// Receiver the `render_app` from the rendering thread. + /// Receive the `render_app` from the rendering thread. pub async fn recv(&mut self) -> SubApp { let render_app = self.render_to_app_receiver.recv().await.unwrap(); self.render_app_in_render_thread = false;