1use 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
60pub 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 pub const DEFAULT_UPDATE_RATE: f32 = 60.0;
87 pub const DEFAULT_TIME_STEP: f32 = 1.0 / Self::DEFAULT_UPDATE_RATE;
89
90 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 })
107 .unwrap();
108
109 Self {
110 event_loop,
111 engine,
112 desired_update_rate: Self::DEFAULT_UPDATE_RATE,
113 throttle_threshold: 2.0 * Self::DEFAULT_TIME_STEP,
114 throttle_frame_interval: 5,
115 resource_hot_reloading: true,
116 }
117 }
118
119 pub fn new(event_loop: Option<EventLoop<()>>) -> Self {
123 let mut window_attributes = WindowAttributes::default();
124 window_attributes.resizable = true;
125 window_attributes.title = "Fyrox Game".to_string();
126
127 Self::from_params(
128 event_loop,
129 GraphicsContextParams {
130 window_attributes,
131 vsync: true,
132 msaa_sample_count: None,
133 graphics_server_constructor: Default::default(),
134 named_objects: false,
135 },
136 )
137 }
138
139 pub fn set_resource_hot_reloading_enabled(&mut self, enabled: bool) {
147 self.resource_hot_reloading = enabled;
148 }
149
150 pub fn is_resource_hot_reloading_enabled(&self) -> bool {
152 self.resource_hot_reloading
153 }
154
155 pub fn set_throttle_threshold(&mut self, threshold: f32) {
167 self.throttle_threshold = threshold.max(0.001);
168 }
169
170 pub fn throttle_threshold(&self) -> f32 {
172 self.throttle_threshold
173 }
174
175 pub fn set_throttle_frame_interval(&mut self, interval: usize) {
181 self.throttle_frame_interval = interval;
182 }
183
184 pub fn throttle_frame_interval(&self) -> usize {
187 self.throttle_frame_interval
188 }
189
190 pub fn set_desired_update_rate(&mut self, update_rate: f32) {
192 self.desired_update_rate = update_rate.abs();
193 }
194
195 pub fn desired_update_rate(&self) -> f32 {
197 self.desired_update_rate
198 }
199
200 pub fn add_plugin<P>(&mut self, plugin: P)
202 where
203 P: Plugin + 'static,
204 {
205 self.engine.add_plugin(plugin)
206 }
207
208 pub fn run(self) {
210 Log::info("Initializing resource registry.");
211 self.engine.resource_manager.update_or_load_registry();
212
213 let engine = self.engine;
214 let event_loop = self.event_loop;
215 let throttle_threshold = self.throttle_threshold;
216 let throttle_frame_interval = self.throttle_frame_interval;
217
218 if self.resource_hot_reloading {
219 #[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
220 {
221 use crate::core::watcher::FileSystemWatcher;
222 use std::time::Duration;
223 match FileSystemWatcher::new(".", Duration::from_secs(1)) {
224 Ok(watcher) => {
225 engine.resource_manager.state().set_watcher(Some(watcher));
226 }
227 Err(e) => {
228 Log::err(format!("Unable to create resource watcher. Reason {e:?}"));
229 }
230 }
231 }
232 }
233
234 let args = Args::try_parse().unwrap_or_default();
235
236 match event_loop {
237 Some(event_loop) => run_normal(
238 engine,
239 args.override_scene.as_deref(),
240 event_loop,
241 throttle_threshold,
242 throttle_frame_interval,
243 self.desired_update_rate,
244 ),
245 None => run_headless(
246 engine,
247 args.override_scene.as_deref(),
248 throttle_threshold,
249 throttle_frame_interval,
250 self.desired_update_rate,
251 ),
252 }
253 }
254}
255
256fn run_headless(
257 mut engine: Engine,
258 override_scene: Option<&str>,
259 throttle_threshold: f32,
260 throttle_frame_interval: usize,
261 desired_update_rate: f32,
262) {
263 let mut previous = Instant::now();
264 let fixed_time_step = 1.0 / desired_update_rate;
265 let mut lag = fixed_time_step;
266 let mut frame_counter = 0usize;
267 let mut last_throttle_frame_number = 0usize;
268 let is_running = Cell::new(true);
269
270 engine.enable_plugins(
271 override_scene,
272 true,
273 ApplicationLoopController::Headless {
274 running: &is_running,
275 },
276 );
277
278 while is_running.get() {
279 register_scripted_scenes(&mut engine);
280
281 game_loop_iteration(
282 &mut engine,
283 ApplicationLoopController::Headless {
284 running: &is_running,
285 },
286 &mut previous,
287 &mut lag,
288 fixed_time_step,
289 throttle_threshold,
290 throttle_frame_interval,
291 frame_counter,
292 &mut last_throttle_frame_number,
293 );
294
295 frame_counter += 1;
296
297 let sleep_time = (fixed_time_step - previous.elapsed().as_secs_f32()).max(0.0) * 0.66666;
299
300 if sleep_time > 0.0 {
301 std::thread::sleep(Duration::from_secs_f32(sleep_time));
302 }
303 }
304}
305
306fn run_normal(
307 mut engine: Engine,
308 override_scene: Option<&str>,
309 event_loop: EventLoop<()>,
310 throttle_threshold: f32,
311 throttle_frame_interval: usize,
312 desired_update_rate: f32,
313) {
314 let mut previous = Instant::now();
315 let fixed_time_step = 1.0 / desired_update_rate;
316 let mut lag = 0.0;
317 let mut frame_counter = 0usize;
318 let mut last_throttle_frame_number = 0usize;
319
320 engine.enable_plugins(
321 override_scene,
322 true,
323 ApplicationLoopController::EventLoop(&event_loop),
324 );
325
326 run_executor(event_loop, move |event, active_event_loop| {
327 active_event_loop.set_control_flow(ControlFlow::Wait);
328
329 engine.handle_os_events(
330 &event,
331 fixed_time_step,
332 ApplicationLoopController::ActiveEventLoop(active_event_loop),
333 &mut lag,
334 );
335
336 let scripted_scenes = register_scripted_scenes(&mut engine);
337 for scripted_scene in scripted_scenes {
338 engine.handle_os_event_by_scripts(&event, scripted_scene, fixed_time_step);
339 }
340
341 match event {
342 Event::Resumed => {
343 engine
344 .initialize_graphics_context(active_event_loop)
345 .expect("Unable to initialize graphics context!");
346
347 engine.handle_graphics_context_created_by_plugins(
348 fixed_time_step,
349 ApplicationLoopController::ActiveEventLoop(active_event_loop),
350 &mut lag,
351 );
352 }
353 Event::Suspended => {
354 engine
355 .destroy_graphics_context()
356 .expect("Unable to destroy graphics context!");
357
358 engine.handle_graphics_context_destroyed_by_plugins(
359 fixed_time_step,
360 ApplicationLoopController::ActiveEventLoop(active_event_loop),
361 &mut lag,
362 );
363 }
364 Event::AboutToWait => {
365 game_loop_iteration(
366 &mut engine,
367 ApplicationLoopController::ActiveEventLoop(active_event_loop),
368 &mut previous,
369 &mut lag,
370 fixed_time_step,
371 throttle_threshold,
372 throttle_frame_interval,
373 frame_counter,
374 &mut last_throttle_frame_number,
375 );
376 }
377 Event::WindowEvent { event, .. } => {
378 match event {
379 WindowEvent::CloseRequested => active_event_loop.exit(),
380 WindowEvent::Resized(size) => {
381 if let Err(e) = engine.set_frame_size(size.into()) {
382 Log::writeln(
383 MessageKind::Error,
384 format!("Unable to set frame size: {e:?}"),
385 );
386 }
387 }
388 WindowEvent::RedrawRequested => {
389 engine.handle_before_rendering_by_plugins(
390 fixed_time_step,
391 ApplicationLoopController::ActiveEventLoop(active_event_loop),
392 &mut lag,
393 );
394
395 engine.render().unwrap();
396
397 frame_counter += 1;
398 }
399 _ => (),
400 }
401
402 if let Some(os_event) = translate_event(&event) {
403 for ui in engine.user_interfaces.iter_mut() {
404 ui.process_os_event(&os_event);
405 }
406 }
407 }
408 _ => (),
409 }
410 })
411}
412
413fn register_scripted_scenes(engine: &mut Engine) -> Vec<Handle<Scene>> {
414 let scenes = engine
415 .scenes
416 .pair_iter()
417 .map(|(s, _)| s)
418 .collect::<Vec<_>>();
419
420 for &scene_handle in scenes.iter() {
421 if !engine.has_scripted_scene(scene_handle) {
422 engine.register_scripted_scene(scene_handle);
423 }
424 }
425
426 scenes
427}
428
429fn game_loop_iteration(
430 engine: &mut Engine,
431 controller: ApplicationLoopController,
432 previous: &mut Instant,
433 lag: &mut f32,
434 fixed_time_step: f32,
435 throttle_threshold: f32,
436 throttle_frame_interval: usize,
437 frame_counter: usize,
438 last_throttle_frame_number: &mut usize,
439) {
440 let elapsed = previous.elapsed();
441 *previous = Instant::now();
442 *lag += elapsed.as_secs_f32();
443
444 while *lag >= fixed_time_step {
446 let time_step;
447 if *lag >= throttle_threshold
448 && (frame_counter - *last_throttle_frame_number >= throttle_frame_interval)
449 {
450 time_step = *lag;
453 *lag = 0.0;
456
457 *last_throttle_frame_number = frame_counter;
458 } else {
459 time_step = fixed_time_step;
460 }
461
462 engine.update(time_step, controller, lag, Default::default());
463
464 if *lag >= fixed_time_step {
467 *lag -= fixed_time_step;
468 } else if *lag < 0.0 {
469 *lag = 0.0;
471 }
472 }
473
474 if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context {
475 ctx.window.request_redraw();
476 }
477}
478
479#[allow(deprecated)] fn run_executor<F>(event_loop: EventLoop<()>, callback: F)
481where
482 F: FnMut(Event<()>, &ActiveEventLoop) + 'static,
483{
484 #[cfg(target_arch = "wasm32")]
485 {
486 use winit::platform::web::EventLoopExtWebSys;
487 event_loop.spawn(callback);
488 }
489
490 #[cfg(not(target_arch = "wasm32"))]
491 {
492 event_loop.run(callback).unwrap();
493 }
494}