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 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 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 pub fn set_resource_hot_reloading_enabled(&mut self, enabled: bool) {
148 self.resource_hot_reloading = enabled;
149 }
150
151 pub fn is_resource_hot_reloading_enabled(&self) -> bool {
153 self.resource_hot_reloading
154 }
155
156 pub fn set_throttle_threshold(&mut self, threshold: f32) {
168 self.throttle_threshold = threshold.max(0.001);
169 }
170
171 pub fn throttle_threshold(&self) -> f32 {
173 self.throttle_threshold
174 }
175
176 pub fn set_throttle_frame_interval(&mut self, interval: usize) {
182 self.throttle_frame_interval = interval;
183 }
184
185 pub fn throttle_frame_interval(&self) -> usize {
188 self.throttle_frame_interval
189 }
190
191 pub fn set_desired_update_rate(&mut self, update_rate: f32) {
193 self.desired_update_rate = update_rate.abs();
194 }
195
196 pub fn desired_update_rate(&self) -> f32 {
198 self.desired_update_rate
199 }
200
201 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 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 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 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 time_step = *lag;
454 *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 if *lag >= fixed_time_step {
468 *lag -= fixed_time_step;
469 } else if *lag < 0.0 {
470 *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)] fn 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}