1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-12-06 12:24:47 +00:00

more precise and less jittery game loop timing

This commit is contained in:
Alula 2020-10-29 13:22:56 +01:00
parent 4b9abc5f1e
commit 72992eb4e4
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
4 changed files with 51 additions and 48 deletions

View file

@ -36,6 +36,7 @@ use crate::ggez::{Context, GameError, GameResult};
use crate::ggez::conf; use crate::ggez::conf;
use crate::ggez::vfs::{self, VFS}; use crate::ggez::vfs::{self, VFS};
pub use crate::ggez::vfs::OpenOptions; pub use crate::ggez::vfs::OpenOptions;
use directories::ProjectDirs;
const CONFIG_NAME: &str = "/conf.toml"; const CONFIG_NAME: &str = "/conf.toml";
@ -43,9 +44,9 @@ const CONFIG_NAME: &str = "/conf.toml";
#[derive(Debug)] #[derive(Debug)]
pub struct Filesystem { pub struct Filesystem {
vfs: vfs::OverlayFS, vfs: vfs::OverlayFS,
/*resources_path: path::PathBuf, //resources_path: path::PathBuf,
user_config_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, /// 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. // Set up VFS to merge resource path, root path, and zip path.
let mut overlay = vfs::OverlayFS::new(); let mut overlay = vfs::OverlayFS::new();
/*let mut resources_path;
let mut resources_zip_path;
let user_data_path; let user_data_path;
let user_config_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, Some(dirs) => dirs,
None => { None => {
return Err(GameError::FilesystemError(String::from( return Err(GameError::FilesystemError(String::from(
@ -128,7 +129,7 @@ impl Filesystem {
// <game exe root>/resources/ // <game exe root>/resources/
{ /*{
resources_path = root_path.clone(); resources_path = root_path.clone();
resources_path.push("resources"); resources_path.push("resources");
trace!("Resources path: {:?}", resources_path); trace!("Resources path: {:?}", resources_path);
@ -147,13 +148,13 @@ impl Filesystem {
} else { } else {
trace!("No resources zip file found"); trace!("No resources zip file found");
} }
} }*/
// Per-user data dir, // Per-user data dir,
// ~/.local/share/whatever/ // ~/.local/share/whatever/
{ {
user_data_path = project_dirs.data_local_dir(); 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); let physfs = vfs::PhysicalFS::new(&user_data_path, true);
overlay.push_back(Box::new(physfs)); overlay.push_back(Box::new(physfs));
} }
@ -162,15 +163,15 @@ impl Filesystem {
// Save game dir is read-write // Save game dir is read-write
{ {
user_config_path = project_dirs.config_dir(); 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); let physfs = vfs::PhysicalFS::new(&user_config_path, false);
overlay.push_back(Box::new(physfs)); overlay.push_back(Box::new(physfs));
}*/ }
let fs = Filesystem { let fs = Filesystem {
vfs: overlay, vfs: overlay,
//user_config_path: user_config_path.to_path_buf(), user_config_path: user_config_path.to_path_buf(),
//user_data_path: user_data_path.to_path_buf(), user_data_path: user_data_path.to_path_buf(),
}; };
Ok(fs) Ok(fs)
@ -473,6 +474,8 @@ mod tests {
ofs.push_front(Box::new(physfs)); ofs.push_front(Box::new(physfs));
Filesystem { Filesystem {
vfs: ofs, vfs: ofs,
user_config_path: path,
user_data_path: path
} }
} }

View file

