use crate::input::Input;
use crate::scene::{Scene, BaseScene, SceneType, ThinkerSystem};
use crate::audio::{Audio, AlAudio, NullAudio, AudioError};
use crate::renderer::{Renderer, RendererError, RendererNewInfo, ViewportMode};
#[cfg(feature = "imgui_base")] use crate::imgui_handler::ImGuiHandler;
#[cfg(feature = "default_logger")] use crate::doglog;
use sdl2::event::Event;
use sdl2::event::WindowEvent;
use sdl2::keyboard::Mod as KeyboardMod;
#[cfg(feature = "graphical_panic")] use sdl2::messagebox;
#[cfg(feature = "graphical_panic")] use std::panic::PanicInfo;
#[cfg(feature = "graphical_panic")] use backtrace::Backtrace;
use keeshond_datapack::DataMultistore;
use keeshond_datapack::source::SourceManager;
#[cfg(feature = "imgui_base")] use imgui;
use std::time::{Instant, Duration};
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashMap;
use std::any::TypeId;
use downcast_rs::Downcast;
const MILLISECONDS_PER_NANOSECOND : f64 = 0.000001;
const LOW_ENERGY_TIMER_MIN : u32 = 60;
const LOW_ENERGY_DRAW_RATE : u32 = 2;
#[derive(Debug)]
pub enum Sdl2Subsystem
{
Main,
Event,
Video,
GameController,
}
#[derive(Debug, Fail)]
pub enum InitError
{
#[fail(display = "SDL {:?} subsystem: {}", _0, _1)]
Sdl2InitError(Sdl2Subsystem, String),
#[fail(display = "{}", _0)]
RendererInitError(RendererError),
#[fail(display = "{}", _0)]
AudioInitError(AudioError),
#[fail(display = "{}", _0)]
ImguiInitError(String)
}
struct PlaceholderScene
{
}
impl SceneType for PlaceholderScene
{
type SpawnableIdType = u32;
fn new() -> Self where Self : Sized { PlaceholderScene {} }
fn thinkers(&mut self, _game : &mut GameControl) -> Vec<Box<dyn ThinkerSystem<Self>>>
{
warn!("No scene loaded in gameloop!");
vec![]
}
}
#[cfg(feature = "graphical_panic")]
fn panic_hook(info : &PanicInfo)
{
let message : &str = match info.payload().downcast_ref::<String>()
{
Some(s) => s,
None => match info.payload().downcast_ref::<&str>()
{
Some(s) => s,
None => "No reason was given.",
}
};
let mut cli_message = match info.location()
{
Some(location) => format!("A panic error occurred on {}:{}: \n{}",
location.file(),
location.line(),
message),
None => format!("A panic error occurred: \n{}", message),
};
let bt = Backtrace::new();
cli_message = format!("{}\n\n{:?}", cli_message, bt);
println!("{}", &cli_message);
let gui_message = format!("A panic error occurred.\n\n{}", message);
match messagebox::show_simple_message_box(messagebox::MessageBoxFlag::ERROR, "Panic Error", &gui_message[..], None)
{
Ok(_) => (),
Err(_) => println!("(Messagebox could not be displayed)"),
};
std::process::exit(1);
}
fn print_log_section(title : &str)
{
info!("");
info!("{:-^40}", format!(" {} ", title));
info!("");
}
#[allow(unused_variables)]
pub fn gui_cli_message(message : &str, title : &str)
{
println!("{}", message);
match messagebox::show_simple_message_box(messagebox::MessageBoxFlag::INFORMATION, title, &message[..], None)
{
Ok(_) => (),
Err(_) => println!("(Messagebox could not be displayed)"),
};
}
fn read_options() -> getopts::Matches
{
let args : Vec<String> = std::env::args().collect();
let mut options = getopts::Options::new();
options.optflag("", "help", "Show this help");
options.optflag("f", "fullscreen", "Run in fullscreen mode");
options.optflag("w", "window", "Run in windowed mode");
options.optopt("", "interpolate", "Unlock display framerate from logic and enable frame interpolation for alternate refresh rates or Variable Refresh Rate displays. Upper framerate limit is determined by FPS_LIMIT (default 120, use 0 to disable). Do *not* use with a fixed refresh rate equal to game's logic rate!", "FPS_LIMIT");
options.optflag("", "no-interpolate", "Force disable frame interpolation and lock display rate to logic rate. Use with a fixed refresh rate equal to game's refresh rate.");
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.");
let match_result = options.parse(&args[1..]);
match match_result
{
Ok(matches) =>
{
if matches.opt_present("help")
{
let message = options.usage("Commandline Parameters Help");
gui_cli_message(&message, "Commandline Help");
std::process::exit(1);
}
matches
}
Err(fail) =>
{
let message = format!("{}\n{}",
fail,
options.usage("Command parsing failed. Run without options to use defaults."));
gui_cli_message(&message, "Commandline Help");
std::process::exit(1);
}
}
}
fn apply_config(gameinfo : &mut GameInfo)
{
let matches = read_options();
if matches.opt_present("window")
{
gameinfo.fullscreen = false;
}
else if matches.opt_present("fullscreen")
{
gameinfo.fullscreen = true;
}
if matches.opt_present("no-interpolate")
{
gameinfo.frame_interpolation = false;
}
else if matches.opt_present("interpolate")
{
gameinfo.frame_interpolation = true;
}
let cap : f64 = matches.opt_get_default("interpolate", 120.0).unwrap_or(120.0);
if cap == 0.0
{
gameinfo.frame_interpolation_cap = None;
}
else
{
gameinfo.frame_interpolation_cap = Some(cap);
}
if matches.opt_present("kiosk")
{
gameinfo.kiosk_mode = true;
}
}
#[derive(Clone)]
pub struct GameInfo
{
pub package_name : &'static str,
pub friendly_name : &'static str,
pub base_width : u32,
pub base_height : u32,
pub default_zoom : u32,
pub texture_filtering : bool,
pub viewport_mode : ViewportMode,
pub fullscreen : bool,
pub target_framerate : f64,
pub frame_interpolation : bool,
pub frame_interpolation_cap : Option<f64>,
pub allow_system_sleep : bool,
pub low_energy_app : bool,
pub kiosk_mode : bool
}
impl Default for GameInfo
{
fn default() -> GameInfo
{
GameInfo
{
package_name : "",
friendly_name : "Untitled",
base_width : 1280,
base_height : 720,
default_zoom : 1,
texture_filtering : true,
viewport_mode : ViewportMode::Independent,
fullscreen : false,
kiosk_mode : false,
allow_system_sleep : false,
low_energy_app : false,
target_framerate : 60.0,
frame_interpolation : false,
frame_interpolation_cap : Some(120.0)
}
}
}
pub struct GameloopStopwatch
{
jiffies : Instant,
jiffies_this_frame : Duration,
jiffies_this_second : Duration,
tics_this_frame : u32,
tics_this_second : u32,
frames_this_second : u32,
in_frame : bool
}
impl GameloopStopwatch
{
pub fn new() -> GameloopStopwatch
{
GameloopStopwatch
{
jiffies : Instant::now(),
jiffies_this_frame : Duration::new(0, 0),
jiffies_this_second : Duration::new(0, 0),
tics_this_frame : 0,
tics_this_second : 0,
frames_this_second : 0,
in_frame : false
}
}
pub fn begin_frame_timing(&mut self)
{
if self.in_frame
{
return;
}
self.in_frame = true;
self.jiffies = Instant::now();
self.jiffies_this_frame = Duration::new(0, 0);
self.tics_this_frame = 0;
}
pub fn end_frame_timing(&mut self)
{
if !self.in_frame
{
return;
}
self.in_frame = false;
self.jiffies_this_frame = Instant::now() - self.jiffies;
self.tics_this_second += self.tics_this_frame;
self.jiffies_this_second += self.jiffies_this_frame;
self.frames_this_second += 1;
}
pub fn tic(&mut self)
{
self.tics_this_frame += 1;
}
pub fn new_second(&mut self)
{
self.jiffies_this_second = Duration::new(0, 0);
self.tics_this_second = 0;
self.frames_this_second = 0;
}
pub fn jiffies_this_frame(&self) -> Duration
{
if self.in_frame
{
return Instant::now() - self.jiffies;
}
self.jiffies_this_frame
}
pub fn jiffies_this_second(&self) -> Duration
{
self.jiffies_this_second
}
pub fn tics_this_frame(&self) -> u32
{
self.tics_this_frame
}
pub fn tics_this_second(&self) -> u32
{
self.tics_this_second
}
pub fn frames_this_second(&self) -> u32
{
self.frames_this_second
}
pub fn average_ms(&self) -> f64
{
self.jiffies_this_second.as_nanos() as f64 * MILLISECONDS_PER_NANOSECOND / self.frames_this_second as f64
}
}
pub trait GameSingleton : Downcast
{
}
impl_downcast!(GameSingleton);
pub struct GameControl
{
target_framerate : f64,
frame_interpolation : bool,
interpolation_cap : Option<f64>,
low_energy_app : bool,
low_energy_timer : u32,
input : Input,
renderer : Renderer,
audio : Box<dyn Audio>,
#[allow(dead_code)]
controller_subsystem : Option<sdl2::GameControllerSubsystem>,
controllers : HashMap<u32, sdl2::controller::GameController>,
next_scene : Box<dyn BaseScene>,
next_scene_waiting : bool,
source_manager : Rc<RefCell<SourceManager>>,
resources : DataMultistore,
singletons : HashMap<TypeId, Box<dyn GameSingleton>>,
focused : bool,
done : bool,
#[cfg(feature = "imgui_base")]
imgui_handler : Option<ImGuiHandler>
}
impl GameControl
{
pub fn new(gameinfo : &GameInfo, sdl_context : Option<&sdl2::Sdl>) -> Result<GameControl, InitError>
{
let source_manager = Rc::new(RefCell::new(SourceManager::new()));
let default_zoom = gameinfo.default_zoom.max(1).min(8);
let mut resources = DataMultistore::new(source_manager.clone());
let mut renderer_new_info = None;
let audio : Box<dyn Audio>;
let controller_subsystem;
if let Some(sdl) = sdl_context
{
let video_subsystem = try_or_else!(sdl.video(),
|error| Err(InitError::Sdl2InitError(Sdl2Subsystem::Video, error)));
controller_subsystem = Some(try_or_else!(sdl.game_controller(),
|error| Err(InitError::Sdl2InitError(Sdl2Subsystem::GameController, error))));
audio = Box::new(try_or_else!(AlAudio::new(&mut resources),
|error| Err(InitError::AudioInitError(error))));
if gameinfo.allow_system_sleep
{
video_subsystem.enable_screen_saver();
}
else
{
video_subsystem.disable_screen_saver();
}
renderer_new_info = Some(RendererNewInfo
{
width : gameinfo.base_width,
height : gameinfo.base_height,
default_zoom,
texture_filtering : gameinfo.texture_filtering,
kiosk_mode : gameinfo.kiosk_mode,
fullscreen : gameinfo.fullscreen,
viewport_mode : gameinfo.viewport_mode,
video_subsystem,
resources : &mut resources
});
}
else
{
audio = Box::new(NullAudio::new());
controller_subsystem = None;
}
#[cfg(feature = "imgui_base")]
let mut imgui_handler = try_or_else!(ImGuiHandler::new(),
|error| Err(InitError::ImguiInitError(error)));
#[allow(unused_mut)]
let mut renderer = try_or_else!(Renderer::new(renderer_new_info),
|error| Err(InitError::RendererInitError(error)));
#[cfg(feature = "imgui_base")]
try_or_else!(renderer.control().init_imgui_renderer(imgui_handler.imgui_mut()),
|error| Err(InitError::RendererInitError(error)));
Ok(GameControl
{
target_framerate : gameinfo.target_framerate,
frame_interpolation : gameinfo.frame_interpolation,
interpolation_cap : gameinfo.frame_interpolation_cap,
low_energy_app : gameinfo.low_energy_app,
low_energy_timer : 0,
input : Input::new(),
renderer,
audio,
controller_subsystem,
controllers : HashMap::new(),
next_scene : Box::new(Scene::<PlaceholderScene>::new()),
next_scene_waiting : false,
source_manager,
resources,
focused : true,
done : false,
singletons : HashMap::new(),
#[cfg(feature = "imgui_base")]
imgui_handler : Some(imgui_handler),
})
}
pub fn target_framerate(&self) -> f64
{
self.target_framerate
}
pub fn set_target_framerate(&mut self, new_framerate : f64)
{
if new_framerate < 1.0 || new_framerate >= 1000.0
{
panic!("Target framerate must be between 1 and 1000.");
}
self.target_framerate = new_framerate;
}
pub fn frame_interpolation(&self) -> bool
{
self.frame_interpolation
}
pub fn set_frame_interpolation(&mut self, enabled : bool)
{
self.frame_interpolation = enabled;
}
pub fn frame_interpolation_cap(&self) -> Option<f64>
{
self.interpolation_cap
}
pub fn set_frame_interpolation_cap(&mut self, framerate : Option<f64>)
{
if let Some(cap) = framerate
{
if cap < 1.0 || cap >= 1000.0
{
panic!("Target framerate cap must be between 1 and 1000.");
}
}
self.interpolation_cap = framerate;
}
pub fn input(&self) -> &Input
{
&self.input
}
pub fn input_mut(&mut self) -> &mut Input
{
&mut self.input
}
pub fn renderer(&self) -> &Renderer
{
&self.renderer
}
pub fn renderer_mut(&mut self) -> &mut Renderer
{
&mut self.renderer
}
pub fn audio(&self) -> &Box<dyn Audio>
{
&self.audio
}
pub fn audio_mut(&mut self) -> &mut Box<dyn Audio>
{
&mut self.audio
}
fn sync_audio_store(&mut self)
{
self.audio.sync_sound_store(&mut self.resources.store_mut())
.expect("Audio backend store sync failed");
}
pub fn source_manager(&self) -> Rc<RefCell<SourceManager>>
{
self.source_manager.clone()
}
pub fn res(&self) -> &DataMultistore
{
&self.resources
}
pub fn singleton_mut<T : 'static + GameSingleton + Default>(&mut self) -> &mut T
{
let type_id = TypeId::of::<T>();
if !self.singletons.contains_key(&type_id)
{
self.singletons.insert(type_id.clone(), Box::new(T::default()));
}
self.singletons.get_mut(&type_id).unwrap().downcast_mut::<T>().expect("Downcast failed!")
}
#[cfg(feature = "imgui_base")]
pub fn imgui(&self) -> Option<&imgui::Context>
{
if let Some(imgui_handler) = &self.imgui_handler
{
return Some(imgui_handler.imgui());
}
None
}
#[cfg(feature = "imgui_base")]
pub fn imgui_mut(&mut self) -> Option<&mut imgui::Context>
{
if let Some(imgui_handler) = &mut self.imgui_handler
{
return Some(imgui_handler.imgui_mut());
}
None
}
pub fn goto_constructed_scene(&mut self, next_scene : Box<dyn BaseScene>)
{
self.next_scene = next_scene;
self.next_scene_waiting = true;
}
pub fn quit(&mut self)
{
self.done = true;
}
}
struct GameloopTimeState
{
jiffies : Instant,
last_jiffies : Instant,
last_fps_jiffies : Instant,
jiffy_queue : Duration,
target_frametime : Duration,
cap_frametime : Duration,
last_display : Instant,
lag_this_second : u32,
thought_yet : bool,
think_timer : GameloopStopwatch,
draw_timer : GameloopStopwatch,
wait_timer : GameloopStopwatch,
present_timer : GameloopStopwatch
}
impl GameloopTimeState
{
fn new() -> GameloopTimeState
{
let jiffies = Instant::now();
let last_jiffies = jiffies;
let last_fps_jiffies = Instant::now();
let jiffy_queue = Duration::new(0, 0);
let target_frametime = Duration::new(0, 0);
let cap_frametime = Duration::new(0, 0);
let last_display = Instant::now();
GameloopTimeState
{
jiffies,
last_jiffies,
last_fps_jiffies,
jiffy_queue,
target_frametime,
cap_frametime,
last_display,
lag_this_second : 0,
thought_yet : false,
think_timer : GameloopStopwatch::new(),
draw_timer : GameloopStopwatch::new(),
wait_timer : GameloopStopwatch::new(),
present_timer : GameloopStopwatch::new()
}
}
fn flush(&mut self)
{
while self.jiffy_queue > self.target_frametime
{
self.jiffy_queue -= self.target_frametime;
}
self.new_second();
self.jiffies = Instant::now();
self.last_jiffies = self.jiffies;
self.last_fps_jiffies = Instant::now();
self.last_display = self.jiffies;
self.thought_yet = false;
}
fn new_second(&mut self)
{
self.think_timer.new_second();
self.draw_timer.new_second();
self.wait_timer.new_second();
self.present_timer.new_second();
self.last_fps_jiffies += Duration::from_secs(1);
self.lag_this_second = 0;
}
fn update_jiffies(&mut self)
{
self.jiffies = Instant::now();
self.jiffy_queue += self.jiffies - self.last_jiffies;
self.last_jiffies = self.jiffies;
}
fn sleep(&mut self, wait_duration : Duration, low_power : bool)
{
while self.jiffy_queue <= wait_duration
{
self.update_jiffies();
if self.jiffy_queue <= wait_duration - Duration::from_millis(1) || low_power
{
std::thread::sleep(Duration::from_millis(1));
}
}
}
fn interpolate_sleep(&mut self, wait_duration : Duration)
{
while Instant::now() - self.last_display <= wait_duration
{
}
self.last_display = Instant::now();
}
}
pub struct Gameloop
{
#[allow(dead_code)]
sdl_context : sdl2::Sdl,
event_pump : sdl2::EventPump,
event_queue : Vec<sdl2::event::Event>,
control : GameControl,
current_scene : Box<dyn BaseScene>,
time : GameloopTimeState,
kiosk_mode : bool
}
impl Gameloop
{
pub fn new(gameinfo : GameInfo) -> Gameloop
{
Gameloop::try_new(gameinfo).unwrap_or_else(|error| panic!("Startup failed: {}", error))
}
pub fn try_new(gameinfo : GameInfo) -> Result<Gameloop, InitError>
{
let mut gameinfo = gameinfo.clone();
if gameinfo.package_name.is_empty()
{
panic!("Package name cannot be empty.");
}
#[cfg(feature = "default_logger")]
{
let mut logger = doglog::DogLog::new();
logger.add_package_filter(String::from(gameinfo.package_name));
if let Err(_) = doglog::DogLog::init(logger, log::LevelFilter::Debug)
{
eprintln!("Could not initialize default logger.");
}
}
#[cfg(feature = "graphical_panic")]
std::panic::set_hook(Box::new(panic_hook));
apply_config(&mut gameinfo);
print_log_section("🐶 KEESHOND Game Engine 🐶");
let version = sdl2::version::version();
info!("Starting SDL version {}", version);
let sdl_context = try_or_else!(sdl2::init(),
|error| Err(InitError::Sdl2InitError(Sdl2Subsystem::Main, error)));
let event_pump = try_or_else!(sdl_context.event_pump(),
|error| Err(InitError::Sdl2InitError(Sdl2Subsystem::Event, error)));
#[allow(unused_mut)]
let mut control = try_or_else!(GameControl::new(&gameinfo, Some(&sdl_context)), |error| Err(error));
control.renderer_mut().set_base_size(gameinfo.base_width as f32, gameinfo.base_height as f32);
control.renderer_mut().set_window_title(&gameinfo.friendly_name);
info!("Gameloop constructed");
Ok(Gameloop
{
sdl_context,
event_pump,
event_queue : Vec::new(),
control,
current_scene : Box::new(Scene::<PlaceholderScene>::new()),
time : GameloopTimeState::new(),
kiosk_mode : gameinfo.kiosk_mode
})
}
pub fn control(&self) -> &GameControl
{
&self.control
}
pub fn control_mut(&mut self) -> &mut GameControl
{
&mut self.control
}
fn think(&mut self)
{
self.time.think_timer.begin_frame_timing();
let target_ms = 1000.0 / self.control.target_framerate();
let cap_ms = 1000.0 / self.control.frame_interpolation_cap().unwrap_or(1000.0);
self.time.target_frametime = Duration::from_nanos((target_ms * 1_000_000.0) as u64);
self.time.cap_frametime = Duration::from_nanos((cap_ms * 1_000_000.0) as u64);
self.control.sync_audio_store();
while self.time.jiffy_queue > self.time.target_frametime
{
if (self.time.think_timer.jiffies_this_frame()) > self.time.target_frametime
{
while self.time.jiffy_queue > self.time.target_frametime
{
self.time.jiffy_queue -= self.time.target_frametime;
self.time.lag_this_second += 1;
}
break;
}
self.control.input.update_actions();
self.current_scene.think(&mut self.control);
self.time.think_timer.tic();
self.time.jiffy_queue -= self.time.target_frametime;
self.time.thought_yet = true;
self.control.low_energy_timer += 1;
}
self.time.think_timer.end_frame_timing();
}
fn draw(&mut self)
{
self.time.draw_timer.begin_frame_timing();
if self.time.thought_yet && self.should_draw()
{
let mut interpolation_amount = 0.0;
if self.control.frame_interpolation && !self.low_energy_timer_expired()
{
interpolation_amount = (self.time.jiffy_queue.as_nanos() as f64
/ self.time.target_frametime.as_nanos() as f64) - 1.0;
}
self.control.renderer.draw(&mut self.current_scene, &mut self.control.resources, interpolation_amount as f32);
}
#[cfg(feature = "imgui_base")]
{
self.update_imgui();
}
self.control.renderer.control().flush_drawing();
self.time.draw_timer.end_frame_timing();
}
#[cfg(feature = "imgui_base")]
fn update_imgui(&mut self)
{
let mut imgui_handler : Option<ImGuiHandler> = None;
let frametime = self.time.target_frametime * self.time.think_timer.tics_this_frame();
let frame_delta = frametime.as_nanos() as f64 * MILLISECONDS_PER_NANOSECOND / 1000.0;
let (width, height) = self.control.renderer.window_size();
std::mem::swap(&mut imgui_handler, &mut self.control.imgui_handler);
if let Some(imgui_some) = &mut imgui_handler
{
imgui_some.update_size_and_delta(frame_delta as f32, width as f32, height as f32);
let mut ui = imgui_some.imgui_mut().frame();
self.current_scene.imgui_think(&mut ui, &mut self.control);
let imgui_cursor = ui.mouse_cursor();
self.control.renderer.draw_imgui(ui, &mut self.control.resources);
imgui_some.update_cursor(&mut self.sdl_context.mouse(), imgui_cursor);
}
std::mem::swap(&mut imgui_handler, &mut self.control.imgui_handler);
}
fn wait(&mut self)
{
self.time.wait_timer.begin_frame_timing();
let low_energy = self.low_energy_timer_expired();
if !self.control.frame_interpolation || low_energy
{
self.time.sleep(self.time.target_frametime, low_energy);
}
else if self.control.frame_interpolation_cap().is_some()
{
self.time.interpolate_sleep(self.time.cap_frametime);
self.time.update_jiffies();
}
else
{
self.time.update_jiffies();
}
self.time.wait_timer.end_frame_timing();
}
fn present(&mut self)
{
self.time.present_timer.begin_frame_timing();
if self.should_draw()
{
self.control.renderer.present();
}
self.time.present_timer.end_frame_timing();
}
fn low_energy_timer_expired(&self) -> bool
{
self.control.low_energy_app && self.control.low_energy_timer > LOW_ENERGY_TIMER_MIN
}
fn should_draw(&self) -> bool
{
!self.low_energy_timer_expired() || self.control.low_energy_timer % LOW_ENERGY_DRAW_RATE == 0
}
fn process_event(&mut self, event : Event) -> bool
{
self.control.low_energy_timer = 0;
#[cfg(feature = "imgui_base")]
{
if let Some(imgui_handler) = &mut self.control.imgui_handler
{
if imgui_handler.handle_event(&event)
{
return true;
}
}
}
match event
{
Event::Quit { .. } =>
{
if !self.kiosk_mode
{
return false;
}
},
Event::Window { win_event, .. } => match win_event
{
WindowEvent::FocusGained { .. } =>
{
self.control.focused = true;
},
WindowEvent::FocusLost { .. } =>
{
self.control.focused = false;
},
WindowEvent::SizeChanged { .. } =>
{
self.control.renderer.recalculate_viewport();
}
_ => ()
},
Event::KeyDown { scancode, keymod, repeat, .. } =>
{
if !repeat && (keymod.contains(KeyboardMod::LALTMOD)
|| keymod.contains(KeyboardMod::RALTMOD))
&& scancode == Some(sdl2::keyboard::Scancode::Return)
&& !self.kiosk_mode
{
self.control.renderer.toggle_fullscreen();
} else if !repeat
{
if let Some(some_scancode) = scancode
{
self.control.input.update_key(some_scancode.name(), true, true);
}
}
},
Event::KeyUp { scancode, .. } =>
{
if let Some(some_scancode) = scancode
{
self.control.input.update_key(some_scancode.name(), false, true);
}
},
Event::ControllerDeviceAdded { which, .. } =>
{
if let Some(controller_subsystem) = &self.control.controller_subsystem
{
match controller_subsystem.open(which)
{
Ok(controller) =>
{
info!("Attached controller {}: {}", which, controller.name());
self.control.controllers.insert(which, controller);
},
Err(error) =>
{
error!("Could not open controller: {}", error.to_string());
}
}
}
}
Event::ControllerDeviceRemoved { which, .. } =>
{
if let Some(controller) = self.control.controllers.remove(&which)
{
info!("Detached controller {}: {}", which, controller.name());
}
}
Event::ControllerButtonDown { button, .. } =>
{
self.control.input.update_gamepad_key(button, true);
},
Event::ControllerButtonUp { button, .. } =>
{
self.control.input.update_gamepad_key(button, false);
},
Event::ControllerAxisMotion { axis, value, .. } =>
{
self.control.input.update_gamepad_axis(axis, value);
},
_ => ()
}
return true;
}
fn update_fps(&mut self)
{
if self.time.jiffies < self.time.last_fps_jiffies + Duration::from_secs(1)
{
return;
}
debug!("{} ent {} com | {} / {} FPS | avg ms think {:.2} draw {:.2} present {:.2} wait {:.2}",
self.current_scene.entity_count(),
self.current_scene.component_count(),
self.time.draw_timer.frames_this_second(),
self.time.think_timer.tics_this_second(),
self.time.think_timer.average_ms(),
self.time.draw_timer.average_ms(),
self.time.present_timer.average_ms(),
self.time.wait_timer.average_ms());
if self.time.lag_this_second > 0
{
warn!("Too much CPU work, game is slowing down!");
}
self.time.new_second();
}
pub fn run(&mut self)
{
print_log_section("START GAMELOOP");
self.time = GameloopTimeState::new();
'runloop: while !self.control.done
{
if self.low_energy_timer_expired() && !self.control.focused
{
self.event_queue.push(self.event_pump.wait_event());
self.time.flush();
}
for event in self.event_pump.poll_iter()
{
self.event_queue.push(event);
}
let mut event_queue = Vec::new();
std::mem::swap(&mut self.event_queue, &mut event_queue);
for event in event_queue.drain(..)
{
if !self.process_event(event)
{
break 'runloop;
}
}
std::mem::swap(&mut self.event_queue, &mut event_queue);
self.think();
self.draw();
self.wait();
self.present();
self.update_fps();
if self.control.next_scene_waiting
{
self.current_scene.end(&mut self.control);
print_log_section("NEW SCENE");
std::mem::swap(&mut self.current_scene, &mut self.control.next_scene);
self.control.next_scene = Box::new(Scene::<PlaceholderScene>::new());
self.current_scene.start(&mut self.control);
self.time.flush();
self.control.next_scene_waiting = false;
}
}
print_log_section("END GAMELOOP");
self.current_scene.end(&mut self.control);
}
}