Skip to content

Commit b4420b3

Browse files
committed
bevy_app changes
1 parent 9e85c1c commit b4420b3

File tree

4 files changed

+279
-71
lines changed

4 files changed

+279
-71
lines changed

crates/bevy_app/src/app.rs

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
use crate::{Main, MainSchedulePlugin, PlaceholderPlugin, Plugin, Plugins, SubApp, SubApps};
1+
use crate::{
2+
app_thread_channel, AppEvent, Main, MainSchedulePlugin, PlaceholderPlugin, Plugin, Plugins,
3+
SubApp, SubApps,
4+
};
25
pub use bevy_derive::AppLabel;
36
use bevy_ecs::schedule::ScheduleBuildSettings;
47
use bevy_ecs::{
58
prelude::*,
69
schedule::{IntoSystemConfigs, IntoSystemSetConfigs, ScheduleLabel},
10+
storage::ThreadLocalStorage,
711
};
12+
813
#[cfg(feature = "trace")]
914
use bevy_utils::tracing::info_span;
1015
use bevy_utils::{tracing::debug, HashMap};
@@ -50,7 +55,10 @@ pub(crate) enum AppError {
5055
/// }
5156
/// ```
5257
pub struct App {
53-
pub(crate) sub_apps: SubApps,
58+
#[doc(hidden)]
59+
pub sub_apps: SubApps,
60+
#[doc(hidden)]
61+
pub tls: ThreadLocalStorage,
5462
/// The function that will manage the app's lifecycle.
5563
///
5664
/// Bevy provides the [`WinitPlugin`] and [`ScheduleRunnerPlugin`] for windowed and headless
@@ -106,17 +114,89 @@ impl App {
106114
main: SubApp::new(),
107115
sub_apps: HashMap::new(),
108116
},
117+
tls: ThreadLocalStorage::new(),
109118
runner: Some(Box::new(run_once)),
110119
}
111120
}
112121

122+
/// Disassembles the [`App`] and returns its individual parts.
123+
pub fn into_parts(self) -> (SubApps, ThreadLocalStorage, Option<RunnerFn>) {
124+
let Self {
125+
sub_apps,
126+
tls,
127+
runner,
128+
} = self;
129+
130+
(sub_apps, tls, runner)
131+
}
132+
133+
/// Returns an [`App`] assembled from the given individual parts.
134+
pub fn from_parts(
135+
sub_apps: SubApps,
136+
tls: ThreadLocalStorage,
137+
runner: Option<RunnerFn>,
138+
) -> Self {
139+
App {
140+
sub_apps,
141+
tls,
142+
runner,
143+
}
144+
}
145+
113146
/// Runs the default schedules of all sub-apps (starting with the "main" app) once.
114147
pub fn update(&mut self) {
115148
if self.is_building_plugins() {
116149
panic!("App::update() was called while a plugin was building.");
117150
}
118151

119-
self.sub_apps.update();
152+
// disassemble
153+
let (mut sub_apps, tls, runner) = std::mem::take(self).into_parts();
154+
155+
// create channel
156+
let (send, recv) = app_thread_channel();
157+
158+
// insert channel
159+
sub_apps
160+
.iter_mut()
161+
.for_each(|sub_app| tls.insert_channel(sub_app.world_mut(), send.clone()));
162+
163+
#[cfg(not(target_arch = "wasm32"))]
164+
{
165+
// Move sub-apps to another thread and run an event loop in this thread.
166+
let thread = std::thread::spawn(move || {
167+
sub_apps.update();
168+
send.send(AppEvent::Exit(Box::new(sub_apps))).unwrap();
169+
});
170+
171+
loop {
172+
let event = recv.recv().unwrap();
173+
match event {
174+
AppEvent::Task(f) => {
175+
f(&mut tls.lock());
176+
}
177+
AppEvent::Exit(boxed) => {
178+
// SAFETY: `Box::<T>::into_raw` returns a pointer that is properly aligned
179+
// and points to an initialized value of `T`.
180+
sub_apps = unsafe { std::ptr::read(Box::into_raw(boxed)) };
181+
thread.join().unwrap();
182+
break;
183+
}
184+
}
185+
}
186+
}
187+
188+
#[cfg(target_arch = "wasm32")]
189+
{
190+
sub_apps.update();
191+
}
192+
193+
// remove channel
194+
sub_apps
195+
.iter_mut()
196+
.for_each(|sub_app| tls.remove_channel(sub_app.world_mut()));
197+
198+
// reassemble
199+
*self = App::from_parts(sub_apps, tls, runner);
120200
}
121201

