From 72992eb4e463f2654b553efbe9caa8cd444fcfc2 Mon Sep 17 00:00:00 2001 From: Alula Date: Thu, 29 Oct 2020 13:22:56 +0100 Subject: [PATCH] more precise and less jittery game loop timing --- src/ggez/filesystem.rs | 27 ++++++++++++++------------ src/lib.rs | 42 ++++++++++++++++++++++++---------------- src/scene/title_scene.rs | 16 ++++----------- src/shared_game_state.rs | 14 +++++++------- 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/ggez/filesystem.rs b/src/ggez/filesystem.rs index 9316d78..429ea2b 100644 --- a/src/ggez/filesystem.rs +++ b/src/ggez/filesystem.rs @@ -36,6 +36,7 @@ use crate::ggez::{Context, GameError, GameResult}; use crate::ggez::conf; use crate::ggez::vfs::{self, VFS}; pub use crate::ggez::vfs::OpenOptions; +use directories::ProjectDirs; const CONFIG_NAME: &str = "/conf.toml"; @@ -43,9 +44,9 @@ const CONFIG_NAME: &str = "/conf.toml"; #[derive(Debug)] pub struct Filesystem { vfs: vfs::OverlayFS, - /*resources_path: path::PathBuf, + //resources_path: path::PathBuf, user_config_path: path::PathBuf, - user_data_path: path::PathBuf,*/ + user_data_path: path::PathBuf, } /// Represents a file, either in the filesystem, or in the resources zip file, @@ -112,12 +113,12 @@ impl Filesystem { // Set up VFS to merge resource path, root path, and zip path. let mut overlay = vfs::OverlayFS::new(); - /*let mut resources_path; - let mut resources_zip_path; let user_data_path; let user_config_path; + // let mut resources_path; + // let mut resources_zip_path; - let project_dirs = match ProjectDirs::from("", author, id) { + let project_dirs = match ProjectDirs::from("", "", id) { Some(dirs) => dirs, None => { return Err(GameError::FilesystemError(String::from( @@ -128,7 +129,7 @@ impl Filesystem { // /resources/ - { + /*{ resources_path = root_path.clone(); resources_path.push("resources"); trace!("Resources path: {:?}", resources_path); @@ -147,13 +148,13 @@ impl Filesystem { } else { trace!("No resources zip file found"); } - } + }*/ // Per-user data dir, // ~/.local/share/whatever/ { user_data_path = project_dirs.data_local_dir(); - trace!("User-local data path: {:?}", user_data_path); + log::trace!("User-local data path: {:?}", user_data_path); let physfs = vfs::PhysicalFS::new(&user_data_path, true); overlay.push_back(Box::new(physfs)); } @@ -162,15 +163,15 @@ impl Filesystem { // Save game dir is read-write { user_config_path = project_dirs.config_dir(); - trace!("User-local configuration path: {:?}", user_config_path); + log::trace!("User-local configuration path: {:?}", user_config_path); let physfs = vfs::PhysicalFS::new(&user_config_path, false); overlay.push_back(Box::new(physfs)); - }*/ + } let fs = Filesystem { vfs: overlay, - //user_config_path: user_config_path.to_path_buf(), - //user_data_path: user_data_path.to_path_buf(), + user_config_path: user_config_path.to_path_buf(), + user_data_path: user_data_path.to_path_buf(), }; Ok(fs) @@ -473,6 +474,8 @@ mod tests { ofs.push_front(Box::new(physfs)); Filesystem { vfs: ofs, + user_config_path: path, + user_data_path: path } } diff --git a/src/lib.rs b/src/lib.rs index 2e28c0c..af1f747 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,11 +18,11 @@ use std::time::Instant; use log::*; use pretty_env_logger::env_logger::Env; -use winit::event::{ElementState, Event, KeyboardInput, WindowEvent, TouchPhase}; +use winit::event::{ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use winit::event_loop::ControlFlow; use crate::builtin_fs::BuiltinFS; -use crate::ggez::{Context, ContextBuilder, filesystem, GameResult, GameError}; +use crate::ggez::{Context, ContextBuilder, filesystem, GameError, GameResult}; use crate::ggez::conf::{Backend, WindowMode, WindowSetup}; use crate::ggez::event::{KeyCode, KeyMods}; use crate::ggez::graphics; @@ -73,7 +73,7 @@ struct Game { ui: UI, def_matrix: ColumnMatrix4, start_time: Instant, - next_tick: u64, + next_tick: u128, loops: u64, } @@ -96,23 +96,21 @@ impl Game { if let Some(scene) = self.scene.as_mut() { match self.state.timing_mode { TimingMode::_50Hz | TimingMode::_60Hz => { - while self.start_time.elapsed().as_millis() as u64 > self.next_tick && self.loops < 3 { - self.next_tick += self.state.timing_mode.get_delta() as u64; + while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 3 { + if (self.state.settings.speed - 1.0).abs() < 0.01 { + self.next_tick += self.state.timing_mode.get_delta() as u128; + } else { + self.next_tick += (self.state.timing_mode.get_delta() as f64 / self.state.settings.speed) as u128; + } self.loops += 1; } for _ in 0..self.loops { scene.tick(&mut self.state, ctx)?; - if self.state.settings.speed_hack { - scene.tick(&mut self.state, ctx)?; - } } } TimingMode::FrameSynchronized => { scene.tick(&mut self.state, ctx)?; - if self.state.settings.speed_hack { - scene.tick(&mut self.state, ctx)?; - } } } } @@ -156,9 +154,19 @@ impl Game { KeyCode::X => { state.key_state.set_fire(true) } KeyCode::A => { state.key_state.set_weapon_prev(true) } KeyCode::S => { state.key_state.set_weapon_next(true) } + KeyCode::F7 => { state.set_speed(1.0) } + KeyCode::F8 => { + if state.settings.speed > 0.5 { + state.set_speed(state.settings.speed - 0.1); + } + } + KeyCode::F9 => { + if state.settings.speed < 3.0 { + state.set_speed(state.settings.speed + 0.1); + } + } KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines } KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode } - KeyCode::F12 => { state.set_speed_hack(!state.settings.speed_hack) } _ => {} } } @@ -238,10 +246,10 @@ pub fn android_main() { } static BACKENDS: [Backend; 4] = [ - Backend::OpenGL {major: 3, minor: 2}, - Backend::OpenGLES { major: 3, minor: 2}, - Backend::OpenGLES { major: 3, minor: 0}, - Backend::OpenGLES { major: 2, minor: 0} + Backend::OpenGL { major: 3, minor: 2 }, + Backend::OpenGLES { major: 3, minor: 2 }, + Backend::OpenGLES { major: 3, minor: 0 }, + Backend::OpenGLES { major: 2, minor: 0 } ]; fn init_ctx + Clone>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult { @@ -372,7 +380,7 @@ pub fn init() -> GameResult { match el_state { ElementState::Pressed => { let repeat = keyboard::is_key_repeated(ctx); - game.key_down_event( keycode, modifiers.into(), repeat); + game.key_down_event(keycode, modifiers.into(), repeat); } ElementState::Released => { game.key_up_event(keycode, modifiers.into()); diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 4de603e..102b5d6 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -75,9 +75,9 @@ impl TitleScene { static ENGINE_VERSION: &str = "doukutsu-rs 0.1.0"; // asset copyright for freeware version static COPYRIGHT_PIXEL: &str = "2004.12 Studio Pixel"; -// asset copyright for Nicalis, why they've even replaced © with @? +// asset copyright for Nicalis static COPYRIGHT_NICALIS: &str = "@2011 NICALIS INC."; -static COPYRIGHT_NICALIS_SWITCH: &str = "@2017 NICALIS INC."; // untested? +static COPYRIGHT_NICALIS_SWITCH: &str = "@2017 NICALIS INC."; static DISCORD_LINK: &str = "https://discord.gg/fbRsNNB"; @@ -100,7 +100,6 @@ impl Scene for TitleScene { } else { self.option_menu.push_entry(MenuEntry::Disabled("Original textures".to_string())); } - self.option_menu.push_entry(MenuEntry::Toggle("2x Speed hack".to_string(), state.settings.speed_hack)); self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string())); self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned())); self.option_menu.push_entry(MenuEntry::Active("Back".to_string())); @@ -180,19 +179,12 @@ impl Scene for TitleScene { *value = state.settings.original_textures; } } - MenuSelectionResult::Selected(4, toggle) => { - if let MenuEntry::Toggle(_, value) = toggle { - state.set_speed_hack(!state.settings.speed_hack); - - *value = state.settings.speed_hack; - } - } - MenuSelectionResult::Selected(5, _) => { + MenuSelectionResult::Selected(4, _) => { if let Err(e) = webbrowser::open(DISCORD_LINK) { log::warn!("Error opening web browser: {}", e); } } - MenuSelectionResult::Selected(7, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(6, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::MainMenu; } _ => {} diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 8e0bf5b..5171ca8 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -31,8 +31,8 @@ pub enum TimingMode { impl TimingMode { pub fn get_delta(self) -> usize { match self { - TimingMode::_50Hz => { 1000 / 50 } - TimingMode::_60Hz => { 1000 / 60 } + TimingMode::_50Hz => { 1000000000 / 50 } + TimingMode::_60Hz => { 1000000000 / 60 } TimingMode::FrameSynchronized => { 0 } } } @@ -40,7 +40,7 @@ impl TimingMode { pub struct Settings { pub god_mode: bool, - pub speed_hack: bool, + pub speed: f64, pub original_textures: bool, pub enhanced_graphics: bool, pub debug_outlines: bool, @@ -131,7 +131,7 @@ impl SharedGameState { sound_manager: SoundManager::new(ctx)?, settings: Settings { god_mode: false, - speed_hack: false, + speed: 1.0, original_textures: false, enhanced_graphics: true, debug_outlines: false, @@ -230,10 +230,10 @@ impl SharedGameState { self.carets.push(Caret::new(x, y, ctype, direct, &self.constants)); } - pub fn set_speed_hack(&mut self, toggle: bool) { - self.settings.speed_hack = toggle; + pub fn set_speed(&mut self, value: f64) { + self.settings.speed = value; - if let Err(err) = self.sound_manager.set_speed(if toggle { 2.0 } else { 1.0 }) { + if let Err(err) = self.sound_manager.set_speed(value as f32) { log::error!("Error while sending a message to sound manager: {}", err); } }