bevy_app/schedule_runner.rs
1use crate::{
2 app::{App, AppExit},
3 plugin::Plugin,
4 PluginsState,
5};
6use bevy_utils::{Duration, Instant};
7
8#[cfg(target_arch = "wasm32")]
9use {
10 alloc::rc::Rc,
11 core::cell::RefCell,
12 wasm_bindgen::{prelude::*, JsCast},
13};
14
15/// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule::Schedule).
16///
17/// It is used in the [`ScheduleRunnerPlugin`].
18#[derive(Copy, Clone, Debug)]
19pub enum RunMode {
20 /// Indicates that the [`App`]'s schedule should run repeatedly.
21 Loop {
22 /// The minimum [`Duration`] to wait after a [`Schedule`](bevy_ecs::schedule::Schedule)
23 /// has completed before repeating. A value of [`None`] will not wait.
24 wait: Option<Duration>,
25 },
26 /// Indicates that the [`App`]'s schedule should run only once.
27 Once,
28}
29
30impl Default for RunMode {
31 fn default() -> Self {
32 RunMode::Loop { wait: None }
33 }
34}
35
36/// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given
37/// [`RunMode`].
38///
39/// [`ScheduleRunnerPlugin`] is included in the
40/// [`MinimalPlugins`](https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html) plugin group.
41///
42/// [`ScheduleRunnerPlugin`] is *not* included in the
43/// [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html) plugin group
44/// which assumes that the [`Schedule`](bevy_ecs::schedule::Schedule) will be executed by other means:
45/// typically, the `winit` event loop
46/// (see [`WinitPlugin`](https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html))
47/// executes the schedule making [`ScheduleRunnerPlugin`] unnecessary.
48#[derive(Default)]
49pub struct ScheduleRunnerPlugin {
50 /// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly.
51 pub run_mode: RunMode,
52}
53
54impl ScheduleRunnerPlugin {
55 /// See [`RunMode::Once`].
56 pub fn run_once() -> Self {
57 ScheduleRunnerPlugin {
58 run_mode: RunMode::Once,
59 }
60 }
61
62 /// See [`RunMode::Loop`].
63 pub fn run_loop(wait_duration: Duration) -> Self {
64 ScheduleRunnerPlugin {
65 run_mode: RunMode::Loop {
66 wait: Some(wait_duration),
67 },
68 }
69 }
70}
71
72impl Plugin for ScheduleRunnerPlugin {
73 fn build(&self, app: &mut App) {
74 let run_mode = self.run_mode;
75 app.set_runner(move |mut app: App| {
76 let plugins_state = app.plugins_state();
77 if plugins_state != PluginsState::Cleaned {
78 while app.plugins_state() == PluginsState::Adding {
79 #[cfg(not(target_arch = "wasm32"))]
80 bevy_tasks::tick_global_task_pools_on_main_thread();
81 }
82 app.finish();
83 app.cleanup();
84 }
85
86 match run_mode {
87 RunMode::Once => {
88 app.update();
89
90 if let Some(exit) = app.should_exit() {
91 return exit;
92 }
93
94 AppExit::Success
95 }
96 RunMode::Loop { wait } => {
97 let tick = move |app: &mut App,
98 wait: Option<Duration>|
99 -> Result<Option<Duration>, AppExit> {
100 let start_time = Instant::now();
101
102 app.update();
103
104 if let Some(exit) = app.should_exit() {
105 return Err(exit);
106 };
107
108 let end_time = Instant::now();
109
110 if let Some(wait) = wait {
111 let exe_time = end_time - start_time;
112 if exe_time < wait {
113 return Ok(Some(wait - exe_time));
114 }
115 }
116
117 Ok(None)
118 };
119
120 #[cfg(not(target_arch = "wasm32"))]
121 {
122 loop {
123 match tick(&mut app, wait) {
124 Ok(Some(delay)) => std::thread::sleep(delay),
125 Ok(None) => continue,
126 Err(exit) => return exit,
127 }
128 }
129 }
130
131 #[cfg(target_arch = "wasm32")]
132 {
133 fn set_timeout(callback: &Closure<dyn FnMut()>, dur: Duration) {
134 web_sys::window()
135 .unwrap()
136 .set_timeout_with_callback_and_timeout_and_arguments_0(
137 callback.as_ref().unchecked_ref(),
138 dur.as_millis() as i32,
139 )
140 .expect("Should register `setTimeout`.");
141 }
142 let asap = Duration::from_millis(1);
143
144 let exit = Rc::new(RefCell::new(AppExit::Success));
145 let closure_exit = exit.clone();
146
147 let mut app = Rc::new(app);
148 let moved_tick_closure = Rc::new(RefCell::new(None));
149 let base_tick_closure = moved_tick_closure.clone();
150
151 let tick_app = move || {
152 let app = Rc::get_mut(&mut app).unwrap();
153 let delay = tick(app, wait);
154 match delay {
155 Ok(delay) => set_timeout(
156 moved_tick_closure.borrow().as_ref().unwrap(),
157 delay.unwrap_or(asap),
158 ),
159 Err(code) => {
160 closure_exit.replace(code);
161 }
162 }
163 };
164 *base_tick_closure.borrow_mut() =
165 Some(Closure::wrap(Box::new(tick_app) as Box<dyn FnMut()>));
166 set_timeout(base_tick_closure.borrow().as_ref().unwrap(), asap);
167
168 exit.take()
169 }
170 }
171 }
172 });
173 }
174}