1
1
use crate :: {
2
- app:: { App , AppExit } ,
2
+ app:: { App , AppExit , AppThreadEvent , SubApps } ,
3
3
plugin:: Plugin ,
4
4
} ;
5
5
use bevy_ecs:: event:: { Events , ManualEventReader } ;
6
+ use bevy_ecs:: storage:: ThreadLocalAccessor ;
6
7
use bevy_utils:: { Duration , Instant } ;
7
8
8
9
#[ cfg( target_arch = "wasm32" ) ]
9
10
use std:: { cell:: RefCell , rc:: Rc } ;
10
11
#[ cfg( target_arch = "wasm32" ) ]
11
12
use wasm_bindgen:: { prelude:: * , JsCast } ;
12
13
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`].
16
15
#[ derive( Copy , Clone , Debug ) ]
17
16
pub 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.
19
20
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 ,
23
25
} ,
24
- /// Indicates that the [`App`]'s schedule should run only once.
25
- Once ,
26
26
}
27
27
28
28
impl Default for RunMode {
29
29
fn default ( ) -> Self {
30
- RunMode :: Loop { wait : None }
30
+ RunMode :: Loop {
31
+ wait : Duration :: ZERO ,
32
+ }
31
33
}
32
34
}
33
35
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`].
36
37
///
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.
39
41
///
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
46
45
#[ derive( Default ) ]
47
46
pub struct ScheduleRunnerPlugin {
48
- /// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly .
47
+ /// Determines how frequently the [`App`] should update .
49
48
pub run_mode : RunMode ,
50
49
}
51
50
52
51
impl ScheduleRunnerPlugin {
53
52
/// See [`RunMode::Once`].
54
53
pub fn run_once ( ) -> Self {
55
- ScheduleRunnerPlugin {
54
+ Self {
56
55
run_mode : RunMode :: Once ,
57
56
}
58
57
}
59
58
60
59
/// 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 } ,
66
63
}
67
64
}
68
65
}
@@ -71,61 +68,77 @@ impl Plugin for ScheduleRunnerPlugin {
71
68
fn build ( & self , app : & mut App ) {
72
69
let run_mode = self . run_mode ;
73
70
app. set_runner ( move |mut app : App | {
74
- while !app. ready ( ) {
71
+ while !app. is_ready ( ) {
75
72
#[ cfg( not( target_arch = "wasm32" ) ) ]
76
73
bevy_tasks:: tick_global_task_pools_on_main_thread ( ) ;
77
74
}
78
75
app. finish ( ) ;
79
76
app. cleanup ( ) ;
80
77
81
- let mut app_exit_event_reader = ManualEventReader :: < AppExit > :: default ( ) ;
78
+ let mut exit_event_reader = ManualEventReader :: < AppExit > :: default ( ) ;
82
79
match run_mode {
83
80
RunMode :: Once => {
84
81
app. update ( ) ;
85
82
}
86
83
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 > {
90
85
let start_time = Instant :: now ( ) ;
86
+ sub_apps. update ( ) ;
87
+ let end_time = Instant :: now ( ) ;
91
88
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 > > ( )
94
91
{
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 ( ) {
97
93
return Err ( exit. clone ( ) ) ;
98
94
}
99
95
}
100
96
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) ;
110
100
}
111
101
112
- let end_time = Instant :: now ( ) ;
102
+ Ok ( Duration :: ZERO )
103
+ } ;
113
104
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 ( ) ;
120
106
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
+ } ) ;
123
118
124
119
#[ cfg( not( target_arch = "wasm32" ) ) ]
125
120
{
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
+ }
129
142
}
130
143
}
131
144
}
@@ -141,25 +154,32 @@ impl Plugin for ScheduleRunnerPlugin {
141
154
)
142
155
. expect ( "Should register `setTimeout`." ) ;
143
156
}
144
- let asap = Duration :: from_millis ( 1 ) ;
145
157
146
- let mut rc = Rc :: new ( app) ;
158
+ let min_sleep = Duration :: from_millis ( 1 ) ;
159
+
160
+ let mut rc = Rc :: new ( sub_apps) ;
147
161
let f = Rc :: new ( RefCell :: new ( None ) ) ;
148
162
let g = f. clone ( ) ;
149
163
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) )
156
169
}
157
170
Err ( _) => { }
158
171
}
159
172
} ;
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) ;
162
178
} ;
179
+
180
+ // remove TLS accessor
181
+ sub_apps
182
+ . for_each ( |sub_app| sub_app. world . remove_resource :: < ThreadLocalAccessor > ( ) ) ;
163
183
}
164
184
}
165
185
} ) ;
0 commit comments