1use crate::datapack::DataMultistore;
4use crate::datapack::source::SourceManager;
5use crate::input::Input;
6use crate::scene::{BaseScene, NullSpawnable, Scene, SceneConfig, SceneType};
7#[cfg(feature = "audio")] use crate::audio::{AlAudio, Audio, AudioError, NullAudio};
8use crate::renderer::{Renderer, RendererError, ViewportMode, WindowScaleMode};
9#[cfg(feature = "imgui_feature")] use crate::imgui_handler::ImGuiHandler;
10#[cfg(feature = "default_logger")] use crate::doglog;
11
12use sdl2::event::Event;
13use sdl2::event::WindowEvent;
14use sdl2::keyboard::Mod as KeyboardMod;
15use sdl2::messagebox;
16
17#[cfg(feature = "graphical_panic")] use std::panic::PanicInfo;
18#[cfg(feature = "graphical_panic")] use backtrace::Backtrace;
19
20#[cfg(feature = "imgui_feature")] use imgui;
21
22use std::time::{Duration, Instant};
23use std::rc::Rc;
24use std::cell::RefCell;
25use std::collections::HashMap;
26use std::any::TypeId;
27use downcast_rs::Downcast;
28use std::sync::{Arc, Mutex, MutexGuard};
29use crate::doglog::ConsoleHistory;
30use crate::renderer::draw_control_private::RendererNewInfo;
31
32const MILLISECONDS_PER_NANOSECOND : f64 = 0.000001;
33const BUSY_WAIT_MARGIN : u64 = 2; const AUTOMATIC_LERP_CAP_MULTIPLIER : f64 = 1.05;
35const MAX_HITCH_MS : u64 = 100;
36const MAX_POST_SLOWDOWN_CATCHUP : u32 = 6;
37const MAX_FRAME_JIFFY_SUBTRACT : u32 = 1000;
38const MAX_FPS_STAT_CATCHUP : u64 = 3;
39const LOW_ENERGY_TIMER_MIN : u32 = 60;
40const LOW_ENERGY_DRAW_RATE : u32 = 2;
41
42#[derive(Debug)]
43pub enum Sdl2Subsystem
44{
45 Main,
46 Event,
47 Video,
48 GameController,
49}
50
51#[derive(Debug, Fail)]
52pub enum InitError
53{
54 #[fail(display = "{}", _0)]
55 KeeshondInitError(String),
56 #[fail(display = "SDL {:?} subsystem: {}", _0, _1)]
57 Sdl2InitError(Sdl2Subsystem, String),
58 #[fail(display = "{}", _0)]
59 RendererInitError(RendererError),
60 #[cfg(feature = "audio")]
61 #[fail(display = "{}", _0)]
62 AudioInitError(AudioError),
63 #[fail(display = "{}", _0)]
64 ImguiInitError(String)
65}
66
67struct PlaceholderScene
68{
69
70}
71
72impl SceneType for PlaceholderScene
73{
74 type SpawnableIdType = NullSpawnable;
75
76 fn new() -> Self where Self : Sized { PlaceholderScene {} }
77 fn config(&mut self, _game: &mut GameControl) -> SceneConfig<Self>
78 {
79 warn!("No scene loaded in gameloop!");
80
81 SceneConfig::new()
82 }
83}
84
85#[cfg(feature = "graphical_panic")]
86fn panic_hook(info : &PanicInfo)
87{
88 let message : &str = match info.payload().downcast_ref::<String>()
90 {
91 Some(s) => s,
92 None => match info.payload().downcast_ref::<&str>()
93 {
94 Some(s) => s,
95 None => "No reason was given.",
96 }
97 };
98 let mut cli_message = match info.location()
100 {
101 Some(location) => format!("A panic error occurred on {}:{}: \n{}",
102 location.file(),
103 location.line(),
104 message),
105 None => format!("A panic error occurred: \n{}", message),
106 };
107
108 let bt = Backtrace::new();
109
110 cli_message = format!("{}\n\n{:?}", cli_message, bt);
111 println!("{}", &cli_message);
112
113 let gui_message = format!("A panic error occurred.\n\n{}", message);
114
115 match messagebox::show_simple_message_box(messagebox::MessageBoxFlag::ERROR, "Panic Error", &gui_message[..], None)
116 {
117 Ok(_) => (),
118 Err(_) => println!("(Messagebox could not be displayed)"),
119 };
120
121 std::process::exit(1);
122}
123
124fn print_log_section(title : &str)
125{
126 info!("");
127 info!("{:-^40}", format!(" {} ", title));
128 info!("");
129}
130
131#[allow(unused_variables)]
133pub fn gui_cli_message(message : &str, title : &str)
134{
135 println!("{}", message);
137
138 match messagebox::show_simple_message_box(messagebox::MessageBoxFlag::INFORMATION, title, &message[..], None)
140 {
141 Ok(_) => (),
142 Err(_) => println!("(Messagebox could not be displayed)"),
143 };
144}
145
146fn read_options() -> getopts::Matches
147{
148 let args : Vec<String> = std::env::args().collect();
149 let mut options = getopts::Options::new();
150
151 options.optflag("", "help", "Show this help");
152 options.optflag("f", "fullscreen", "Run in fullscreen mode");
153 options.optflag("w", "window", "Run in windowed mode");
154 options.optflag("", "interpolate", "When an alternate refresh rate is detected, unlock display framerate from logic and enable frame interpolation.");
155 options.optflag("", "no-interpolate", "Force disable frame interpolation and lock display rate to logic rate. Use when monitor's refresh rate equals game's logic rate.");
156 options.optflag("", "force-interpolate", "Force enable frame interpolation. Not recommended if monitor's refresh rate equals game's logic rate!");
157 options.optopt("", "interp-cap", "Upper framerate limit for frame interpolation, determined by FPS_LIMIT (\"auto\" for default, \"off\" to disable).", "FPS_LIMIT");
158 options.optflag("", "kiosk", "Enable kiosk mode, which forces fullscreen and disables closing the window. Note that the user may still be able to close the program depending on your operating environment's global shortcuts.");
159
160 match options.parse(&args[1..])
161 {
162 Ok(matches) =>
163 {
164 if matches.opt_present("help")
165 {
166 let message = options.usage("Commandline Parameters Help");
167 gui_cli_message(&message, "Commandline Help");
168 std::process::exit(1);
169 }
170
171 matches
172 }
173 Err(fail) =>
174 {
175 let message = format!("{}\n{}",
176 fail,
177 options.usage("Command parsing failed. Run without options to use defaults."));
178 gui_cli_message(&message, "Commandline Help");
179 std::process::exit(1);
180 }
181 }
182}
183
184fn apply_config(gameinfo : &mut GameInfo)
185{
186 let matches = read_options();
188
189 if matches.opt_present("window")
190 {
191 gameinfo.fullscreen = false;
192 }
193 else if matches.opt_present("fullscreen")
194 {
195 gameinfo.fullscreen = true;
196 }
197
198 if matches.opt_present("no-interpolate")
199 {
200 gameinfo.frame_interpolation = FrameInterpolationMode::Off;
201 }
202 else if matches.opt_present("interpolate")
203 {
204 gameinfo.frame_interpolation = FrameInterpolationMode::Automatic;
205 }
206 else if matches.opt_present("force-interpolate")
207 {
208 gameinfo.frame_interpolation = FrameInterpolationMode::Force;
209 }
210
211 match matches.opt_str("interp-cap")
212 {
213 Some(cap) =>
214 {
215 match cap.as_str()
216 {
217 "auto" => { gameinfo.frame_interpolation_cap = FrameInterpolationCap::Automatic; }
218 "off" => { gameinfo.frame_interpolation_cap = FrameInterpolationCap::None; }
219 _ =>
220 {
221 match cap.parse::<f64>()
222 {
223 Ok(value) => { gameinfo.frame_interpolation_cap = FrameInterpolationCap::Manual(value); }
224 Err(_) => { gameinfo.frame_interpolation_cap = FrameInterpolationCap::Automatic; }
225 }
226 }
227 }
228 },
229 _ => ()
230 };
231
232 if matches.opt_present("kiosk")
233 {
234 gameinfo.kiosk_mode = true;
235 }
236}
237
238#[derive(Copy, Clone, Debug, PartialEq, Eq)]
240pub enum FrameInterpolationMode
241{
242 Off,
244 Automatic,
246 Force
248}
249
250#[derive(Copy, Clone, Debug, PartialEq)]
252pub enum FrameInterpolationCap
253{
254 Automatic,
257 Manual(f64),
259 None
261}
262
263#[derive(Copy, Clone, Debug, PartialEq)]
265pub enum FrameInterpolationPacing
266{
267 PreferSmoothMotion,
270 PreferHighFramerate
272}
273
274#[derive(Clone)]
276pub struct GameInfo
277{
278 pub package_name : &'static str,
280 pub friendly_name : &'static str,
282 pub base_width : u32,
284 pub base_height : u32,
286 pub default_window_scale : WindowScaleMode,
288 pub texture_filtering : bool,
290 pub viewport_mode : ViewportMode,
293 pub fullscreen : bool,
295 pub target_framerate : f64,
297 pub frame_interpolation : FrameInterpolationMode,
300 pub frame_interpolation_cap : FrameInterpolationCap,
303 pub frame_interpolation_pacing : FrameInterpolationPacing,
306 pub cursor_visible : bool,
308 pub auto_unload : bool,
310 pub allow_system_sleep : bool,
313 pub low_energy_app : bool,
317 pub log_stats : bool,
320 pub log_packages : Vec<&'static str>,
322 pub kiosk_mode : bool
326}
327
328impl Default for GameInfo
329{
330 fn default() -> GameInfo
331 {
332 GameInfo
333 {
334 package_name : "",
335 friendly_name : "Untitled",
336 base_width : 1280,
337 base_height : 720,
338 default_window_scale : WindowScaleMode::MultiplierDpiScaled(1.0),
339 texture_filtering : true,
340 viewport_mode : ViewportMode::Independent,
341 fullscreen : false,
342 kiosk_mode : false,
343 cursor_visible : true,
344 auto_unload : true,
345 allow_system_sleep : false,
346 low_energy_app : false,
347 target_framerate : 60.0,
348 frame_interpolation : FrameInterpolationMode::Off,
349 frame_interpolation_cap : FrameInterpolationCap::Automatic,
350 frame_interpolation_pacing : FrameInterpolationPacing::PreferSmoothMotion,
351 log_stats : true,
352 log_packages : Vec::new()
353 }
354 }
355}
356
357pub struct GameloopStopwatch
359{
360 jiffies : Instant,
361 jiffies_this_frame : Duration,
362 jiffies_this_second : Duration,
363 jiffies_last_frame : Duration,
364 tics_this_frame : u32,
365 tics_this_second : u32,
366 frames_this_second : u32,
367 in_frame : bool
368}
369
370impl GameloopStopwatch
371{
372 pub fn new() -> GameloopStopwatch
373 {
374 GameloopStopwatch
375 {
376 jiffies : Instant::now(),
377 jiffies_this_frame : Duration::new(0, 0),
378 jiffies_this_second : Duration::new(0, 0),
379 jiffies_last_frame : Duration::new(0, 0),
380 tics_this_frame : 0,
381 tics_this_second : 0,
382 frames_this_second : 0,
383 in_frame : false
384 }
385 }
386
387 pub fn begin_frame_timing(&mut self)
388 {
389 if self.in_frame
390 {
391 return;
392 }
393
394 self.in_frame = true;
395
396 self.jiffies = Instant::now();
397 self.jiffies_last_frame = self.jiffies_this_frame;
398 self.jiffies_this_frame = Duration::new(0, 0);
399 self.tics_this_frame = 0;
400 }
401
402 pub fn end_frame_timing(&mut self)
403 {
404 if !self.in_frame
405 {
406 return;
407 }
408
409 self.in_frame = false;
410
411 self.jiffies_this_frame = Instant::now() - self.jiffies;
412 self.tics_this_second += self.tics_this_frame;
413 self.jiffies_this_second += self.jiffies_this_frame;
414 self.frames_this_second += 1;
415 }
416
417 pub fn tic(&mut self)
418 {
419 self.tics_this_frame += 1;
420 }
421
422 pub fn new_second(&mut self)
423 {
424 self.jiffies_this_second = Duration::new(0, 0);
425 self.tics_this_second = 0;
426 self.frames_this_second = 0;
427 }
428
429 pub fn jiffies_this_frame(&self) -> Duration
430 {
431 if self.in_frame
432 {
433 return Instant::now() - self.jiffies;
434 }
435
436 self.jiffies_this_frame
437 }
438
439 pub fn jiffies_this_second(&self) -> Duration
440 {
441 self.jiffies_this_second
442 }
443
444 pub fn tics_this_frame(&self) -> u32
445 {
446 self.tics_this_frame
447 }
448
449 pub fn tics_this_second(&self) -> u32
450 {
451 self.tics_this_second
452 }
453
454 pub fn frames_this_second(&self) -> u32
455 {
456 self.frames_this_second
457 }
458
459 pub fn current_ms(&self) -> f64
460 {
461 self.jiffies_this_frame.as_nanos() as f64 * MILLISECONDS_PER_NANOSECOND
462 }
463
464 pub fn average_ms(&self) -> f64
465 {
466 self.jiffies_this_second.as_nanos() as f64 * MILLISECONDS_PER_NANOSECOND / self.frames_this_second as f64
467 }
468}
469
470pub trait GameSingleton : Downcast
473{
474
475}
476
477impl_downcast!(GameSingleton);
478
479pub(crate) struct GameControlOptions<'a>
480{
481 console_history: Arc<Mutex<ConsoleHistory>>,
482 sdl_context : Option<&'a sdl2::Sdl>
483}
484
485impl<'a> GameControlOptions<'a>
486{
487 pub fn headless() -> GameControlOptions<'a>
489 {
490 GameControlOptions
491 {
492 console_history : Arc::new(Mutex::new(ConsoleHistory::new())),
493 sdl_context : None
494 }
495 }
496}
497
498pub struct GameControl
502{
503 target_framerate : f64,
504 frame_interpolation : FrameInterpolationMode,
505 interpolation_cap : FrameInterpolationCap,
506 interpolation_pacing : FrameInterpolationPacing,
507 low_energy_app : bool,
508 low_energy_timer : u32,
509 auto_unload : bool,
510 input : Input,
511 renderer : Renderer,
512 #[cfg(feature = "audio")]
513 audio : Box<dyn Audio>,
514 #[allow(dead_code)]
515 controller_subsystem : Option<sdl2::GameControllerSubsystem>,
516 controllers : HashMap<u32, sdl2::controller::GameController>,
517 next_scene : Box<dyn BaseScene>,
518 next_scene_waiting : bool,
519 source_manager : Rc<RefCell<SourceManager>>,
520 resources : DataMultistore,
521 singletons : HashMap<TypeId, Box<dyn GameSingleton>>,
522 console_history : Arc<Mutex<ConsoleHistory>>,
523 stats : GameStats,
524 focused : bool,
525 done : bool,
526 #[cfg(feature = "imgui_feature")]
527 imgui_handler : Option<ImGuiHandler>
528}
529
530impl GameControl
531{
532 pub(crate) fn new(gameinfo : &GameInfo, options : GameControlOptions) -> Result<GameControl, InitError>
533 {
534 let source_manager = Rc::new(RefCell::new(SourceManager::new()));
535
536 let mut resources = DataMultistore::new(source_manager.clone());
537
538 let mut renderer_new_info = None;
539 #[cfg(feature = "audio")]
540 let audio : Box<dyn Audio>;
541 let controller_subsystem;
542 let mut input;
543
544 if let Some(sdl) = options.sdl_context
545 {
546 let video_subsystem = sdl.video().map_err(
547 |error| InitError::Sdl2InitError(Sdl2Subsystem::Video, error))?;
548
549 controller_subsystem = Some(sdl.game_controller().map_err(
550 |error| InitError::Sdl2InitError(Sdl2Subsystem::GameController, error))?);
551
552 #[cfg(feature = "audio")]
553 {
554 audio = Box::new(AlAudio::new(&mut resources).map_err(
555 |error| InitError::AudioInitError(error))?);
556 }
557
558 input = Input::new(Some(sdl.mouse()));
559 input.set_cursor_visible(gameinfo.cursor_visible);
560
561 if gameinfo.allow_system_sleep
562 {
563 video_subsystem.enable_screen_saver();
564 }
565 else
566 {
567 video_subsystem.disable_screen_saver();
568 }
569
570 renderer_new_info = Some(RendererNewInfo
571 {
572 width : gameinfo.base_width,
573 height : gameinfo.base_height,
574 default_window_scale : gameinfo.default_window_scale,
575 texture_filtering : gameinfo.texture_filtering,
576 kiosk_mode : gameinfo.kiosk_mode,
577 fullscreen : gameinfo.fullscreen,
578 viewport_mode : gameinfo.viewport_mode,
579 video_subsystem,
580 resources : &mut resources
581 });
582 }
583 else
584 {
585 #[cfg(feature = "audio")]
586 {
587 audio = Box::new(NullAudio::new());
588 }
589 input = Input::new(None);
590 controller_subsystem = None;
591 }
592
593 #[cfg(feature = "imgui_feature")]
594 let mut imgui_handler = ImGuiHandler::new().map_err(
595 |error| InitError::ImguiInitError(error))?;
596
597 #[allow(unused_mut)] let mut renderer = Renderer::new(renderer_new_info).map_err(
599 |error| InitError::RendererInitError(error))?;
600
601 #[cfg(feature = "imgui_feature")]
602 renderer.control().init_imgui_renderer(imgui_handler.imgui_mut()).map_err(
603 |error| InitError::RendererInitError(error))?;
604
605 input.update_mouse_metrics(&renderer);
606
607 Ok(GameControl
608 {
609 target_framerate : gameinfo.target_framerate,
610 frame_interpolation : gameinfo.frame_interpolation,
611 interpolation_cap : gameinfo.frame_interpolation_cap,
612 interpolation_pacing : gameinfo.frame_interpolation_pacing,
613 low_energy_app : gameinfo.low_energy_app,
614 low_energy_timer : 0,
615 auto_unload : gameinfo.auto_unload,
616 input,
617 renderer,
618 #[cfg(feature = "audio")]
619 audio,
620 controller_subsystem,
621 controllers : HashMap::new(),
622 next_scene : Box::new(Scene::<PlaceholderScene>::new()),
623 next_scene_waiting : false,
624 source_manager,
625 resources,
626 console_history : options.console_history,
627 stats : GameStats::new(),
628 focused : true,
629 done : false,
630 singletons : HashMap::new(),
631 #[cfg(feature = "imgui_feature")]
632 imgui_handler : Some(imgui_handler),
633 })
634 }
635
636 pub fn new_headless(gameinfo : &GameInfo) -> Result<GameControl, InitError>
639 {
640 GameControl::new(gameinfo, GameControlOptions::headless())
641 }
642
643 pub fn input(&self) -> &Input
645 {
646 &self.input
647 }
648
649 pub fn input_mut(&mut self) -> &mut Input
651 {
652 &mut self.input
653 }
654
655 pub fn renderer(&self) -> &Renderer
657 {
658 &self.renderer
659 }
660
661 pub fn renderer_mut(&mut self) -> &mut Renderer
663 {
664 &mut self.renderer
665 }
666
667 #[cfg(feature = "audio")]
669 pub fn audio(&self) -> &Box<dyn Audio>
670 {
671 &self.audio
672 }
673
674 #[cfg(feature = "audio")]
676 pub fn audio_mut(&mut self) -> &mut Box<dyn Audio>
677 {
678 &mut self.audio
679 }
680
681 #[cfg(feature = "audio")]
682 fn sync_audio_store(&mut self)
683 {
684 self.audio.sync_sound_store(&mut self.resources.store_mut())
685 .expect("Audio backend store sync failed");
686 }
687
688 pub fn source_manager(&self) -> Rc<RefCell<SourceManager>>
690 {
691 self.source_manager.clone()
692 }
693
694 pub fn res(&self) -> &DataMultistore
696 {
697 &self.resources
698 }
699
700 #[allow(dead_code)]
701 pub(crate) fn res_mut(&mut self) -> &mut DataMultistore
702 {
703 &mut self.resources
704 }
705
706 pub fn singleton_mut<T : 'static + GameSingleton + Default>(&mut self) -> &mut T
709 {
710 let type_id = TypeId::of::<T>();
711
712 if !self.singletons.contains_key(&type_id)
713 {
714 self.singletons.insert(type_id.clone(), Box::new(T::default()));
715 }
716
717 self.singletons.get_mut(&type_id).unwrap().downcast_mut::<T>().expect("Downcast failed!")
718 }
719
720 #[cfg(feature = "imgui_feature")]
722 pub fn imgui(&self) -> Option<&imgui::Context>
723 {
724 if let Some(imgui_handler) = &self.imgui_handler
725 {
726 return Some(imgui_handler.imgui());
727 }
728
729 None
730 }
731
732 #[cfg(feature = "imgui_feature")]
734 pub fn imgui_mut(&mut self) -> Option<&mut imgui::Context>
735 {
736 if let Some(imgui_handler) = &mut self.imgui_handler
737 {
738 return Some(imgui_handler.imgui_mut());
739 }
740
741 None
742 }
743
744 pub fn target_framerate(&self) -> f64
746 {
747 self.target_framerate
748 }
749
750 pub fn set_target_framerate(&mut self, new_framerate : f64)
752 {
753 self.target_framerate = new_framerate.clamp(5.0, 1000.0);
754 }
755
756 pub fn frame_interpolation(&self) -> FrameInterpolationMode
758 {
759 self.frame_interpolation
760 }
761
762 pub fn set_frame_interpolation(&mut self, mode : FrameInterpolationMode)
764 {
765 self.frame_interpolation = mode;
766 }
767
768 pub fn frame_interpolation_cap(&self) -> FrameInterpolationCap
770 {
771 self.interpolation_cap
772 }
773
774 pub fn set_frame_interpolation_cap(&mut self, mut cap : FrameInterpolationCap)
776 {
777 if let FrameInterpolationCap::Manual(value) = cap
778 {
779 cap = FrameInterpolationCap::Manual(value.clamp(5.0, 1000.0));
780 }
781
782 self.interpolation_cap = cap;
783 }
784
785 pub fn frame_interpolation_pacing(&self) -> FrameInterpolationPacing
787 {
788 self.interpolation_pacing
789 }
790
791 pub fn set_frame_interpolation_pacing(&mut self, pacing : FrameInterpolationPacing)
793 {
794 self.interpolation_pacing = pacing;
795 }
796
797 pub fn frame_interpolation_active(&self) -> bool
801 {
802 match self.frame_interpolation
803 {
804 FrameInterpolationMode::Off => { return false; }
805 FrameInterpolationMode::Automatic =>
806 {
807 let current_refresh_rate = self.renderer.current_refresh_rate();
808
809 if current_refresh_rate == 0
810 {
811 return false;
812 }
813
814 ((self.target_framerate.round() as i32) - current_refresh_rate).abs() > 1
815 }
816 FrameInterpolationMode::Force => { return true; }
817 }
818 }
819
820 pub fn console_history(&self) -> MutexGuard<'_, ConsoleHistory>
822 {
823 self.console_history.lock().unwrap()
824 }
825
826 pub fn stats(&self) -> &GameStats
828 {
829 &self.stats
830 }
831
832 pub fn goto_new_scene<T : SceneType + 'static>(&mut self)
837 {
838 let next_scene = Box::new(Scene::<T>::new());
839
840 self.next_scene = next_scene;
841 self.next_scene_waiting = true;
842 }
843
844 pub fn quit(&mut self)
846 {
847 self.done = true;
848 }
849}
850
851struct GameloopTimeState
852{
853 jiffies : Instant,
854 last_jiffies : Instant,
855 last_fps_jiffies : Instant,
856 jiffy_queue : Duration,
857 target_frametime : Duration,
858 cap_frametime : Duration,
859 frame_start_jiffies : Duration,
860 first_draw_jiffies : Duration,
861 last_display : Instant,
862 #[cfg(feature = "imgui_feature")]
863 last_imgui : Instant,
864 lag_this_frame : u32,
865 lag_this_second : u32,
866 thought_yet : bool,
867 drawn_thought_yet : bool,
868 lag_recovery : bool,
869 refresh_rate : i32,
870 think_timer : GameloopStopwatch,
871 draw_timer : GameloopStopwatch,
872 wait_timer : GameloopStopwatch,
873 present_timer : GameloopStopwatch
874}
875
876impl GameloopTimeState
877{
878 fn new() -> GameloopTimeState
879 {
880 let jiffies = Instant::now();
881 let last_jiffies = jiffies;
882
883 GameloopTimeState
884 {
885 jiffies,
886 last_jiffies,
887 last_fps_jiffies : Instant::now(),
888 jiffy_queue : Duration::new(0, 0),
889 target_frametime : Duration::new(0, 0),
890 cap_frametime : Duration::new(0, 0),
891 frame_start_jiffies : Duration::new(0, 0),
892 first_draw_jiffies : Duration::new(0, 0),
893 last_display : Instant::now(),
894 #[cfg(feature = "imgui_feature")]
895 last_imgui : Instant::now(),
896 lag_this_frame : 0,
897 lag_this_second : 0,
898 thought_yet : false,
899 drawn_thought_yet : false,
900 lag_recovery : false,
901 refresh_rate : 0,
902 think_timer : GameloopStopwatch::new(),
903 draw_timer : GameloopStopwatch::new(),
904 wait_timer : GameloopStopwatch::new(),
905 present_timer : GameloopStopwatch::new()
906 }
907 }
908
909 fn flush(&mut self)
910 {
911 if self.jiffy_queue > self.target_frametime * MAX_FRAME_JIFFY_SUBTRACT
912 {
913 self.jiffy_queue = Duration::new(0, 0);
914 }
915 else
916 {
917 while self.jiffy_queue > self.target_frametime
918 {
919 self.jiffy_queue -= self.target_frametime;
920 }
921 }
922
923 self.new_second();
924
925 self.jiffies = Instant::now();
926 self.last_jiffies = self.jiffies;
927 self.last_fps_jiffies = Instant::now();
928 self.last_display = self.jiffies;
929 self.thought_yet = false;
930 }
931
932 fn new_second(&mut self)
933 {
934 self.think_timer.new_second();
935 self.draw_timer.new_second();
936 self.wait_timer.new_second();
937 self.present_timer.new_second();
938
939 if self.jiffies > self.last_fps_jiffies + Duration::from_secs(MAX_FPS_STAT_CATCHUP)
940 {
941 self.last_fps_jiffies = self.jiffies;
942 }
943 else
944 {
945 self.last_fps_jiffies += Duration::from_secs(1);
946 }
947
948 self.lag_this_second = 0;
949 }
950
951 fn update_jiffies(&mut self)
952 {
953 self.jiffies = Instant::now();
954 self.jiffy_queue += self.jiffies - self.last_jiffies;
955 self.last_jiffies = self.jiffies;
956 }
957
958 fn sleep(&mut self, framerate_wait_target : Duration, low_power : bool)
959 {
960 while self.jiffy_queue <= framerate_wait_target
961 {
962 self.update_jiffies();
963
964 let remaining = framerate_wait_target.saturating_sub(self.jiffy_queue);
965
966 if remaining >= Duration::from_millis(BUSY_WAIT_MARGIN) || low_power
968 {
969 std::thread::sleep(Duration::from_millis(1));
970 }
971 }
972 }
973
974 fn interpolate_sleep(&mut self, framerate_wait_target : Duration, low_power : bool)
975 {
976 let mut current_display = Instant::now();
977
978 while current_display - self.last_display <= framerate_wait_target
979 {
980 let remaining = framerate_wait_target.saturating_sub(current_display - self.last_display);
981
982 if remaining >= Duration::from_millis(BUSY_WAIT_MARGIN) || low_power
984 {
985 std::thread::sleep(Duration::from_millis(1));
986 }
987
988 current_display = Instant::now();
989 }
990
991 self.last_display = Instant::now();
992 }
993}
994
995#[derive(Clone)]
997pub struct GameStats
998{
999 pub draw_fps : u32,
1000 pub think_fps : u32,
1001 pub frame_ms_think : f64,
1002 pub frame_ms_draw : f64,
1003 pub frame_ms_present : f64,
1004 pub frame_ms_wait : f64,
1005 pub average_ms_think : f64,
1006 pub average_ms_draw : f64,
1007 pub average_ms_present : f64,
1008 pub average_ms_wait : f64
1009}
1010
1011impl GameStats
1012{
1013 pub fn new() -> GameStats
1014 {
1015 GameStats
1016 {
1017 draw_fps : 0,
1018 think_fps : 0,
1019 frame_ms_think : 0.0,
1020 frame_ms_draw : 0.0,
1021 frame_ms_present : 0.0,
1022 frame_ms_wait : 0.0,
1023 average_ms_think : 0.0,
1024 average_ms_draw : 0.0,
1025 average_ms_present : 0.0,
1026 average_ms_wait : 0.0
1027 }
1028 }
1029
1030 fn update_frame_timings(&mut self, time : &GameloopTimeState)
1031 {
1032 self.frame_ms_think = time.think_timer.current_ms();
1033 self.frame_ms_draw = time.draw_timer.current_ms();
1034 self.frame_ms_present = time.present_timer.current_ms();
1035 self.frame_ms_wait = time.wait_timer.current_ms();
1036 }
1037
1038 fn update_second_timings(&mut self, time : &GameloopTimeState)
1039 {
1040 self.draw_fps = time.draw_timer.frames_this_second();
1041 self.think_fps = time.think_timer.tics_this_second();
1042 self.average_ms_think = time.think_timer.average_ms();
1043 self.average_ms_draw = time.draw_timer.average_ms();
1044 self.average_ms_present = time.present_timer.average_ms();
1045 self.average_ms_wait = time.wait_timer.average_ms();
1046 }
1047}
1048
1049pub struct Gameloop
1052{
1053 #[allow(dead_code)] sdl_context : sdl2::Sdl,
1055 event_pump : sdl2::EventPump,
1056 event_queue : Vec<sdl2::event::Event>,
1057 control : Box<GameControl>,
1058 current_scene : Box<dyn BaseScene>,
1059 time : Box<GameloopTimeState>,
1060 log_stats : bool,
1061 kiosk_mode : bool
1062}
1063
1064impl Gameloop
1065{
1066 pub fn new(gameinfo : GameInfo) -> Gameloop
1068 {
1069 Gameloop::try_new(gameinfo).unwrap_or_else(|error| panic!("Startup failed: {}", error))
1070 }
1071
1072 pub fn try_new(gameinfo : GameInfo) -> Result<Gameloop, InitError>
1074 {
1075 let gameloop_init_start = Instant::now();
1076
1077 #[cfg(feature = "graphical_panic")]
1079 std::panic::set_hook(Box::new(panic_hook));
1080
1081 let mut gameinfo = gameinfo.clone();
1082
1083 if gameinfo.package_name.is_empty()
1084 {
1085 return Err(InitError::KeeshondInitError(String::from("Package name cannot be empty.")));
1086 }
1087
1088 let console_history = Arc::new(Mutex::new(ConsoleHistory::new()));
1089
1090 #[cfg(feature = "default_logger")]
1091 {
1092 let mut logger = doglog::DogLog::new(console_history.clone());
1093 logger.add_package_filter(String::from(gameinfo.package_name));
1094
1095 for package in &gameinfo.log_packages
1096 {
1097 logger.add_package_filter(package.to_string());
1098 }
1099
1100 #[cfg(debug_assertions)]
1101 let filter = log::LevelFilter::Debug;
1102 #[cfg(not(debug_assertions))]
1103 let filter = log::LevelFilter::Info;
1104
1105 if let Err(_) = logger.install(filter)
1106 {
1107 eprintln!("Could not initialize default logger.");
1108 }
1109 }
1110
1111 apply_config(&mut gameinfo);
1112
1113 print_log_section("KEESHOND Game Engine");
1114
1115 let version = sdl2::version::version();
1116 info!("Starting SDL version {}", version);
1117
1118 let sdl_context = sdl2::init().map_err(
1120 |error| InitError::Sdl2InitError(Sdl2Subsystem::Main, error))?;
1121 let event_pump = sdl_context.event_pump().map_err(
1122 |error| InitError::Sdl2InitError(Sdl2Subsystem::Event, error))?;
1123
1124 #[allow(unused_mut)] let mut control = Box::new(GameControl::new(&gameinfo, GameControlOptions
1126 {
1127 console_history,
1128 sdl_context : Some(&sdl_context)
1129 }).map_err(|error| error)?);
1130
1131 control.renderer_mut().set_base_size(gameinfo.base_width as f32, gameinfo.base_height as f32);
1132 control.renderer_mut().set_window_title(&gameinfo.friendly_name);
1133
1134 let gameloop_init_time = (Instant::now() - gameloop_init_start).as_millis();
1135
1136 info!("Initialized in {} ms", gameloop_init_time);
1137
1138 Ok(Gameloop
1139 {
1140 sdl_context,
1141 event_pump,
1142 event_queue : Vec::new(),
1143 control,
1144 current_scene : Box::new(Scene::<PlaceholderScene>::new()),
1145 time : Box::new(GameloopTimeState::new()),
1146 log_stats : gameinfo.log_stats,
1147 kiosk_mode : gameinfo.kiosk_mode
1148 })
1149 }
1150
1151 pub fn control(&self) -> &GameControl
1152 {
1153 &self.control
1154 }
1155
1156 pub fn control_mut(&mut self) -> &mut GameControl
1157 {
1158 &mut self.control
1159 }
1160
1161 fn think(&mut self)
1162 {
1163 let target_ms = 1000.0 / self.control.target_framerate();
1164 let cap_ms = match self.control.frame_interpolation_cap()
1165 {
1166 FrameInterpolationCap::Automatic =>
1167 {
1168 1000.0 / (self.control.renderer.current_refresh_rate() as f64
1169 * AUTOMATIC_LERP_CAP_MULTIPLIER)
1170 }
1171 FrameInterpolationCap::Manual(value) => { 1000.0 / value }
1172 FrameInterpolationCap::None => { 0.0 }
1173 };
1174 self.time.target_frametime = Duration::from_nanos((target_ms * 1_000_000.0) as u64);
1175 self.time.cap_frametime = Duration::from_nanos((cap_ms * 1_000_000.0) as u64);
1176
1177 if self.time.jiffy_queue <= self.time.target_frametime
1178 {
1179 return;
1180 }
1181
1182 self.time.think_timer.begin_frame_timing();
1183
1184 #[cfg(feature = "audio")]
1185 self.control.sync_audio_store();
1186
1187 self.time.lag_this_frame = 0;
1188
1189 while self.time.jiffy_queue > self.time.target_frametime
1190 {
1191 if self.time.lag_recovery
1193 {
1194 if self.time.jiffy_queue > self.time.target_frametime * MAX_POST_SLOWDOWN_CATCHUP
1195 {
1196 self.time.jiffy_queue = self.time.target_frametime * MAX_POST_SLOWDOWN_CATCHUP;
1197 }
1198
1199 self.time.lag_recovery = false;
1200 }
1201
1202 self.control.input.update_actions();
1203
1204 self.current_scene.think(&mut self.control);
1205
1206 self.time.think_timer.tic();
1207 self.time.thought_yet = true;
1208 self.time.drawn_thought_yet = false;
1209 self.control.low_energy_timer += 1;
1210
1211 let max_think_jiffies = self.time.target_frametime;
1212
1213 if self.time.think_timer.jiffies_this_frame() > max_think_jiffies + Duration::from_millis(MAX_HITCH_MS)
1222 || (self.time.think_timer.jiffies_this_frame() > max_think_jiffies
1223 && self.time.think_timer.jiffies_last_frame > max_think_jiffies)
1224 {
1225 while self.time.jiffy_queue > self.time.target_frametime
1226 {
1227 self.time.jiffy_queue -= self.time.target_frametime;
1228 self.time.lag_this_frame += 1;
1229 self.time.lag_this_second += 1;
1230 self.time.lag_recovery = true;
1231
1232 if self.time.lag_this_frame > MAX_FRAME_JIFFY_SUBTRACT
1234 {
1235 break;
1236 }
1237 }
1238 break;
1239 }
1240
1241 self.time.jiffy_queue -= self.time.target_frametime;
1242 }
1243
1244 self.time.think_timer.end_frame_timing();
1245
1246 self.time.frame_start_jiffies = self.time.jiffy_queue;
1247 }
1248
1249 fn draw(&mut self)
1250 {
1251 self.time.draw_timer.begin_frame_timing();
1252
1253 #[cfg(feature = "imgui_feature")]
1254 {
1255 if !self.imgui_draw()
1256 {
1257 self.scene_draw();
1258 }
1259 }
1260
1261 #[cfg(not(feature = "imgui_feature"))]
1262 self.scene_draw();
1263
1264 if self.time.thought_yet && self.should_draw()
1265 {
1266 self.control.renderer.control().end_drawing();
1267 }
1268
1269 self.time.draw_timer.end_frame_timing();
1270
1271 if !self.time.drawn_thought_yet
1272 {
1273 self.time.first_draw_jiffies = self.time.draw_timer.jiffies_this_frame;
1274 self.time.drawn_thought_yet = true;
1275 }
1276 }
1277
1278 fn scene_draw(&mut self)
1279 {
1280 if self.time.thought_yet && self.should_draw()
1281 {
1282 let mut interpolation_amount = 0.0;
1283
1284 if self.control.frame_interpolation_active() && self.time.lag_this_frame == 0
1285 && !self.low_energy_timer_expired()
1286 {
1287 let jiffy_offset = self.time.jiffy_queue.as_nanos() as f64;
1288 let jiffy_target = self.time.target_frametime.as_nanos() as f64;
1289
1290 interpolation_amount = ((jiffy_offset / jiffy_target) - 1.0)
1291 .clamp(-1.0, 0.0);
1292 }
1293
1294 self.control.renderer.draw(&mut self.current_scene, &mut self.control.resources, interpolation_amount as f32);
1295 }
1296 }
1297
1298 #[cfg(feature = "imgui_feature")]
1299 fn imgui_draw(&mut self) -> bool
1300 {
1301 let mut imgui_handler : Option<ImGuiHandler> = None;
1302
1303 let frametime = self.time.target_frametime * self.time.think_timer.tics_this_frame();
1304 let (width, height) = self.control.renderer.window_size();
1305 let scale = self.control.renderer.current_display_scale_factor();
1306
1307 let frame_delta = match self.control.frame_interpolation_active()
1308 {
1309 false => { frametime.as_nanos() as f64 * MILLISECONDS_PER_NANOSECOND / 1000.0 }
1310 true =>
1311 {
1312 let now = Instant::now();
1313 let delta = now - self.time.last_imgui;
1314 self.time.last_imgui = now;
1315
1316 delta.as_nanos() as f64 * MILLISECONDS_PER_NANOSECOND / 1000.0
1317 }
1318 };
1319
1320 std::mem::swap(&mut imgui_handler, &mut self.control.imgui_handler);
1321
1322 if let Some(imgui_some) = &mut imgui_handler
1323 {
1324 imgui_some.update_size_and_delta(frame_delta as f32, width as f32, height as f32, scale);
1325 self.control.renderer.update_imgui(imgui_some.imgui_mut());
1326
1327 let mut ui = imgui_some.imgui_mut().frame();
1328
1329 self.current_scene.imgui_think(&mut ui, &mut self.control);
1330
1331 self.scene_draw();
1332
1333 let imgui_cursor = ui.mouse_cursor();
1334
1335 self.control.renderer.draw_imgui(ui, &mut self.control.resources);
1336
1337 imgui_some.update_cursor(&mut self.sdl_context.mouse(), imgui_cursor);
1338 }
1339
1340 std::mem::swap(&mut imgui_handler, &mut self.control.imgui_handler);
1341
1342 true
1343 }
1344
1345 fn wait(&mut self, do_sleep : bool)
1346 {
1347 if !do_sleep
1348 {
1349 self.time.update_jiffies();
1350 return;
1351 }
1352
1353 self.time.wait_timer.begin_frame_timing();
1354
1355 let low_energy = self.low_energy_timer_expired();
1356
1357 if !self.control.frame_interpolation_active() || low_energy
1358 {
1359 self.time.sleep(self.time.target_frametime, low_energy);
1360 }
1361 else
1362 {
1363 let mut cap_frametime = self.time.cap_frametime;
1364
1365 if self.control.interpolation_pacing != FrameInterpolationPacing::PreferHighFramerate
1366 {
1367 let pacing_target = self.time.think_timer.jiffies_this_frame
1368 + self.time.first_draw_jiffies;
1369
1370 cap_frametime = self.time.cap_frametime.max(pacing_target)
1371 .min(self.time.target_frametime);
1372 }
1373
1374 if !cap_frametime.is_zero()
1375 {
1376 self.time.interpolate_sleep(cap_frametime, low_energy);
1377 }
1378 else
1379 {
1380 self.time.last_display = Instant::now();
1381 }
1382
1383 self.time.update_jiffies();
1384 }
1385
1386 self.time.wait_timer.end_frame_timing();
1387 }
1388
1389 fn present(&mut self)
1390 {
1391 self.time.present_timer.begin_frame_timing();
1392
1393 if self.should_draw()
1394 {
1395 self.control.renderer.present();
1396 }
1397
1398 self.time.present_timer.end_frame_timing();
1399 }
1400
1401 fn low_energy_timer_expired(&self) -> bool
1402 {
1403 self.control.low_energy_app && self.control.low_energy_timer > LOW_ENERGY_TIMER_MIN
1404 }
1405
1406 fn should_draw(&self) -> bool
1407 {
1408 !self.low_energy_timer_expired() || self.control.low_energy_timer % LOW_ENERGY_DRAW_RATE == 0
1409 }
1410
1411 fn process_event(&mut self, event : Event) -> bool
1412 {
1413 self.control.low_energy_timer = 0;
1414
1415 #[cfg(feature = "imgui_feature")]
1416 {
1417 if let Some(imgui_handler) = &mut self.control.imgui_handler
1418 {
1419 if imgui_handler.handle_event(&event)
1420 {
1421 return true;
1422 }
1423 }
1424 }
1425
1426 match event
1427 {
1428 Event::Quit { .. } =>
1429 {
1430 if !self.kiosk_mode
1431 {
1432 return false;
1433 }
1434 },
1435 Event::Window { win_event, .. } => match win_event
1436 {
1437 WindowEvent::FocusGained { .. } =>
1438 {
1439 self.control.focused = true;
1440 },
1441 WindowEvent::FocusLost { .. } =>
1442 {
1443 self.control.focused = false;
1444 },
1445 WindowEvent::Moved { .. } | WindowEvent::SizeChanged { .. } =>
1446 {
1447 self.control.renderer.recalculate_viewport();
1448 self.control.input.update_mouse_metrics(&self.control.renderer);
1449 }
1450 _ => ()
1451 },
1452 Event::KeyDown { keycode, keymod, repeat, .. } =>
1453 {
1454 if !repeat && (keymod.contains(KeyboardMod::LALTMOD)
1455 || keymod.contains(KeyboardMod::RALTMOD))
1456 && keycode == Some(sdl2::keyboard::Keycode::Return)
1457 && !self.kiosk_mode
1458 {
1459 self.control.renderer.toggle_fullscreen();
1460 }
1461 else if !repeat
1462 {
1463 if let Some(some_keycode) = keycode
1464 {
1465 self.control.input.update_key(&some_keycode.name(), true, true);
1466 }
1467 }
1468 },
1469 Event::KeyUp { keycode, .. } =>
1470 {
1471 if let Some(some_keycode) = keycode
1472 {
1473 self.control.input.update_key(&some_keycode.name(), false, true);
1474 }
1475 },
1476 Event::MouseMotion { x, y, xrel, yrel, .. } =>
1477 {
1478 self.control.input.update_mouse_movement(x, y, xrel, yrel);
1479 },
1480 Event::MouseButtonDown { mouse_btn, .. } =>
1481 {
1482 self.control.input.update_mouse_button(mouse_btn, true);
1483 },
1484 Event::MouseButtonUp { mouse_btn, .. } =>
1485 {
1486 self.control.input.update_mouse_button(mouse_btn, false);
1487 },
1488 Event::MouseWheel { x, y, .. } =>
1489 {
1490 self.control.input.update_mouse_wheel(x, y);
1491 },
1492 Event::ControllerDeviceAdded { which, .. } =>
1493 {
1494 if let Some(controller_subsystem) = &self.control.controller_subsystem
1495 {
1496 match controller_subsystem.open(which)
1497 {
1498 Ok(controller) =>
1499 {
1500 info!("Attached controller {}: {}", which, controller.name());
1501 self.control.controllers.insert(which, controller);
1502 },
1503 Err(error) =>
1504 {
1505 error!("Could not open controller: {}", error.to_string());
1506 }
1507 }
1508 }
1509 }
1510 Event::ControllerDeviceRemoved { which, .. } =>
1511 {
1512 if let Some(controller) = self.control.controllers.remove(&which)
1513 {
1514 info!("Detached controller {}: {}", which, controller.name());
1515 }
1516 }
1517 Event::ControllerButtonDown { button, .. } =>
1518 {
1519 self.control.input.update_gamepad_key(button, true);
1520 },
1521 Event::ControllerButtonUp { button, .. } =>
1522 {
1523 self.control.input.update_gamepad_key(button, false);
1524 },
1525 Event::ControllerAxisMotion { axis, value, .. } =>
1526 {
1527 self.control.input.update_gamepad_axis(axis, value);
1528 },
1529 _ => ()
1530 }
1531
1532 return true;
1533 }
1534
1535 fn update_fps(&mut self)
1536 {
1537 self.control.stats.update_frame_timings(&self.time);
1538
1539 let current_refresh_rate = self.control.renderer.current_refresh_rate();
1540
1541 if self.time.refresh_rate != current_refresh_rate
1542 {
1543 self.time.refresh_rate = current_refresh_rate;
1544 info!("Refresh rate: {}hz", current_refresh_rate);
1545 }
1546
1547 if self.time.jiffies < self.time.last_fps_jiffies + Duration::from_secs(1)
1548 {
1549 return;
1550 }
1551
1552 self.control.stats.update_second_timings(&self.time);
1553
1554 let lerp_active = match self.control.frame_interpolation_active()
1555 {
1556 true => { "ON" }
1557 false => { "OFF" }
1558 };
1559
1560 if self.log_stats
1561 {
1562 debug!("{} ent {} com | {} / {} FPS | avg ms think {:.2} draw {:.2} present {:.2} wait {:.2} | lerp {}",
1563 self.current_scene.entity_count(),
1564 self.current_scene.component_count(),
1565 self.time.draw_timer.frames_this_second(),
1566 self.time.think_timer.tics_this_second(),
1567 self.time.think_timer.average_ms(),
1568 self.time.draw_timer.average_ms(),
1569 self.time.present_timer.average_ms(),
1570 self.time.wait_timer.average_ms(),
1571 lerp_active);
1572 }
1573
1574 #[cfg(debug_assertions)]
1575 if self.time.lag_this_second > 0
1576 {
1577 warn!("Too much logic processing, game is slowing down!");
1578 }
1579
1580 self.time.new_second();
1581 }
1582
1583 pub fn single_frame(&mut self, do_sleep : bool) -> bool
1584 {
1585 if self.low_energy_timer_expired() && !self.control.focused && do_sleep
1586 {
1587 self.event_queue.push(self.event_pump.wait_event());
1588
1589 self.time.flush();
1590 }
1591
1592 self.control.input.pre_update();
1593
1594 for event in self.event_pump.poll_iter()
1596 {
1597 self.event_queue.push(event);
1598 }
1599
1600 let mut event_queue = Vec::new();
1601
1602 std::mem::swap(&mut self.event_queue, &mut event_queue);
1603
1604 for event in event_queue.drain(..)
1605 {
1606 if !self.process_event(event)
1607 {
1608 return false;
1609 }
1610 }
1611
1612 std::mem::swap(&mut self.event_queue, &mut event_queue);
1613
1614 self.think();
1615 self.draw();
1616 self.wait(do_sleep);
1617 self.present();
1618
1619 self.update_fps();
1620
1621 if self.control.next_scene_waiting
1623 {
1624 self.current_scene.end(&mut self.control);
1625
1626 if self.control.auto_unload
1627 {
1628 self.control.resources.unload_unused();
1629 }
1630
1631 print_log_section("NEW SCENE");
1632
1633 std::mem::swap(&mut self.current_scene, &mut self.control.next_scene);
1634 self.control.next_scene = Box::new(Scene::<PlaceholderScene>::new());
1635
1636 let scene_init_start = Instant::now();
1637
1638 self.current_scene.start(&mut self.control);
1639
1640 let scene_init_time = (Instant::now() - scene_init_start).as_millis();
1641 info!("Scene started in {} ms", scene_init_time);
1642
1643 self.time.flush();
1645 self.control.next_scene_waiting = false;
1646 }
1647
1648 return !self.control.done;
1649 }
1650
1651 #[allow(unused_mut)]
1652 pub fn run(mut self)
1653 {
1654 #[cfg(not(target_os = "emscripten"))]
1655 self.run_desktop();
1656 #[cfg(target_os = "emscripten")]
1657 self.run_web();
1658 }
1659
1660 #[cfg(not(target_os = "emscripten"))]
1661 pub fn run_desktop(&mut self)
1662 {
1663 print_log_section("START GAMELOOP");
1664
1665 self.time = Box::new(GameloopTimeState::new());
1666
1667 loop
1668 {
1669 if !self.single_frame(true)
1670 {
1671 break;
1672 }
1673 }
1674
1675 print_log_section("END GAMELOOP");
1676
1677 self.current_scene.end(&mut self.control);
1678 }
1679
1680 #[cfg(target_os = "emscripten")]
1681 pub fn run_web(mut self)
1682 {
1683 print_log_section("START GAMELOOP");
1684
1685 self.time = Box::new(GameloopTimeState::new());
1686
1687 let loop_closure = move ||
1688 {
1689 match self.single_frame(false)
1690 {
1691 true =>
1692 {
1693 crate::thirdparty::emscripten::MainLoopEvent::Continue
1694 },
1695 false =>
1696 {
1697 self.current_scene.end(&mut self.control);
1698 return crate::thirdparty::emscripten::MainLoopEvent::Terminate;
1699 }
1700 }
1701 };
1702
1703 crate::thirdparty::emscripten::set_main_loop_callback(loop_closure);
1704 }
1705}