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