|
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 | +}; |
2 | 5 | pub use bevy_derive::AppLabel;
|
3 | 6 | use bevy_ecs::schedule::ScheduleBuildSettings;
|
4 | 7 | use bevy_ecs::{
|
5 | 8 | prelude::*,
|
6 | 9 | schedule::{IntoSystemConfigs, IntoSystemSetConfigs, ScheduleLabel},
|
| 10 | + storage::ThreadLocalStorage, |
7 | 11 | };
|
| 12 | + |
8 | 13 | #[cfg(feature = "trace")]
|
9 | 14 | use bevy_utils::tracing::info_span;
|
10 | 15 | use bevy_utils::{tracing::debug, HashMap};
|
@@ -50,7 +55,10 @@ pub(crate) enum AppError {
|
50 | 55 | /// }
|
51 | 56 | /// ```
|
52 | 57 | pub struct App {
|
53 |
| - pub(crate) sub_apps: SubApps, |
| 58 | + #[doc(hidden)] |
| 59 | + pub sub_apps: SubApps, |
| 60 | + #[doc(hidden)] |
| 61 | + pub tls: ThreadLocalStorage, |
54 | 62 | /// The function that will manage the app's lifecycle.
|
55 | 63 | ///
|
56 | 64 | /// Bevy provides the [`WinitPlugin`] and [`ScheduleRunnerPlugin`] for windowed and headless
|
@@ -106,17 +114,89 @@ impl App {
|
106 | 114 | main: SubApp::new(),
|
107 | 115 | sub_apps: HashMap::new(),
|
108 | 116 | },
|
| 117 | + tls: ThreadLocalStorage::new(), |
109 | 118 | runner: Some(Box::new(run_once)),
|
110 | 119 | }
|
111 | 120 | }
|
112 | 121 |
|
| 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 | + |
113 | 146 | /// Runs the default schedules of all sub-apps (starting with the "main" app) once.
|
114 | 147 | pub fn update(&mut self) {
|
115 | 148 | if self.is_building_plugins() {
|
116 | 149 | panic!("App::update() was called while a plugin was building.");
|
117 | 150 | }
|
118 | 151 |
|
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); |
120 | 200 | }
|
121 | 201 |
|
122 | 202 | /// Runs the [`App`] by calling its [runner](Self::set_runner).
|
@@ -387,30 +467,31 @@ impl App {
|
387 | 467 | /// of the same type.
|
388 | 468 | ///
|
389 | 469 | /// There is also an [`init_non_send_resource`](Self::init_non_send_resource) for
|
390 |
| - /// resources that implement [`Default`] |
| 470 | + /// resources that implement [`Default`]. |
391 | 471 | ///
|
392 | 472 | /// # Examples
|
393 | 473 | ///
|
394 | 474 | /// ```
|
395 | 475 | /// # use bevy_app::prelude::*;
|
396 | 476 | /// # use bevy_ecs::prelude::*;
|
397 | 477 | /// #
|
| 478 | + /// #[derive(ThreadLocalResource)] |
398 | 479 | /// struct MyCounter {
|
399 | 480 | /// counter: usize,
|
400 | 481 | /// }
|
401 | 482 | ///
|
402 | 483 | /// App::new()
|
403 | 484 | /// .insert_non_send_resource(MyCounter { counter: 0 });
|
404 | 485 | /// ```
|
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); |
407 | 488 | self
|
408 | 489 | }
|
409 | 490 |
|
410 | 491 | /// Inserts the [`!Send`](Send) resource into the app, initialized with its default value,
|
411 | 492 | /// 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>(); |
414 | 495 | self
|
415 | 496 | }
|
416 | 497 |
|
@@ -692,13 +773,55 @@ impl App {
|
692 | 773 | type RunnerFn = Box<dyn FnOnce(App)>;
|
693 | 774 |
|
694 | 775 | 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 |
695 | 789 | while !app.is_ready() {
|
696 | 790 | #[cfg(not(target_arch = "wasm32"))]
|
697 | 791 | bevy_tasks::tick_global_task_pools_on_main_thread();
|
698 | 792 | }
|
699 | 793 | app.finish();
|
700 | 794 | 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 | + } |
702 | 825 | }
|
703 | 826 |
|
704 | 827 | /// An event that indicates the [`App`] should exit. If one or more of these are present at the
|
|
0 commit comments