bevy_app/
schedule_runner.rs

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