1use 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
53pub 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 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(
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 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 pub fn set_resource_hot_reloading_enabled(&mut self, enabled: bool) {
144 self.resource_hot_reloading = enabled;
145 }
146
147 pub fn is_resource_hot_reloading_enabled(&self) -> bool {
149 self.resource_hot_reloading
150 }
151
152 pub fn set_headless(&mut self, headless: bool) {
156 self.headless = headless;
157 }
158
159 pub fn is_headless(&self) -> bool {
161 self.headless
162 }
163
164 pub fn set_throttle_threshold(&mut self, threshold: f32) {
176 self.throttle_threshold = threshold.max(0.001);
177 }
178
179 pub fn throttle_threshold(&self) -> f32 {
181 self.throttle_threshold
182 }
183
184 pub fn set_throttle_frame_interval(&mut self, interval: usize) {
190 self.throttle_frame_interval = interval;
191 }
192
193 pub fn throttle_frame_interval(&self) -> usize {
196 self.throttle_frame_interval
197 }
198
199 pub fn set_desired_update_rate(&mut self, update_rate: f32) {
201 self.desired_update_rate = update_rate.abs();
202 }
203
204 pub fn desired_update_rate(&self) -> f32 {
206 self.desired_update_rate
207 }
208
209 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 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 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 time_step = lag;
308 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 if lag >= fixed_time_step {
322 lag -= fixed_time_step;
323 } else if lag < 0.0 {
324 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}