@ -18,11 +18,11 @@ use std::time::Instant;
use log::*; use log::*;
use pretty_env_logger::env_logger::Env; 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 winit::event_loop::ControlFlow;
use crate::builtin_fs::BuiltinFS; 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::conf::{Backend, WindowMode, WindowSetup};
use crate::ggez::event::{KeyCode, KeyMods}; use crate::ggez::event::{KeyCode, KeyMods};
use crate::ggez::graphics; use crate::ggez::graphics;
@ -73,7 +73,7 @@ struct Game {
ui: UI, ui: UI,
def_matrix: ColumnMatrix4<f32>, def_matrix: ColumnMatrix4<f32>,
start_time: Instant, start_time: Instant,
next_tick: u64, next_tick: u128,
loops: u64, loops: u64,
} }
@ -96,23 +96,21 @@ impl Game {
if let Some(scene) = self.scene.as_mut() { if let Some(scene) = self.scene.as_mut() {
match self.state.timing_mode { match self.state.timing_mode {
TimingMode::_50Hz | TimingMode::_60Hz => { TimingMode::_50Hz | TimingMode::_60Hz => {
while self.start_time.elapsed().as_millis() as u64 > self.next_tick && self.loops < 3 { while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 3 {
self.next_tick += self.state.timing_mode.get_delta() as u64; 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; self.loops += 1;
} }
for _ in 0..self.loops { for _ in 0..self.loops {
scene.tick(&mut self.state, ctx)?; scene.tick(&mut self.state, ctx)?;
if self.state.settings.speed_hack {
scene.tick(&mut self.state, ctx)?;
}
} }
} }
TimingMode::FrameSynchronized => { TimingMode::FrameSynchronized => {
scene.tick(&mut self.state, ctx)?; 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::X => { state.key_state.set_fire(true) }
KeyCode::A => { state.key_state.set_weapon_prev(true) } KeyCode::A => { state.key_state.set_weapon_prev(true) }
KeyCode::S => { state.key_state.set_weapon_next(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::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines }
KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode } 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] = [ static BACKENDS: [Backend; 4] = [
Backend::OpenGL {major: 3, minor: 2}, Backend::OpenGL { major: 3, minor: 2 },
Backend::OpenGLES { major: 3, minor: 2}, Backend::OpenGLES { major: 3, minor: 2 },
Backend::OpenGLES { major: 3, minor: 0}, Backend::OpenGLES { major: 3, minor: 0 },
Backend::OpenGLES { major: 2, minor: 0} Backend::OpenGLES { major: 2, minor: 0 }
]; ];
fn init_ctx<P: Into<path::PathBuf> + Clone>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult<Context> { fn init_ctx<P: Into<path::PathBuf> + Clone>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult<Context> {
@ -372,7 +380,7 @@ pub fn init() -> GameResult {
match el_state { match el_state {
ElementState::Pressed => { ElementState::Pressed => {
let repeat = keyboard::is_key_repeated(ctx); 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 => { ElementState::Released => {
game.key_up_event(keycode, modifiers.into()); game.key_up_event(keycode, modifiers.into());

View file

@ -75,9 +75,9 @@ impl TitleScene {
static ENGINE_VERSION: &str = "doukutsu-rs 0.1.0"; static ENGINE_VERSION: &str = "doukutsu-rs 0.1.0";
// asset copyright for freeware version // asset copyright for freeware version
static COPYRIGHT_PIXEL: &str = "2004.12 Studio Pixel"; 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: &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"; static DISCORD_LINK: &str = "https://discord.gg/fbRsNNB";
@ -100,7 +100,6 @@ impl Scene for TitleScene {
} else { } else {
self.option_menu.push_entry(MenuEntry::Disabled("Original textures".to_string())); 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::Active("Join our Discord".to_string()));
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned())); self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
self.option_menu.push_entry(MenuEntry::Active("Back".to_string())); self.option_menu.push_entry(MenuEntry::Active("Back".to_string()));
@ -180,19 +179,12 @@ impl Scene for TitleScene {
*value = state.settings.original_textures; *value = state.settings.original_textures;
} }
} }
MenuSelectionResult::Selected(4, toggle) => { MenuSelectionResult::Selected(4, _) => {
if let MenuEntry::Toggle(_, value) = toggle {
state.set_speed_hack(!state.settings.speed_hack);
*value = state.settings.speed_hack;
}
}
MenuSelectionResult::Selected(5, _) => {
if let Err(e) = webbrowser::open(DISCORD_LINK) { if let Err(e) = webbrowser::open(DISCORD_LINK) {
log::warn!("Error opening web browser: {}", e); log::warn!("Error opening web browser: {}", e);
} }
} }
MenuSelectionResult::Selected(7, _) | MenuSelectionResult::Canceled => { MenuSelectionResult::Selected(6, _) | MenuSelectionResult::Canceled => {
self.current_menu = CurrentMenu::MainMenu; self.current_menu = CurrentMenu::MainMenu;
} }
_ => {} _ => {}

View file

@ -31,8 +31,8 @@ pub enum TimingMode {
impl TimingMode { impl TimingMode {
pub fn get_delta(self) -> usize { pub fn get_delta(self) -> usize {
match self { match self {
TimingMode::_50Hz => { 1000 / 50 } TimingMode::_50Hz => { 1000000000 / 50 }
TimingMode::_60Hz => { 1000 / 60 } TimingMode::_60Hz => { 1000000000 / 60 }
TimingMode::FrameSynchronized => { 0 } TimingMode::FrameSynchronized => { 0 }
} }
} }
@ -40,7 +40,7 @@ impl TimingMode {
pub struct Settings { pub struct Settings {
pub god_mode: bool, pub god_mode: bool,
pub speed_hack: bool, pub speed: f64,
pub original_textures: bool, pub original_textures: bool,
pub enhanced_graphics: bool, pub enhanced_graphics: bool,
pub debug_outlines: bool, pub debug_outlines: bool,
@ -131,7 +131,7 @@ impl SharedGameState {
sound_manager: SoundManager::new(ctx)?, sound_manager: SoundManager::new(ctx)?,
settings: Settings { settings: Settings {
god_mode: false, god_mode: false,
speed_hack: false, speed: 1.0,
original_textures: false, original_textures: false,
enhanced_graphics: true, enhanced_graphics: true,
debug_outlines: false, debug_outlines: false,
@ -230,10 +230,10 @@ impl SharedGameState {
self.carets.push(Caret::new(x, y, ctype, direct, &self.constants)); self.carets.push(Caret::new(x, y, ctype, direct, &self.constants));
} }
pub fn set_speed_hack(&mut self, toggle: bool) { pub fn set_speed(&mut self, value: f64) {
self.settings.speed_hack = toggle; 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); log::error!("Error while sending a message to sound manager: {}", err);
} }
} }