11use crate :: {
2- app:: { App , AppExit } ,
2+ app:: { App , AppExit , AppThreadEvent , SubApps } ,
33 plugin:: Plugin ,
44} ;
55use bevy_ecs:: event:: { Events , ManualEventReader } ;
6+ use bevy_ecs:: storage:: ThreadLocalAccessor ;
67use bevy_utils:: { Duration , Instant } ;
78
89#[ cfg( target_arch = "wasm32" ) ]
910use std:: { cell:: RefCell , rc:: Rc } ;
1011#[ cfg( target_arch = "wasm32" ) ]
1112use wasm_bindgen:: { prelude:: * , JsCast } ;
1213
13- /// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule::Schedule).
14- ///
15- /// It is used in the [`ScheduleRunnerPlugin`].
14+ /// Determines how frequently the [`App`] should be updated by the [`ScheduleRunnerPlugin`].
1615#[ derive( Copy , Clone , Debug ) ]
1716pub enum RunMode {
18- /// Indicates that the [`App`]'s schedule should run repeatedly.
17+ /// The [`App`] will update once.
18+ Once ,
19+ /// The [`App`] will update over and over, until an [`AppExit`] event appears.
1920 Loop {
20- /// The minimum [`Duration`] to wait after a [`Schedule`](bevy_ecs::schedule::Schedule)
21- /// has completed before repeating. A value of [`None`] will not wait.
22- wait : Option < Duration > ,
21+ /// The minimum time from the start of one update to the next.
22+ ///
23+ /// **Note:** This has no upper limit, but the [`App`] will hang if you set this too high.
24+ wait : Duration ,
2325 } ,
24- /// Indicates that the [`App`]'s schedule should run only once.
25- Once ,
2626}
2727
2828impl Default for RunMode {
2929 fn default ( ) -> Self {
30- RunMode :: Loop { wait : None }
30+ RunMode :: Loop {
31+ wait : Duration :: ZERO ,
32+ }
3133 }
3234}
3335
34- /// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given
35- /// [`RunMode`].
36+ /// Runs an [`App`] according to the selected [`RunMode`].
3637///
37- /// [`ScheduleRunnerPlugin`] is included in the
38- /// [`MinimalPlugins`](https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html) plugin group.
38+ /// This plugin is included in the [`MinimalPlugins`] group, but **not** included in the
39+ /// [`DefaultPlugins`] group. [`DefaultPlugins`] assumes the [`App`] will render to a window,
40+ /// so it comes with the [`WinitPlugin`] instead.
3941///
40- /// [`ScheduleRunnerPlugin`] is *not* included in the
41- /// [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html) plugin group
42- /// which assumes that the [`Schedule`](bevy_ecs::schedule::Schedule) will be executed by other means:
43- /// typically, the `winit` event loop
44- /// (see [`WinitPlugin`](https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html))
45- /// executes the schedule making [`ScheduleRunnerPlugin`] unnecessary.
42+ /// [`DefaultPlugins`]: https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html
43+ /// [`MinimalPlugins`]: https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html
44+ /// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html
4645#[ derive( Default ) ]
4746pub struct ScheduleRunnerPlugin {
48- /// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly .
47+ /// Determines how frequently the [`App`] should update .
4948 pub run_mode : RunMode ,
5049}
5150
5251impl ScheduleRunnerPlugin {
5352 /// See [`RunMode::Once`].
5453 pub fn run_once ( ) -> Self {
55- ScheduleRunnerPlugin {
54+ Self {
5655 run_mode : RunMode :: Once ,
5756 }
5857 }
5958
6059 /// See [`RunMode::Loop`].
61- pub fn run_loop ( wait_duration : Duration ) -> Self {
62- ScheduleRunnerPlugin {
63- run_mode : RunMode :: Loop {
64- wait : Some ( wait_duration) ,
65- } ,
60+ pub fn run_loop ( wait : Duration ) -> Self {
61+ Self {
62+ run_mode : RunMode :: Loop { wait } ,
6663 }
6764 }
6865}
@@ -71,61 +68,77 @@ impl Plugin for ScheduleRunnerPlugin {
7168 fn build ( & self , app : & mut App ) {
7269 let run_mode = self . run_mode ;
7370 app. set_runner ( move |mut app : App | {
74- while !app. ready ( ) {
71+ while !app. is_ready ( ) {
7572 #[ cfg( not( target_arch = "wasm32" ) ) ]
7673 bevy_tasks:: tick_global_task_pools_on_main_thread ( ) ;
7774 }
7875 app. finish ( ) ;
7976 app. cleanup ( ) ;
8077
81- let mut app_exit_event_reader = ManualEventReader :: < AppExit > :: default ( ) ;
78+ let mut exit_event_reader = ManualEventReader :: < AppExit > :: default ( ) ;
8279 match run_mode {
8380 RunMode :: Once => {
8481 app. update ( ) ;
8582 }
8683 RunMode :: Loop { wait } => {
87- let mut tick = move |app : & mut App ,
88- wait : Option < Duration > |
89- -> Result < Option < Duration > , AppExit > {
84+ let mut update = move |sub_apps : & mut SubApps | -> Result < Duration , AppExit > {
9085 let start_time = Instant :: now ( ) ;
86+ sub_apps. update ( ) ;
87+ let end_time = Instant :: now ( ) ;
9188
92- if let Some ( app_exit_events ) =
93- app . world . get_resource_mut :: < Events < AppExit > > ( )
89+ if let Some ( exit_events ) =
90+ sub_apps . main . world . get_resource_mut :: < Events < AppExit > > ( )
9491 {
95- if let Some ( exit) = app_exit_event_reader. iter ( & app_exit_events) . last ( )
96- {
92+ if let Some ( exit) = exit_event_reader. iter ( & exit_events) . last ( ) {
9793 return Err ( exit. clone ( ) ) ;
9894 }
9995 }
10096
101- app. update ( ) ;
102-
103- if let Some ( app_exit_events) =
104- app. world . get_resource_mut :: < Events < AppExit > > ( )
105- {
106- if let Some ( exit) = app_exit_event_reader. iter ( & app_exit_events) . last ( )
107- {
108- return Err ( exit. clone ( ) ) ;
109- }
97+ let elapsed = end_time - start_time;
98+ if elapsed < wait {
99+ return Ok ( wait - elapsed) ;
110100 }
111101
112- let end_time = Instant :: now ( ) ;
102+ Ok ( Duration :: ZERO )
103+ } ;
113104
114- if let Some ( wait) = wait {
115- let exe_time = end_time - start_time;
116- if exe_time < wait {
117- return Ok ( Some ( wait - exe_time) ) ;
118- }
119- }
105+ let ( mut sub_apps, mut tls, _) = app. into_parts ( ) ;
120106
121- Ok ( None )
122- } ;
107+ // create event loop channel
108+ let ( send, recv) = std:: sync:: mpsc:: channel ( ) ;
109+
110+ // insert TLS accessor
111+ sub_apps. for_each ( |sub_app| {
112+ // SAFETY: `tls` is not moved or dropped until `access` has been dropped.
113+ let access = unsafe {
114+ ThreadLocalAccessor :: new ( std:: ptr:: addr_of_mut!( tls) , send. clone ( ) )
115+ } ;
116+ sub_app. world . insert_resource ( access) ;
117+ } ) ;
123118
124119 #[ cfg( not( target_arch = "wasm32" ) ) ]
125120 {
126- while let Ok ( delay) = tick ( & mut app, wait) {
127- if let Some ( delay) = delay {
128- std:: thread:: sleep ( delay) ;
121+ // Move sub-apps to another thread and run an event loop in this thread.
122+ let handle = std:: thread:: spawn ( move || {
123+ while let Ok ( sleep) = update ( & mut sub_apps) {
124+ if !sleep. is_zero ( ) {
125+ std:: thread:: sleep ( sleep) ;
126+ }
127+ }
128+
129+ send. send ( AppThreadEvent :: Exit ( sub_apps) ) ;
130+ } ) ;
131+
132+ loop {
133+ let event = recv. recv ( ) . unwrap ( ) ;
134+ match event {
135+ AppThreadEvent :: RunTask ( f) => {
136+ f ( & mut tls) ;
137+ }
138+ AppThreadEvent :: Exit ( sub_apps) => {
139+ handle. join ( ) ;
140+ break ;
141+ }
129142 }
130143 }
131144 }
@@ -141,25 +154,32 @@ impl Plugin for ScheduleRunnerPlugin {
141154 )
142155 . expect ( "Should register `setTimeout`." ) ;
143156 }
144- let asap = Duration :: from_millis ( 1 ) ;
145157
146- let mut rc = Rc :: new ( app) ;
158+ let min_sleep = Duration :: from_millis ( 1 ) ;
159+
160+ let mut rc = Rc :: new ( sub_apps) ;
147161 let f = Rc :: new ( RefCell :: new ( None ) ) ;
148162 let g = f. clone ( ) ;
149163
150- let c = move || {
151- let mut app = Rc :: get_mut ( & mut rc) . unwrap ( ) ;
152- let delay = tick ( & mut app, wait) ;
153- match delay {
154- Ok ( delay) => {
155- set_timeout ( f. borrow ( ) . as_ref ( ) . unwrap ( ) , delay. unwrap_or ( asap) )
164+ let closure = move || {
165+ let mut sub_apps = Rc :: get_mut ( & mut rc) . unwrap ( ) ;
166+ match update ( & mut sub_apps) {
167+ Ok ( sleep) => {
168+ set_timeout ( f. borrow ( ) . as_ref ( ) . unwrap ( ) , sleep. max ( min_sleep) )
156169 }
157170 Err ( _) => { }
158171 }
159172 } ;
160- * g. borrow_mut ( ) = Some ( Closure :: wrap ( Box :: new ( c) as Box < dyn FnMut ( ) > ) ) ;
161- set_timeout ( g. borrow ( ) . as_ref ( ) . unwrap ( ) , asap) ;
173+
174+ * g. borrow_mut ( ) =
175+ Some ( Closure :: wrap ( Box :: new ( closure) as Box < dyn FnMut ( ) > ) ) ;
176+
177+ set_timeout ( g. borrow ( ) . as_ref ( ) . unwrap ( ) , min_sleep) ;
162178 } ;
179+
180+ // remove TLS accessor
181+ sub_apps
182+ . for_each ( |sub_app| sub_app. world . remove_resource :: < ThreadLocalAccessor > ( ) ) ;
163183 }
164184 }
165185 } ) ;
0 commit comments