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