122202
/// Runs the [`App`] by calling its [runner](Self::set_runner).
@@ -387,30 +467,31 @@ impl App {
387467
/// of the same type.
388468
///
389469
/// There is also an [`init_non_send_resource`](Self::init_non_send_resource) for
390-
/// resources that implement [`Default`]
470+
/// resources that implement [`Default`].
391471
///
392472
/// # Examples
393473
///
394474
/// ```
395475
/// # use bevy_app::prelude::*;
396476
/// # use bevy_ecs::prelude::*;
397477
/// #
478+
/// #[derive(ThreadLocalResource)]
398479
/// struct MyCounter {
399480
/// counter: usize,
400481
/// }
401482
///
402483
/// App::new()
403484
/// .insert_non_send_resource(MyCounter { counter: 0 });
404485
/// ```
405-
pub fn insert_non_send_resource<R: 'static>(&mut self, resource: R) -> &mut Self {
406-
self.world_mut().insert_non_send_resource(resource);
486+
pub fn insert_non_send_resource<R: ThreadLocalResource>(&mut self, resource: R) -> &mut Self {
487+
self.tls.lock().insert_resource(resource);
407488
self
408489
}
409490

410491
/// Inserts the [`!Send`](Send) resource into the app, initialized with its default value,
411492
/// if there is no existing instance of `R`.
412-
pub fn init_non_send_resource<R: 'static + Default>(&mut self) -> &mut Self {
413-
self.world_mut().init_non_send_resource::<R>();
493+
pub fn init_non_send_resource<R: ThreadLocalResource + Default>(&mut self) -> &mut Self {
494+
self.tls.lock().init_resource::<R>();
414495
self
415496
}
416497

@@ -692,13 +773,55 @@ impl App {
692773
type RunnerFn = Box<dyn FnOnce(App)>;
693774

694775
fn run_once(mut app: App) {
776+
if app.is_building_plugins() {
777+
panic!("App::update() was called while a plugin was building.");
778+
}
779+
780+
// TODO: rework app setup
781+
// create channel
782+
let (send, recv) = app_thread_channel();
783+
// insert channel
784+
app.sub_apps
785+
.iter_mut()
786+
.for_each(|sub_app| app.tls.insert_channel(sub_app.world_mut(), send.clone()));
787+
788+
// wait for plugins to finish setting up
695789
while !app.is_ready() {
696790
#[cfg(not(target_arch = "wasm32"))]
697791
bevy_tasks::tick_global_task_pools_on_main_thread();
698792
}
699793
app.finish();
700794
app.cleanup();
701-
app.update();
795+
796+
// disassemble
797+
let (mut sub_apps, tls, _) = app.into_parts();
798+
799+
#[cfg(not(target_arch = "wasm32"))]
800+
{
801+
// Move sub-apps to another thread and run an event loop in this thread.
802+
let thread = std::thread::spawn(move || {
803+
sub_apps.update();
804+
send.send(AppEvent::Exit(Box::new(sub_apps))).unwrap();
805+
});
806+
807+
loop {
808+
let event = recv.recv().unwrap();
809+
match event {
810+
AppEvent::Task(f) => {
811+
f(&mut tls.lock());
812+
}
813+
AppEvent::Exit(_) => {
814+
thread.join().unwrap();
815+
break;
816+
}
817+
}
818+
}
819+
}
820+
821+
#[cfg(target_arch = "wasm32")]
822+
{
823+
sub_apps.update();
824+
}
702825
}
703826

704827
/// 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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use bevy_ecs::storage::{ThreadLocalTask, ThreadLocalTaskSendError, ThreadLocalTaskSender};
2+
use std::sync::mpsc::{channel, Receiver, Sender};
3+
4+
/// Events an [`App`](crate::App) can send to another thread when using a multi-threaded runner.
5+
pub enum AppEvent {
6+
/// The app has sent a task with access to [`ThreadLocals`](bevy_ecs::prelude::ThreadLocals).
7+
Task(ThreadLocalTask),
8+
/// The app has exited.
9+
Exit(Box<crate::SubApps>),
10+
}
11+
12+
/// The sender half of an [`app_thread_channel`].
13+
#[derive(Clone)]
14+
pub struct AppEventSender(Sender<AppEvent>);
15+
16+
/// Constructs a new asynchronous channel for passing [`AppEvent`] instances
17+
/// to an event loop and returns the sender and receiver halves.
18+
pub fn app_thread_channel() -> (AppEventSender, AppEventReceiver) {
19+
let (send, recv) = channel();
20+
(AppEventSender(send), AppEventReceiver(recv))
21+
}
22+
23+
impl std::ops::Deref for AppEventSender {
24+
type Target = Sender<AppEvent>;
25+
fn deref(&self) -> &Self::Target {
26+
&self.0
27+
}
28+
}
29+
30+
impl std::ops::DerefMut for AppEventSender {
31+
fn deref_mut(&mut self) -> &mut Self::Target {
32+
&mut self.0
33+
}
34+
}
35+
36+
impl ThreadLocalTaskSender for AppEventSender {
37+
fn send_task(
38+
&mut self,
39+
task: ThreadLocalTask,
40+
) -> Result<(), ThreadLocalTaskSendError<ThreadLocalTask>> {
41+
self.send(AppEvent::Task(task)).map_err(|error| {
42+
let AppEvent::Task(task) = error.0 else {
43+
unreachable!()
44+
};
45+
ThreadLocalTaskSendError(task)
46+
})
47+
}
48+
}
49+
50+
/// The receiver-half of an [`app_thread_channel`].
51+
pub struct AppEventReceiver(Receiver<AppEvent>);
52+
53+
impl std::ops::Deref for AppEventReceiver {
54+
type Target = Receiver<AppEvent>;
55+
fn deref(&self) -> &Self::Target {
56+
&self.0
57+
}
58+
}
59+
60+
impl std::ops::DerefMut for AppEventReceiver {
61+
fn deref_mut(&mut self) -> &mut Self::Target {
62+
&mut self.0
63+
}
64+
}

crates/bevy_app/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![allow(clippy::type_complexity)]
55

66
mod app;
7+
mod events;
78
mod main_schedule;
89
mod plugin;
910
mod plugin_group;
@@ -15,6 +16,7 @@ pub mod ci_testing;
1516

1617
pub use app::*;
1718
pub use bevy_derive::DynamicPlugin;
19+
pub use events::*;
1820
pub use main_schedule::*;
1921
pub use plugin::*;
2022
pub use plugin_group::*;

0 commit comments

Comments
 (0)