Skip to content

Commit 5202c4e

Browse files
committed
use std::sync::mpsc::channel in all cases, WinitPlugin now spawns extra listener thread
1 parent 6cf907d commit 5202c4e

File tree

5 files changed

+114
-145
lines changed

5 files changed

+114
-145
lines changed

crates/bevy_app/src/app.rs

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
2-
app_thread_channel, AppEvent, Main, MainSchedulePlugin, PlaceholderPlugin, Plugin, Plugins,
3-
PluginsState, SubApp, SubApps,
2+
app_thread_channel, AppEvent, AppEventReceiver, AppEventSender, Main, MainSchedulePlugin,
3+
PlaceholderPlugin, Plugin, Plugins, PluginsState, SubApp, SubApps,
44
};
55
pub use bevy_derive::AppLabel;
66
use bevy_ecs::{
@@ -64,6 +64,8 @@ pub struct App {
6464
pub sub_apps: SubApps,
6565
#[doc(hidden)]
6666
pub tls: ThreadLocalStorage,
67+
send: AppEventSender,
68+
recv: AppEventReceiver,
6769
/// The function that will manage the app's lifecycle.
6870
///
6971
/// Bevy provides the [`WinitPlugin`] and [`ScheduleRunnerPlugin`] for windowed and headless
@@ -114,65 +116,96 @@ impl App {
114116
///
115117
/// Use this constructor if you want to customize scheduling, exit handling, cleanup, etc.
116118
pub fn empty() -> App {
119+
let (send, recv) = app_thread_channel();
117120
Self {
118121
sub_apps: SubApps::new(),
119122
tls: ThreadLocalStorage::new(),
123+
send,
124+
recv,
120125
runner: Some(Box::new(run_once)),
121126
}
122127
}
123128

124129
/// Disassembles the [`App`] and returns its individual parts.
125-
pub fn into_parts(self) -> (SubApps, ThreadLocalStorage, Option<RunnerFn>) {
130+
#[doc(hidden)]
131+
pub fn into_parts(
132+
self,
133+
) -> (
134+
SubApps,
135+
ThreadLocalStorage,
136+
AppEventSender,
137+
AppEventReceiver,
138+
Option<RunnerFn>,
139+
) {
126140
let Self {
127141
sub_apps,
128142
tls,
143+
send,
144+
recv,
129145
runner,
130146
} = self;
131147

132-
(sub_apps, tls, runner)
148+
(sub_apps, tls, send, recv, runner)
133149
}
134150

135151
/// Returns an [`App`] assembled from the given individual parts.
152+
#[doc(hidden)]
136153
pub fn from_parts(
137154
sub_apps: SubApps,
138155
tls: ThreadLocalStorage,
156+
send: AppEventSender,
157+
recv: AppEventReceiver,
139158
runner: Option<RunnerFn>,
140159
) -> Self {
141160
App {
142161
sub_apps,
143162
tls,
163+
send,
164+
recv,
144165
runner,
145166
}
146167
}
147168

169+
/// Inserts the channel to [`ThreadLocals`] into all sub-apps.
170+
#[doc(hidden)]
171+
pub fn insert_tls_channel(&mut self) {
172+
self.sub_apps.iter_mut().for_each(|sub_app| {
173+
self.tls
174+
.insert_channel(sub_app.world_mut(), self.send.clone());
175+
});
176+
}
177+
178+
/// Removes the channel to [`ThreadLocals`] from all sub-apps.
179+
#[doc(hidden)]
180+
pub fn remove_tls_channel(&mut self) {
181+
self.sub_apps
182+
.iter_mut()
183+
.for_each(|sub_app| self.tls.remove_channel(sub_app.world_mut()));
184+
}
185+
148186
/// Runs the default schedules of all sub-apps (starting with the "main" app) once.
149187
pub fn update(&mut self) {
150188
if self.is_building_plugins() {
151189
panic!("App::update() was called while a plugin was building.");
152190
}
153191

154-
// disassemble
155-
let (mut sub_apps, tls, runner) = std::mem::take(self).into_parts();
156-
157-
// create channel
158-
let (send, recv) = app_thread_channel();
192+
self.insert_tls_channel();
159193

160-
// insert channel
161-
sub_apps
162-
.iter_mut()
163-
.for_each(|sub_app| tls.insert_channel(sub_app.world_mut(), send.clone()));
194+
// disassemble
195+
let (mut sub_apps, tls, send, recv, runner) = std::mem::take(self).into_parts();
164196

165197
#[cfg(not(target_arch = "wasm32"))]
166198
{
167199
// Move sub-apps to another thread and run an event loop in this thread.
200+
let thread_send = send.clone();
168201
let thread = std::thread::spawn(move || {
169202
let result = catch_unwind(AssertUnwindSafe(|| {
170203
sub_apps.update();
171-
send.send(AppEvent::Exit(sub_apps)).unwrap();
204+
thread_send.send(AppEvent::Exit(sub_apps)).unwrap();
172205
}));
173206

174207
if let Some(payload) = result.err() {
175-
send.send(AppEvent::Error(payload)).unwrap();
208+
thread_send.send(AppEvent::Error(payload)).unwrap();
176209
}
177210
});
178211

@@ -200,13 +233,10 @@ impl App {
200233
sub_apps.update();
201234
}
202235

203-
// remove channel
204-
sub_apps
205-
.iter_mut()
206-
.for_each(|sub_app| tls.remove_channel(sub_app.world_mut()));
207-
208236
// reassemble
209-
*self = App::from_parts(sub_apps, tls, runner);
237+
*self = App::from_parts(sub_apps, tls, send, recv, runner);
238+
239+
self.remove_tls_channel();
210240
}
211241

212242
/// Runs the [`App`] by calling its [runner](Self::set_runner).
@@ -239,6 +269,10 @@ impl App {
239269
panic!("App::run() was called while a plugin was building.");
240270
}
241271

272+
// Insert channel here because some sub-apps are moved to a different thread during
273+
// plugin build.
274+
self.insert_tls_channel();
275+
242276
if self.plugins_state() == PluginsState::Ready {
243277
// If we're already ready, we finish up now and advance one frame.
244278
// This prevents black frames during the launch transition on iOS.
@@ -893,14 +927,6 @@ impl App {
893927
type RunnerFn = Box<dyn FnOnce(App)>;
894928

895929
fn run_once(mut app: App) {
896-
// TODO: rework app setup
897-
// create channel
898-
let (send, recv) = app_thread_channel();
899-
// insert channel
900-
app.sub_apps
901-
.iter_mut()
902-
.for_each(|sub_app| app.tls.insert_channel(sub_app.world_mut(), send.clone()));
903-
904930
// wait for plugins to finish setting up
905931
let plugins_state = app.plugins_state();
906932
if plugins_state != PluginsState::Cleaned {
@@ -912,13 +938,13 @@ fn run_once(mut app: App) {
912938
app.cleanup();
913939
}
914940

915-
// if plugins where cleaned before the runner start, an update already ran
941+
// If plugins where cleaned before the runner start, an update already ran
916942
if plugins_state == PluginsState::Cleaned {
917943
return;
918944
}
919945

920946
// disassemble
921-
let (mut sub_apps, _, _) = app.into_parts();
947+
let (mut sub_apps, mut tls, send, recv, _) = app.into_parts();
922948

923949
#[cfg(not(target_arch = "wasm32"))]
924950
{
@@ -955,6 +981,8 @@ fn run_once(mut app: App) {
955981
{
956982
sub_apps.update();
957983
}
984+
985+
tls.clear();
958986
}
959987

960988
/// An event that indicates the [`App`] should exit. If one or more of these are present at the

crates/bevy_app/src/events.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,21 @@ impl ThreadLocalTaskSender for AppEventSender {
4141
&mut self,
4242
task: ThreadLocalTask,
4343
) -> Result<(), ThreadLocalTaskSendError<ThreadLocalTask>> {
44-
self.send(AppEvent::Task(task)).map_err(|error| {
45-
let AppEvent::Task(task) = error.0 else {
46-
unreachable!()
47-
};
48-
ThreadLocalTaskSendError(task)
49-
})
44+
#[cfg(not(target_arch = "wasm32"))]
45+
{
46+
self.send(AppEvent::Task(task)).map_err(|error| {
47+
let AppEvent::Task(task) = error.0 else {
48+
unreachable!()
49+
};
50+
ThreadLocalTaskSendError(task)
51+
})
52+
}
53+
54+
#[cfg(target_arch = "wasm32")]
55+
{
56+
// wasm builds should always access TLS directly
57+
unreachable!("currently, only single-threaded wasm is supported")
58+
}
5059
}
5160
}
5261

crates/bevy_app/src/schedule_runner.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{app_thread_channel, App, AppEvent, AppExit, Plugin, PluginsState, SubApps};
1+
use crate::{App, AppEvent, AppExit, Plugin, PluginsState, SubApps};
22
use bevy_ecs::event::{Events, ManualEventReader};
33
use bevy_utils::{Duration, Instant};
44

@@ -66,14 +66,6 @@ impl Plugin for ScheduleRunnerPlugin {
6666
fn build(&self, app: &mut App) {
6767
let run_mode = self.run_mode;
6868
app.set_runner(move |mut app: App| {
69-
// TODO: rework app setup
70-
// create channel
71-
let (send, recv) = app_thread_channel();
72-
// insert channel
73-
app.sub_apps.iter_mut().for_each(|sub_app| {
74-
app.tls.insert_channel(sub_app.world_mut(), send.clone());
75-
});
76-
7769
// wait for plugins to finish setting up
7870
let plugins_state = app.plugins_state();
7971
if plugins_state != PluginsState::Cleaned {
@@ -116,7 +108,7 @@ impl Plugin for ScheduleRunnerPlugin {
116108
};
117109

118110
// disassemble
119-
let (mut sub_apps, _, _) = app.into_parts();
111+
let (mut sub_apps, mut tls, send, recv, _) = app.into_parts();
120112

121113
#[cfg(not(target_arch = "wasm32"))]
122114
{
@@ -152,6 +144,8 @@ impl Plugin for ScheduleRunnerPlugin {
152144
}
153145
}
154146
}
147+
148+
tls.clear();
155149
}
156150

157151
#[cfg(target_arch = "wasm32")]

crates/bevy_ecs/src/storage/resource_non_send.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::any::TypeId;
22
use std::cell::RefCell;
33
use std::marker::PhantomData;
4+
use std::mem;
5+
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
6+
use std::sync::mpsc::sync_channel;
47

58
use bevy_ptr::{OwningPtr, Ptr};
69

@@ -337,7 +340,8 @@ pub type ThreadLocalTask = Box<dyn FnOnce() + Send + 'static>;
337340
#[derive(Debug)]
338341
pub struct ThreadLocalTaskSendError<T>(pub T);
339342

340-
/// Channel for sending [`ThreadLocalTask`] instances.
343+
/// Sends [`ThreadLocalTask`].
344+
// TODO: can remove this trait if `bevy_app` changes from its own crate to a `bevy_ecs` feature
341345
pub trait ThreadLocalTaskSender: Send + 'static {
342346
/// Attempts to send a task over this channel, returning it back if it could not be sent.
343347
fn send_task(
@@ -353,11 +357,7 @@ struct ThreadLocalChannel {
353357
sender: Box<dyn ThreadLocalTaskSender>,
354358
}
355359

356-
// SAFETY: The pointer to the thread-local storage is only dereferenced in its owning thread.
357-
unsafe impl Send for ThreadLocalChannel {}
358-
359-
// SAFETY: The pointer to the thread-local storage is only dereferenced in its owning thread.
360-
// Likewise, all operations require an exclusive reference, so there can be no races.
360+
// SAFETY: All operations require an exclusive reference, so there can be no races.
361361
unsafe impl Sync for ThreadLocalChannel {}
362362

363363
/// A guard to access [`ThreadLocals`].
@@ -484,7 +484,7 @@ impl ThreadLocal<'_, '_> {
484484

485485
TLS.with_borrow_mut(|tls| {
486486
tls.update_change_tick();
487-
let saved = std::mem::replace(&mut tls.last_tick, *self.last_run);
487+
let saved = mem::replace(&mut tls.last_tick, *self.last_run);
488488
let result = f(tls);
489489
tls.last_tick = saved;
490490
*self.last_run = tls.curr_tick;
@@ -502,15 +502,13 @@ impl ThreadLocal<'_, '_> {
502502
};
503503

504504
let system_tick = *self.last_run;
505-
let (result_tx, result_rx) = std::sync::mpsc::sync_channel(1);
505+
let (result_tx, result_rx) = sync_channel(1);
506506
let task = move || {
507507
TLS.with_borrow_mut(|tls| {
508508
tls.update_change_tick();
509-
let saved = std::mem::replace(&mut tls.last_tick, system_tick);
509+
let saved = mem::replace(&mut tls.last_tick, system_tick);
510510
// we want to propagate to caller instead of panicking in the main thread
511-
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
512-
(f(tls), tls.curr_tick)
513-
}));
511+
let result = catch_unwind(AssertUnwindSafe(|| (f(tls), tls.curr_tick)));
514512
tls.last_tick = saved;
515513
result_tx.send(result).unwrap();
516514
});
@@ -519,7 +517,7 @@ impl ThreadLocal<'_, '_> {
519517
let task: Box<dyn FnOnce() + Send> = Box::new(task);
520518
// SAFETY: This function will block the calling thread until `f` completes,
521519
// so any captured references in `f` will remain valid until then.
522-
let task: Box<dyn FnOnce() + Send + 'static> = unsafe { std::mem::transmute(task) };
520+
let task: Box<dyn FnOnce() + Send + 'static> = unsafe { mem::transmute(task) };
523521

524522
// Send task to the main thread.
525523
sender
@@ -533,7 +531,7 @@ impl ThreadLocal<'_, '_> {
533531
result
534532
}
535533
Err(payload) => {
536-
std::panic::resume_unwind(payload);
534+
resume_unwind(payload);
537535
}
538536
}
539537
}

0 commit comments

Comments
 (0)