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::{
24    asset::manager::ResourceManager,
25    core::{
26        instant::Instant,
27        log::{Log, MessageKind},
28        task::TaskPool,
29    },
30    engine::{
31        Engine, EngineInitParams, GraphicsContext, GraphicsContextParams, SerializationContext,
32    },
33    event::{Event, WindowEvent},
34    event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
35    plugin::Plugin,
36    utils::translate_event,
37    window::WindowAttributes,
38};
39use clap::Parser;
40use fyrox_ui::constructor::new_widget_constructor_container;
41use std::{
42    ops::{Deref, DerefMut},
43    sync::Arc,
44};
45
46#[derive(Parser, Debug, Default)]
47#[clap(author, version, about, long_about = None)]
48struct Args {
49    #[clap(short, long, default_value = None)]
50    override_scene: Option<String>,
51}
52
53/// Executor is a small wrapper that manages plugins and scripts for your game.
54pub struct Executor {
55    event_loop: EventLoop<()>,
56    engine: Engine,
57    desired_update_rate: f32,
58    headless: bool,
59    throttle_threshold: f32,
60    throttle_frame_interval: usize,
61    resource_hot_reloading: bool,
62}
63
64impl Deref for Executor {
65    type Target = Engine;
66
67    fn deref(&self) -> &Self::Target {
68        &self.engine
69    }
70}
71
72impl DerefMut for Executor {
73    fn deref_mut(&mut self) -> &mut Self::Target {
74        &mut self.engine
75    }
76}
77
78impl Default for Executor {
79    fn default() -> Self {
80        Self::new()
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`].
92    pub fn from_params(
93        event_loop: EventLoop<()>,
94        graphics_context_params: GraphicsContextParams,
95    ) -> Self {
96        let serialization_context = Arc::new(SerializationContext::new());
97        let task_pool = Arc::new(TaskPool::new());
98        let engine = Engine::new(EngineInitParams {
99            graphics_context_params,
100            resource_manager: ResourceManager::new(task_pool.clone()),
101            serialization_context,
102            task_pool,
103            widget_constructors: Arc::new(new_widget_constructor_container()),
104        })
105        .unwrap();
106
107        Self {
108            event_loop,
109            engine,
110            desired_update_rate: Self::DEFAULT_UPDATE_RATE,
111            headless: false,
112            throttle_threshold: 2.0 * Self::DEFAULT_TIME_STEP,
113            throttle_frame_interval: 5,
114            resource_hot_reloading: true,
115        }
116    }
117
118    /// Creates new game executor using default window and with vsync turned on. For more flexible
119    /// way to create an executor see [`Executor::from_params`].
120    pub fn new() -> Self {
121        let mut window_attributes = WindowAttributes::default();
122        window_attributes.resizable = true;
123        window_attributes.title = "Fyrox Game".to_string();
124
125        Self::from_params(
126            EventLoop::new().unwrap(),
127            GraphicsContextParams {
128                window_attributes,
129                vsync: true,
130                msaa_sample_count: None,
131                graphics_server_constructor: Default::default(),
132            },
133        )
134    }
135
136    /// Enables or disables hot reloading of changed resources (such as textures, shaders, scenes, etc.).
137    /// Enabled by default.
138    ///
139    /// # Platform-specific
140    ///
141    /// Does nothing on Android and WebAssembly, because these OSes does not have rich file system
142    /// as PC.
143    pub fn set_resource_hot_reloading_enabled(&mut self, enabled: bool) {
144        self.resource_hot_reloading = enabled;
145    }
146
147    /// Returns `true` if hot reloading of changed resources is enabled, `false` - otherwise.
148    pub fn is_resource_hot_reloading_enabled(&self) -> bool {
149        self.resource_hot_reloading
150    }
151
152    /// Defines whether the executor should initialize graphics context or not. Headless mode could
153    /// be useful for game servers, where you don't need to have a window, renderer, sound, etc.
154    /// By default, headless mode is off.
155    pub fn set_headless(&mut self, headless: bool) {
156        self.headless = headless;
157    }
158
159    /// Returns `true` if the headless mode is turned on, `false` - otherwise.
160    pub fn is_headless(&self) -> bool {
161        self.headless
162    }
163
164    /// Sets the desired throttle threshold (in seconds), at which the engine will stop trying to
165    /// stabilize the update rate of the game logic and will increase the time step. This option
166    /// could be useful to prevent potential hang up of the game if its logic or rendering takes too
167    /// much time at each frame. The default value is two default time steps (33.3(3) milliseconds
168    /// or 0.0333(3) seconds).
169    ///
170    /// ## Important notes
171    ///
172    /// Physics could suffer from variable time step which may result in objects falling through the
173    /// ground and some other nasty things. Throttle threshold should be at reasonably high levels
174    /// (usually 2x-3x of the fixed time step).
175    pub fn set_throttle_threshold(&mut self, threshold: f32) {
176        self.throttle_threshold = threshold.max(0.001);
177    }
178
179    /// Returns current throttle threshold. See [`Self::set_throttle_threshold`] docs for more info.
180    pub fn throttle_threshold(&self) -> f32 {
181        self.throttle_threshold
182    }
183
184    /// Sets the amount of frames (consecutive) that will be allowed to have lag spikes and the engine
185    /// won't modify time step for internal update calls during such interval. This setting allows the
186    /// engine to ignore small lag spikes and do not fast-forward game logic using variable time step.
187    /// Variable time step could be bad for physics, which may result in objects falling through the
188    /// ground, etc. Default is 5 frames.
189    pub fn set_throttle_frame_interval(&mut self, interval: usize) {
190        self.throttle_frame_interval = interval;
191    }
192
193    /// Returns current throttle frame interval. See [`Self::set_throttle_frame_interval`] docs for
194    /// more info.
195    pub fn throttle_frame_interval(&self) -> usize {
196        self.throttle_frame_interval
197    }
198
199    /// Sets the desired update rate in frames per second.
200    pub fn set_desired_update_rate(&mut self, update_rate: f32) {
201        self.desired_update_rate = update_rate.abs();
202    }
203
204    /// Returns desired update rate in frames per second.
205    pub fn desired_update_rate(&self) -> f32 {
206        self.desired_update_rate
207    }
208
209    /// Adds new plugin to the executor, the plugin will be enabled only on [`Executor::run`].
210    pub fn add_plugin<P>(&mut self, plugin: P)
211    where
212        P: Plugin + 'static,
213    {
214        self.engine.add_plugin(plugin)
215    }
216
217    /// Runs the executor - starts your game.
218    pub fn run(self) {
219        let mut engine = self.engine;
220        let event_loop = self.event_loop;
221        let headless = self.headless;
222        let throttle_threshold = self.throttle_threshold;
223        let throttle_frame_interval = self.throttle_frame_interval;
224
225        if self.resource_hot_reloading {
226            #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
227            {
228                use crate::core::watcher::FileSystemWatcher;
229                use std::time::Duration;
230                match FileSystemWatcher::new(".", Duration::from_secs(1)) {
231                    Ok(watcher) => {
232                        engine.resource_manager.state().set_watcher(Some(watcher));
233                    }
234                    Err(e) => {
235                        Log::err(format!("Unable to create resource watcher. Reason {e:?}"));
236                    }
237                }
238            }
239        }
240
241        let args = Args::try_parse().unwrap_or_default();
242
243        engine.enable_plugins(args.override_scene.as_deref(), true, Some(&event_loop));
244
245        let mut previous = Instant::now();
246        let fixed_time_step = 1.0 / self.desired_update_rate;
247        let mut lag = 0.0;
248        let mut frame_counter = 0usize;
249        let mut last_throttle_frame_number = 0usize;
250
251        run_executor(event_loop, move |event, window_target| {
252            window_target.set_control_flow(ControlFlow::Wait);
253
254            engine.handle_os_event_by_plugins(&event, fixed_time_step, window_target, &mut lag);
255
256            let scenes = engine
257                .scenes
258                .pair_iter()
259                .map(|(s, _)| s)
260                .collect::<Vec<_>>();
261
262            for &scene_handle in scenes.iter() {
263                if !engine.has_scripted_scene(scene_handle) {
264                    engine.register_scripted_scene(scene_handle);
265                }
266
267                engine.handle_os_event_by_scripts(&event, scene_handle, fixed_time_step);
268            }
269
270            match event {
271                Event::Resumed if !headless => {
272                    engine
273                        .initialize_graphics_context(window_target)
274                        .expect("Unable to initialize graphics context!");
275
276                    engine.handle_graphics_context_created_by_plugins(
277                        fixed_time_step,
278                        window_target,
279                        &mut lag,
280                    );
281                }
282                Event::Suspended if !headless => {
283                    engine
284                        .destroy_graphics_context()
285                        .expect("Unable to destroy graphics context!");
286
287                    engine.handle_graphics_context_destroyed_by_plugins(
288                        fixed_time_step,
289                        window_target,
290                        &mut lag,
291                    );
292                }
293                Event::AboutToWait => {
294                    let elapsed = previous.elapsed();
295                    previous = Instant::now();
296                    lag += elapsed.as_secs_f32();
297
298                    // Update rate stabilization loop.
299                    while lag >= fixed_time_step {
300                        let time_step;
301                        if lag >= throttle_threshold
302                            && (frame_counter - last_throttle_frame_number
303                                >= throttle_frame_interval)
304                        {
305                            // Modify the delta time to let the game internals to fast-forward the
306                            // logic by the current lag.
307                            time_step = lag;
308                            // Reset the lag to exit early from the loop, thus preventing its
309                            // potential infinite increase, that in its turn could hang up the game.
310                            lag = 0.0;
311
312                            last_throttle_frame_number = frame_counter;
313                        } else {
314                            time_step = fixed_time_step;
315                        }
316
317                        engine.update(time_step, window_target, &mut lag, Default::default());
318
319                        // Additional check is needed, because the `update` call above could modify
320                        // the lag.
321                        if lag >= fixed_time_step {
322                            lag -= fixed_time_step;
323                        } else if lag < 0.0 {
324                            // Prevent from going back in time.
325                            lag = 0.0;
326                        }
327                    }
328
329                    if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context {
330                        ctx.window.request_redraw();
331                    }
332                }
333                Event::WindowEvent { event, .. } => {
334                    match event {
335                        WindowEvent::CloseRequested => window_target.exit(),
336                        WindowEvent::Resized(size) => {
337                            if let Err(e) = engine.set_frame_size(size.into()) {
338                                Log::writeln(
339                                    MessageKind::Error,
340                                    format!("Unable to set frame size: {e:?}"),
341                                );
342                            }
343                        }
344                        WindowEvent::RedrawRequested => {
345                            engine.handle_before_rendering_by_plugins(
346                                fixed_time_step,
347                                window_target,
348                                &mut lag,
349                            );
350
351                            engine.render().unwrap();
352
353                            frame_counter += 1;
354                        }
355                        _ => (),
356                    }
357
358                    if let Some(os_event) = translate_event(&event) {
359                        for ui in engine.user_interfaces.iter_mut() {
360                            ui.process_os_event(&os_event);
361                        }
362                    }
363                }
364                _ => (),
365            }
366        })
367    }
368}
369
370fn run_executor<F>(event_loop: EventLoop<()>, callback: F)
371where
372    F: FnMut(Event<()>, &EventLoopWindowTarget<()>) + 'static,
373{
374    #[cfg(target_arch = "wasm32")]
375    {
376        use winit::platform::web::EventLoopExtWebSys;
377        event_loop.spawn(callback);
378    }
379
380    #[cfg(not(target_arch = "wasm32"))]
381    {
382        event_loop.run(callback).unwrap();
383    }
384}