Skip to main content

fyrox_impl/engine/
executor.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Executor is a small wrapper that manages plugins and scripts for your game.
22
23use crate::engine::ApplicationLoopController;
24use crate::scene::Scene;
25use crate::{
26    asset::manager::ResourceManager,
27    core::{
28        instant::Instant,
29        log::{Log, MessageKind},
30        task::TaskPool,
31    },
32    engine::{
33        Engine, EngineInitParams, GraphicsContext, GraphicsContextParams, SerializationContext,
34    },
35    event::{Event, WindowEvent},
36    event_loop::{ControlFlow, EventLoop},
37    plugin::Plugin,
38    utils::translate_event,
39    window::WindowAttributes,
40};
41use clap::Parser;
42use fyrox_core::pool::Handle;
43use fyrox_resource::io::FsResourceIo;
44use fyrox_ui::constructor::new_widget_constructor_container;
45use std::cell::Cell;
46use std::time::Duration;
47use std::{
48    ops::{Deref, DerefMut},
49    sync::Arc,
50};
51use winit::event_loop::ActiveEventLoop;
52
53#[derive(Parser, Debug, Default)]
54#[clap(author, version, about, long_about = None)]
55struct Args {
56    #[clap(short, long, default_value = None)]
57    override_scene: Option<String>,
58}
59
60/// Executor is a small wrapper that manages plugins and scripts for your game.
61pub struct Executor {
62    event_loop: Option<EventLoop<()>>,
63    engine: Engine,
64    desired_update_rate: f32,
65    throttle_threshold: f32,
66    throttle_frame_interval: usize,
67    resource_hot_reloading: bool,
68}
69
70impl Deref for Executor {
71    type Target = Engine;
72
73    fn deref(&self) -> &Self::Target {
74        &self.engine
75    }
76}
77
78impl DerefMut for Executor {
79    fn deref_mut(&mut self) -> &mut Self::Target {
80        &mut self.engine
81    }
82}
83
84impl Executor {
85    /// Default update rate in frames per second.
86    pub const DEFAULT_UPDATE_RATE: f32 = 60.0;
87    /// Default time step (in seconds).
88    pub const DEFAULT_TIME_STEP: f32 = 1.0 / Self::DEFAULT_UPDATE_RATE;
89
90    /// Creates new game executor using specified set of parameters. Much more flexible version of
91    /// [`Executor::new`]. To run the engine in headless mode, pass [`None`] to the `event_loop`
92    /// argument.
93    pub fn from_params(
94        event_loop: Option<EventLoop<()>>,
95        graphics_context_params: GraphicsContextParams,
96    ) -> Self {
97        let serialization_context = Arc::new(SerializationContext::new());
98        let task_pool = Arc::new(TaskPool::new());
99        let io = Arc::new(FsResourceIo);
100        let engine = Engine::new(EngineInitParams {
101            graphics_context_params,
102            resource_manager: ResourceManager::new(io, task_pool.clone()),
103            serialization_context,
104            task_pool,
105            widget_constructors: Arc::new(new_widget_constructor_container()),
106            dyn_type_constructors: Default::default(),
107        })
108        .unwrap();
109
110        Self {
111            event_loop,
112            engine,
113            desired_update_rate: Self::DEFAULT_UPDATE_RATE,
114            throttle_threshold: 2.0 * Self::DEFAULT_TIME_STEP,
115            throttle_frame_interval: 5,
116            resource_hot_reloading: true,
117        }
118    }
119
120    /// Creates new game executor using default window and with vsync turned on. For more flexible
121    /// way to create an executor see [`Executor::from_params`]. To run the engine in headless mode,
122    /// pass [`None`] to the `event_loop` argument.
123    pub fn new(event_loop: Option<EventLoop<()>>) -> Self {
124        let mut window_attributes = WindowAttributes::default();
125        window_attributes.resizable = true;
126        window_attributes.title = "Fyrox Game".to_string();
127
128        Self::from_params(
129            event_loop,
130            GraphicsContextParams {
131                window_attributes,
132                vsync: true,
133                msaa_sample_count: None,
134                graphics_server_constructor: Default::default(),
135                named_objects: false,
136            },
137        )
138    }
139
140    /// Enables or disables hot reloading of changed resources (such as textures, shaders, scenes, etc.).
141    /// Enabled by default.
142    ///
143    /// # Platform-specific
144    ///
145    /// Does nothing on Android and WebAssembly, because these OSes does not have rich file system
146    /// as PC.
147    pub fn set_resource_hot_reloading_enabled(&mut self, enabled: bool) {
148        self.resource_hot_reloading = enabled;
149    }
150
151    /// Returns `true` if hot reloading of changed resources is enabled, `false` - otherwise.
152    pub fn is_resource_hot_reloading_enabled(&self) -> bool {
153        self.resource_hot_reloading
154    }
155
156    /// Sets the desired throttle threshold (in seconds), at which the engine will stop trying to
157    /// stabilize the update rate of the game logic and will increase the time step. This option
158    /// could be useful to prevent potential hang up of the game if its logic or rendering takes too
159    /// much time at each frame. The default value is two default time steps (33.3(3) milliseconds
160    /// or 0.0333(3) seconds).
161    ///
162    /// ## Important notes
163    ///
164    /// Physics could suffer from variable time step which may result in objects falling through the
165    /// ground and some other nasty things. Throttle threshold should be at reasonably high levels
166    /// (usually 2x-3x of the fixed time step).
167    pub fn set_throttle_threshold(&mut self, threshold: f32) {
168        self.throttle_threshold = threshold.max(0.001);
169    }
170
171    /// Returns current throttle threshold. See [`Self::set_throttle_threshold`] docs for more info.
172    pub fn throttle_threshold(&self) -> f32 {
173        self.throttle_threshold
174    }
175
176    /// Sets the amount of frames (consecutive) that will be allowed to have lag spikes and the engine
177    /// won't modify time step for internal update calls during such interval. This setting allows the
178    /// engine to ignore small lag spikes and do not fast-forward game logic using variable time step.
179    /// Variable time step could be bad for physics, which may result in objects falling through the
180    /// ground, etc. Default is 5 frames.
181    pub fn set_throttle_frame_interval(&mut self, interval: usize) {
182        self.throttle_frame_interval = interval;
183    }
184
185    /// Returns current throttle frame interval. See [`Self::set_throttle_frame_interval`] docs for
186    /// more info.
187    pub fn throttle_frame_interval(&self) -> usize {
188        self.throttle_frame_interval
189    }
190
191    /// Sets the desired update rate in frames per second.
192    pub fn set_desired_update_rate(&mut self, update_rate: f32) {
193        self.desired_update_rate = update_rate.abs();
194    }
195
196    /// Returns desired update rate in frames per second.
197    pub fn desired_update_rate(&self) -> f32 {
198        self.desired_update_rate
199    }
200
201    /// Adds new plugin to the executor, the plugin will be enabled only on [`Executor::run`].
202    pub fn add_plugin<P>(&mut self, plugin: P)
203    where
204        P: Plugin + 'static,
205    {
206        self.engine.add_plugin(plugin)
207    }
208
209    /// Runs the executor - starts your game.
210    pub fn run(self) {
211        Log::info("Initializing resource registry.");
212        self.engine.resource_manager.update_or_load_registry();
213
214        let engine = self.engine;
215        let event_loop = self.event_loop;
216        let throttle_threshold = self.throttle_threshold;
217        let throttle_frame_interval = self.throttle_frame_interval;
218
219        if self.resource_hot_reloading {
220            #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
221            {
222                use crate::core::watcher::FileSystemWatcher;
223                use std::time::Duration;
224                match FileSystemWatcher::new(".", Duration::from_secs(1)) {
225                    Ok(watcher) => {
226                        engine.resource_manager.state().set_watcher(Some(watcher));
227                    }
228                    Err(e) => {
229                        Log::err(format!("Unable to create resource watcher. Reason {e:?}"));
230                    }
231                }
232            }
233        }
234
235        let args = Args::try_parse().unwrap_or_default();
236
237        match event_loop {
238            Some(event_loop) => run_normal(
239                engine,
240                args.override_scene.as_deref(),
241                event_loop,
242                throttle_threshold,
243                throttle_frame_interval,
244                self.desired_update_rate,
245            ),
246            None => run_headless(
247                engine,
248                args.override_scene.as_deref(),
249                throttle_threshold,
250                throttle_frame_interval,
251                self.desired_update_rate,
252            ),
253        }
254    }
255}
256
257fn run_headless(
258    mut engine: Engine,
259    override_scene: Option<&str>,
260    throttle_threshold: f32,
261    throttle_frame_interval: usize,
262    desired_update_rate: f32,
263) {
264    let mut previous = Instant::now();
265    let fixed_time_step = 1.0 / desired_update_rate;
266    let mut lag = fixed_time_step;
267    let mut frame_counter = 0usize;
268    let mut last_throttle_frame_number = 0usize;
269    let is_running = Cell::new(true);
270
271    engine.enable_plugins(
272        override_scene,
273        true,
274        ApplicationLoopController::Headless {
275            running: &is_running,
276        },
277    );
278
279    while is_running.get() {
280        register_scripted_scenes(&mut engine);
281
282        game_loop_iteration(
283            &mut engine,
284            ApplicationLoopController::Headless {
285                running: &is_running,
286            },
287            &mut previous,
288            &mut lag,
289            fixed_time_step,
290            throttle_threshold,
291            throttle_frame_interval,
292            frame_counter,
293            &mut last_throttle_frame_number,
294        );
295
296        frame_counter += 1;
297
298        // Only sleep for two-third of the remaining time step because thread::sleep tends to overshoot.
299        let sleep_time = (fixed_time_step - previous.elapsed().as_secs_f32()).max(0.0) * 0.66666;
300
301        if sleep_time > 0.0 {
302            std::thread::sleep(Duration::from_secs_f32(sleep_time));
303        }
304    }
305}
306
307fn run_normal(
308    mut engine: Engine,
309    override_scene: Option<&str>,
310    event_loop: EventLoop<()>,
311    throttle_threshold: f32,
312    throttle_frame_interval: usize,
313    desired_update_rate: f32,
314) {
315    let mut previous = Instant::now();
316    let fixed_time_step = 1.0 / desired_update_rate;
317    let mut lag = 0.0;
318    let mut frame_counter = 0usize;
319    let mut last_throttle_frame_number = 0usize;
320
321    engine.enable_plugins(
322        override_scene,
323        true,
324        ApplicationLoopController::EventLoop(&event_loop),
325    );
326
327    run_executor(event_loop, move |event, active_event_loop| {
328        active_event_loop.set_control_flow(ControlFlow::Wait);
329
330        engine.handle_os_events(
331            &event,
332            fixed_time_step,
333            ApplicationLoopController::ActiveEventLoop(active_event_loop),
334            &mut lag,
335        );
336
337        let scripted_scenes = register_scripted_scenes(&mut engine);
338        for scripted_scene in scripted_scenes {
339            engine.handle_os_event_by_scripts(&event, scripted_scene, fixed_time_step);
340        }
341
342        match event {
343            Event::Resumed => {
344                engine
345                    .initialize_graphics_context(active_event_loop)
346                    .expect("Unable to initialize graphics context!");
347
348                engine.handle_graphics_context_created_by_plugins(
349                    fixed_time_step,
350                    ApplicationLoopController::ActiveEventLoop(active_event_loop),
351                    &mut lag,
352                );
353            }
354            Event::Suspended => {
355                engine
356                    .destroy_graphics_context()
357                    .expect("Unable to destroy graphics context!");
358
359                engine.handle_graphics_context_destroyed_by_plugins(
360                    fixed_time_step,
361                    ApplicationLoopController::ActiveEventLoop(active_event_loop),
362                    &mut lag,
363                );
364            }
365            Event::AboutToWait => {
366                game_loop_iteration(
367                    &mut engine,
368                    ApplicationLoopController::ActiveEventLoop(active_event_loop),
369                    &mut previous,
370                    &mut lag,
371                    fixed_time_step,
372                    throttle_threshold,
373                    throttle_frame_interval,
374                    frame_counter,
375                    &mut last_throttle_frame_number,
376                );
377            }
378            Event::WindowEvent { event, .. } => {
379                match event {
380                    WindowEvent::CloseRequested => active_event_loop.exit(),
381                    WindowEvent::Resized(size) => {
382                        if let Err(e) = engine.set_frame_size(size.into()) {
383                            Log::writeln(
384                                MessageKind::Error,
385                                format!("Unable to set frame size: {e:?}"),
386                            );
387                        }
388                    }
389                    WindowEvent::RedrawRequested => {
390                        engine.handle_before_rendering_by_plugins(
391                            fixed_time_step,
392                            ApplicationLoopController::ActiveEventLoop(active_event_loop),
393                            &mut lag,
394                        );
395
396                        engine.render().unwrap();
397
398                        frame_counter += 1;
399                    }
400                    _ => (),
401                }
402
403                if let Some(os_event) = translate_event(&event) {
404                    for ui in engine.user_interfaces.iter_mut() {
405                        ui.process_os_event(&os_event);
406                    }
407                }
408            }
409            _ => (),
410        }
411    })
412}
413
414fn register_scripted_scenes(engine: &mut Engine) -> Vec<Handle<Scene>> {
415    let scenes = engine
416        .scenes
417        .pair_iter()
418        .map(|(s, _)| s)
419        .collect::<Vec<_>>();
420
421    for &scene_handle in scenes.iter() {
422        if !engine.has_scripted_scene(scene_handle) {
423            engine.register_scripted_scene(scene_handle);
424        }
425    }
426
427    scenes
428}
429
430fn game_loop_iteration(
431    engine: &mut Engine,
432    controller: ApplicationLoopController,
433    previous: &mut Instant,
434    lag: &mut f32,
435    fixed_time_step: f32,
436    throttle_threshold: f32,
437    throttle_frame_interval: usize,
438    frame_counter: usize,
439    last_throttle_frame_number: &mut usize,
440) {
441    let elapsed = previous.elapsed();
442    *previous = Instant::now();
443    *lag += elapsed.as_secs_f32();
444
445    // Update rate stabilization loop.
446    while *lag >= fixed_time_step {
447        let time_step;
448        if *lag >= throttle_threshold
449            && (frame_counter - *last_throttle_frame_number >= throttle_frame_interval)
450        {
451            // Modify the delta time to let the game internals to fast-forward the
452            // logic by the current lag.
453            time_step = *lag;
454            // Reset the lag to exit early from the loop, thus preventing its
455            // potential infinite increase, that in its turn could hang up the game.
456            *lag = 0.0;
457
458            *last_throttle_frame_number = frame_counter;
459        } else {
460            time_step = fixed_time_step;
461        }
462
463        engine.update(time_step, controller, lag, Default::default());
464
465        // Additional check is needed, because the `update` call above could modify
466        // the lag.
467        if *lag >= fixed_time_step {
468            *lag -= fixed_time_step;
469        } else if *lag < 0.0 {
470            // Prevent from going back in time.
471            *lag = 0.0;
472        }
473    }
474
475    if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context {
476        ctx.window.request_redraw();
477    }
478}
479
480#[allow(deprecated)] // TODO
481fn run_executor<F>(event_loop: EventLoop<()>, callback: F)
482where
483    F: FnMut(Event<()>, &ActiveEventLoop) + 'static,
484{
485    #[cfg(target_arch = "wasm32")]
486    {
487        use winit::platform::web::EventLoopExtWebSys;
488        event_loop.spawn(callback);
489    }
490
491    #[cfg(not(target_arch = "wasm32"))]
492    {
493        event_loop.run(callback).unwrap();
494    }
495}