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}