keeshond/
gameloop.rs

1//! Gameloop and [GameControl] context, which is used to control the game globally
2
3use 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; // For smooth results on Windows. Linux can probably get away with 1.
34const 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    // Get error message if that's what the payload happens to be
89    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    // Format with file/line info if available
99    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/// Displays a message popup in the GUI and prints the message to the console
132#[allow(unused_variables)]
133pub fn gui_cli_message(message : &str, title : &str)
134{
135    // Print to console first in case something weird happens
136    println!("{}", message);
137    
138    // Show dialog box
139    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    // Get commandline options
187    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/// Whether to use frame interpolation, which results in cleaner motion on high refresh rate displays for games that support it
239#[derive(Copy, Clone, Debug, PartialEq, Eq)]
240pub enum FrameInterpolationMode
241{
242    /// Never do frame interpolation (recommended for games that don't support frame interpolation)
243    Off,
244    /// Do frame interpolation if the display refresh rate doesn't match the game's target framerate (recommended for games that support frame interpolation)
245    Automatic,
246    /// Always do frame interpolation (not recommended, but since automatic detection doesn't work for web builds, you might want to include this as an in-game option)
247    Force
248}
249
250/// The framerate cap to use for when frame interpolation is enabled
251#[derive(Copy, Clone, Debug, PartialEq)]
252pub enum FrameInterpolationCap
253{
254    /// Sets the framerate limit to slightly above the monitor's current refresh rate. This improves the smoothness of
255    ///  lerping when there is significant logic processing.
256    Automatic,
257    /// Sets the framerate limit to the specified amount (between 5-1000 FPS)
258    Manual(f64),
259    /// Uncaps the framerate entirely (not recommended)
260    None
261}
262
263/// Whether to space out display frames for smoother motion when the framerate is higher than the target rate and there is significant logic processing.
264#[derive(Copy, Clone, Debug, PartialEq)]
265pub enum FrameInterpolationPacing
266{
267    /// Regulate the timing of display frames so that they are more evenly spaced apart, albeit
268    ///  lowering the framerate (except when it falls below the target framerate).
269    PreferSmoothMotion,
270    /// Don't regulate the timing of display frames, increasing framerate.
271    PreferHighFramerate
272}
273
274/// Properties and metadata for this game
275#[derive(Clone)]
276pub struct GameInfo
277{
278    /// The name of the Rust package, used for some internal operations
279    pub package_name : &'static str,
280    /// Friendly, user-facing name, displayed in the window title
281    pub friendly_name : &'static str,
282    /// Base resolution width of the game graphics
283    pub base_width : u32,
284    /// Base resolution height of the game graphics
285    pub base_height : u32,
286    /// The window scale strategy to use when creating the window
287    pub default_window_scale : WindowScaleMode,
288    /// Whether to use texture filtering. Set to false for pixel art style games.
289    pub texture_filtering : bool,
290    /// The viewport mode to use, which determines how the game scales to different
291    ///  resolutions.
292    pub viewport_mode : ViewportMode,
293    /// Whether to default to fullscreen as opposed to windowed mode
294    pub fullscreen : bool,
295    /// The framerate that game logic should run at.
296    pub target_framerate : f64,
297    /// Unlock display framerate from logic and enable frame interpolation for
298    ///  alternate refresh rates or Variable Refresh Rate displays.
299    pub frame_interpolation : FrameInterpolationMode,
300    /// Optional framerate cap for frame interpolation mode, useful for keeping the
301    ///  display framerate below the maximum supported VRR rate.
302    pub frame_interpolation_cap : FrameInterpolationCap,
303    /// Whether to space out display frames for smoother motion when the framerate is higher than
304    ///  the target rate and there is significant logic processing.
305    pub frame_interpolation_pacing : FrameInterpolationPacing,
306    /// Whether to display the mouse cursor when it is over the window.
307    pub cursor_visible : bool,
308    /// Whether unused resources should be automatically unloaded between scenes.
309    pub auto_unload : bool,
310    /// Whether the computer should automatically go to sleep while the application is
311    ///  running. Useful for editors and other utilities.
312    pub allow_system_sleep : bool,
313    /// Whether to reduce the framerate when the user is not interacting with the application.
314    ///  Also pauses the application when it is not focused unless there is interaction.
315    ///  Useful for editors and other utilities.
316    pub low_energy_app : bool,
317    /// Whether to print basic stats (number of entities and components, framerate, basic timing information)
318    ///  to the log. Does nothing on release builds.
319    pub log_stats : bool,
320    /// A list of additional packages to include in the filter used by the default logger (DogLog).
321    pub log_packages : Vec<&'static str>,
322    /// Enable kiosk mode, which forces fullscreen and disables closing the window.
323    ///  Note that the user may still be able to close the program depending on your
324    ///  operating environment's global shortcuts.
325    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
357/// (Advanced) Provides basic diagnostic timekeeping facilities
358pub 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
470/// Trait for a struct that stores data global to the game - i.e. presists between scene switches,
471///  and is accessible using [GameControl::singleton_mut()].
472pub 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    /// Returns a configuration for creating the [GameControl] in headless mode (i.e. no window, renderer, or audio)
488    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
498/// Main context, for controlling the game while it is running. You don't normally need to create
499///  this yourself as [Gameloop::new()] does this for you, but it can be useful to create this
500///  directly if you need to run automated tests.
501pub 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)] // mut used only by imgui feature
598        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    /// Creates a [GameControl] in a headless configuration, allowing you to run automated tests,
637    ///  validation, or other processes without needing a gameloop, window, renderer, or audio.
638    pub fn new_headless(gameinfo : &GameInfo) -> Result<GameControl, InitError>
639    {
640        GameControl::new(gameinfo, GameControlOptions::headless())
641    }
642
643    /// Retrieves the [Input] object for reading player input such as keyboard, mouse, and gamepad.
644    pub fn input(&self) -> &Input
645    {
646        &self.input
647    }
648
649    /// Retrieves the [Input] object mutably for changing input bindings and deadzones
650    pub fn input_mut(&mut self) -> &mut Input
651    {
652        &mut self.input
653    }
654
655    /// Retrieves the [Renderer] object to read various parameters for the renderer window
656    pub fn renderer(&self) -> &Renderer
657    {
658        &self.renderer
659    }
660
661    /// Retrieves the [Renderer] object mutably to change various parameters for the renderer window
662    pub fn renderer_mut(&mut self) -> &mut Renderer
663    {
664        &mut self.renderer
665    }
666
667    /// Retrieves the [Audio] object for reading information about audio playback
668    #[cfg(feature = "audio")]
669    pub fn audio(&self) -> &Box<dyn Audio>
670    {
671        &self.audio
672    }
673
674    /// Retrieves the [Audio] object mutably to control audio playback
675    #[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    /// Retrieves the [SourceManager] for registering and querying data [Sources](crate::datapack::source::Source)
689    pub fn source_manager(&self) -> Rc<RefCell<SourceManager>>
690    {
691        self.source_manager.clone()
692    }
693
694    /// Retrieves the [DataMultistore] for accessing data resources
695    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    /// Retrieves the [GameSingleton] of the current type mutably. If it has not been created yet,
707    ///  it will be [Default]-constructed.
708    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    /// Retrieves the imgui context
721    #[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    /// Retrieves the imgui context mutably
733    #[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    /// Returns the framerate that game logic is synchronized to, in frames per second
745    pub fn target_framerate(&self) -> f64
746    {
747        self.target_framerate
748    }
749
750    /// Sets the framerate that game logic is synchronized to, in frames per second
751    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    /// Returns the mode to use for interpolating frames in the renderer.
757    pub fn frame_interpolation(&self) -> FrameInterpolationMode
758    {
759        self.frame_interpolation
760    }
761
762    /// Sets the mode to use for interpolating frames in the renderer.
763    pub fn set_frame_interpolation(&mut self, mode : FrameInterpolationMode)
764    {
765        self.frame_interpolation = mode;
766    }
767
768    /// Returns the framerate cap mode for when frame interpolation is enabled.
769    pub fn frame_interpolation_cap(&self) -> FrameInterpolationCap
770    {
771        self.interpolation_cap
772    }
773
774    /// Sets the framerate cap mode for when frame interpolation is enabled.
775    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    /// Returns the framerate pacing mode for when frame interpolation is enabled.
786    pub fn frame_interpolation_pacing(&self) -> FrameInterpolationPacing
787    {
788        self.interpolation_pacing
789    }
790
791    /// Sets the framerate pacing mode for when frame interpolation is enabled.
792    pub fn set_frame_interpolation_pacing(&mut self, pacing : FrameInterpolationPacing)
793    {
794        self.interpolation_pacing = pacing;
795    }
796
797    /// Returns true if frame interpolation is currently active. For the automatic mode, returns
798    ///  true if the refresh rate of the window's current monitor does not match with the target
799    ///  logic framerate.
800    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    /// Retrieves the [ConsoleHistory] object, which is useful for implementing an in-game console window
821    pub fn console_history(&self) -> MutexGuard<'_, ConsoleHistory>
822    {
823        self.console_history.lock().unwrap()
824    }
825
826    /// Retrieves the [GameStats] object, which contains timing statistics
827    pub fn stats(&self) -> &GameStats
828    {
829        &self.stats
830    }
831
832    /// Switches the game to a new scene of the given [SceneType] after the current frame is done.
833    ///  Doing this will perform automatic unloading of unused data resources (ones without an active
834    ///  [crate::datapack::DataHandle] or [crate::datapack::PackageUseToken]) if it is configured to
835    ///  do so via the [GameInfo] passed in during creation of the [Gameloop] or [GameControl].
836    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    /// Leaves the main gameloop. This usually means closing the game.
845    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            // Sleep as long as we reliably can, then busy wait
967            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            // Sleep as long as we reliably can, then busy wait
983            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/// Contains timing statistics such as FPS and milliseconds taken for each part of the gameloop.
996#[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
1049/// Holds the main game context and performs event handling and timekeeping
1050///  automatically. One of the first structs you will instantiate in your program.
1051pub struct Gameloop
1052{
1053    #[allow(dead_code)] // Only used by the imgui integration feature
1054    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    /// Creates the main game context. Will panic if startup fails for any reason.
1067    pub fn new(gameinfo : GameInfo) -> Gameloop
1068    {
1069        Gameloop::try_new(gameinfo).unwrap_or_else(|error| panic!("Startup failed: {}", error))
1070    }
1071
1072    /// Creates the main game context. Will return an error instead of panicking if startup fails for any reason.
1073    pub fn try_new(gameinfo : GameInfo) -> Result<Gameloop, InitError>
1074    {
1075        let gameloop_init_start = Instant::now();
1076
1077        // Messagebox panic handler
1078        #[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        // Subsystems init
1119        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)] // mut used only by imgui feature
1125        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            // Don't jump ahead too far after lag ends
1192            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            // Prevent overflow if CPU cannot process all the frames
1214            //  Scenario 1: Hitch for a single frame. Could be OS taking resource time away from
1215            //              the program for a brief moment, or a long initialization process. For
1216            //              this we should allow for a single shot of several frames worth of lag.
1217            //  Scenario 2: Continuous slow processing. Game is simply doing too much work over
1218            //              multiple frames. If previous think took longer than a frame and this
1219            //              think is also taking longer than a frame, stop processing for smoother
1220            //              slowdown should it occur.
1221            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                    // Don't spend a really long time if the jiffy queue happens to be really large
1233                    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        // Consume events
1595        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        // Go to next scene if one is requested
1622        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            // Reset jiffy queue
1644            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}