From a50dfb8d2f042dabc27e5ca9f3f6fa0350bb257b Mon Sep 17 00:00:00 2001 From: Alex Knauth Date: Tue, 23 Apr 2024 20:19:27 -0400 Subject: [PATCH] Rewrite timer state update (#71) * Don't reset on manual Ended * timer_state may be out of date * Rewrite timer state update * don't forget to update last_state before return * refactor action Split --- src/lib.rs | 150 ++++++++++++---------------------------- src/splits.rs | 46 +------------ src/timer.rs | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 230 insertions(+), 152 deletions(-) create mode 100644 src/timer.rs diff --git a/src/lib.rs b/src/lib.rs index d0e6c024..6cb64eca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod hollow_knight_memory; mod legacy_xml; mod settings_gui; mod splits; +mod timer; use asr::future::{next_tick, retry}; use asr::Process; @@ -15,7 +16,8 @@ use asr::time::Duration; use asr::timer::TimerState; use settings_gui::{SettingsGui, TimingMethod}; use hollow_knight_memory::*; -use splits::{Split, SplitterAction}; +use splits::Split; +use timer::{Resettable, SplitterAction, Timer}; use ugly_widget::store::StoreGui; asr::async_main!(stable); @@ -47,7 +49,6 @@ async fn main() { // TODO: Load some initial information from the process. let mut scene_store = Box::new(SceneStore::new()); let mut load_remover = Box::new(TimingMethodLoadRemover::new(timing_method)); - let mut auto_reset: &'static [TimerState] = splits::auto_reset_safe(&splits); next_tick().await; let game_manager_finder = Box::new(GameManagerFinder::wait_attach(&process).await); @@ -62,12 +63,11 @@ async fn main() { #[cfg(debug_assertions)] asr::print_message(&format!("scenes_grub_rescued: {:?}", scenes_grub_rescued)); - let mut last_timer_state = TimerState::Unknown; - let mut i = 0; + let mut timer = Timer::new(splits.len(), splits::auto_reset_safe(&splits)); loop { - tick_action(&process, &splits, &mut last_timer_state, &mut i, auto_reset, &game_manager_finder, &mut scene_store, &mut player_data_store, &mut scene_data_store, &mut load_remover).await; + tick_action(&process, &splits, &mut timer, &game_manager_finder, &mut scene_store, &mut player_data_store, &mut scene_data_store, &mut load_remover).await; - load_remover.load_removal(&process, &game_manager_finder, i); + load_remover.load_removal(&process, &game_manager_finder); #[cfg(debug_assertions)] let new_scenes_grub_rescued = game_manager_finder.scenes_grub_rescued(&process); @@ -79,13 +79,13 @@ async fn main() { ticks_since_gui += 1; if TICKS_PER_GUI <= ticks_since_gui && gui.load_update_store_if_unchanged() { - if i == 0 && [TimerState::NotRunning, TimerState::Ended].contains(&asr::timer::state()) { + if timer.is_timer_state_between_runs() { if let Some(new_timing_method) = gui.check_timing_method(&mut timing_method) { *load_remover = TimingMethodLoadRemover::new(new_timing_method); } } if let Some(new_splits) = gui.check_splits(&mut splits) { - auto_reset = splits::auto_reset_safe(new_splits); + timer.renew(new_splits.len(), splits::auto_reset_safe(new_splits)); } ticks_since_gui = 0; } @@ -109,65 +109,39 @@ async fn wait_attach_hollow_knight(gui: &mut SettingsGui, timing_method: &mut Ti async fn tick_action( process: &Process, splits: &[splits::Split], - last_timer_state: &mut TimerState, - i: &mut usize, - auto_reset: &'static [TimerState], + timer: &mut Timer, game_manager_finder: &GameManagerFinder, scene_store: &mut SceneStore, player_data_store: &mut PlayerDataStore, scene_data_store: &mut SceneDataStore, load_remover: &mut TimingMethodLoadRemover, ) { - let n = splits.len(); - let timer_state = asr::timer::state(); - match timer_state { - // detect manual resets - TimerState::NotRunning if 0 < *i => { - *i = 0; - load_remover.reset(); - asr::print_message("Detected a manual reset."); - } - // detect manual starts - TimerState::Running if *i == 0 && is_timer_state_between_runs(*last_timer_state) => { - *i = 1; - load_remover.reset(); - asr::print_message("Detected a manual start."); - } - // detect manual end-splits - TimerState::Ended if 0 < *i && *i < n => { - *i = n; - load_remover.reset(); - asr::print_message("Detected a manual end-split."); - } - s => { - *last_timer_state = s; - } - } + timer.update(load_remover); let trans_now = scene_store.transition_now(&process, &game_manager_finder); loop { - let Some(s) = splits.get(*i) else { + let Some(s) = splits.get(timer.i()) else { break; }; let a = splits::splits(s, &process, game_manager_finder, trans_now, scene_store, player_data_store, scene_data_store); match a { SplitterAction::Split | SplitterAction::ManualSplit => { - splitter_action(a, i, n, load_remover); + timer.action(a, load_remover); next_tick().await; break; } SplitterAction::Skip | SplitterAction::Reset => { - splitter_action(a, i, n, load_remover); + timer.action(a, load_remover); next_tick().await; // no break, allow other actions after a skip or reset } SplitterAction::Pass => { - if auto_reset.contains(&timer_state) { + if timer.is_auto_reset_safe() { let a0 = splits::splits(&splits[0], &process, game_manager_finder, trans_now, scene_store, player_data_store, scene_data_store); match a0 { SplitterAction::Split | SplitterAction::Reset => { - *i = 0; - splitter_action(a0, i, n, load_remover); + timer.reset(); + timer.action(a0, load_remover); } _ => (), } @@ -177,10 +151,6 @@ async fn tick_action( } } - if auto_reset.contains(&timer_state) && n <= *i { - *i = 0; - } - if trans_now { if scene_store.pair().old == MENU_TITLE { player_data_store.reset(); @@ -191,45 +161,20 @@ async fn tick_action( } } -fn is_timer_state_between_runs(s: TimerState) -> bool { - s == TimerState::NotRunning || s == TimerState::Ended +enum TimingMethodLoadRemover { + LoadRemover(LoadRemover), + HitCounter(HitCounter), } -fn splitter_action(a: SplitterAction, i: &mut usize, n: usize, load_remover: &mut TimingMethodLoadRemover) { - match a { - SplitterAction::Pass => (), - SplitterAction::Reset => { - asr::timer::reset(); - load_remover.reset(); - *i = 0; - } - SplitterAction::Skip => { - asr::timer::skip_split(); - *i += 1; - } - SplitterAction::Split if *i == 0 => { - asr::timer::reset(); - asr::timer::start(); - load_remover.reset(); - *i += 1; - } - SplitterAction::Split => { - asr::timer::split(); - *i += 1; - } - SplitterAction::ManualSplit => { - if 0 < *i && *i + 1 < n { - *i += 1; - } +impl Resettable for TimingMethodLoadRemover { + fn reset(&mut self) { + match self { + TimingMethodLoadRemover::LoadRemover(lr) => lr.reset(), + TimingMethodLoadRemover::HitCounter(hc) => hc.reset(), } } } -enum TimingMethodLoadRemover { - LoadRemover(LoadRemover), - HitCounter(HitCounter), -} - impl TimingMethodLoadRemover { fn new(timing_method: TimingMethod) -> TimingMethodLoadRemover { match timing_method { @@ -239,17 +184,10 @@ impl TimingMethodLoadRemover { } } - fn reset(&mut self) { + fn load_removal(&mut self, process: &Process, game_manager_finder: &GameManagerFinder) -> Option<()> { match self { - TimingMethodLoadRemover::LoadRemover(lr) => lr.reset(), - TimingMethodLoadRemover::HitCounter(hc) => hc.reset(), - } - } - - fn load_removal(&mut self, process: &Process, game_manager_finder: &GameManagerFinder, i: usize) -> Option<()> { - match self { - TimingMethodLoadRemover::LoadRemover(lr) => lr.load_removal(process, game_manager_finder, i), - TimingMethodLoadRemover::HitCounter(hc) => hc.load_removal(process, game_manager_finder, i), + TimingMethodLoadRemover::LoadRemover(lr) => lr.load_removal(process, game_manager_finder), + TimingMethodLoadRemover::HitCounter(hc) => hc.load_removal(process, game_manager_finder), } } } @@ -261,6 +199,10 @@ struct LoadRemover { last_paused: bool, } +impl Resettable for LoadRemover { + fn reset(&mut self) {} +} + #[allow(unused)] impl LoadRemover { fn new() -> LoadRemover { @@ -272,9 +214,7 @@ impl LoadRemover { } } - fn reset(&mut self) {} - - fn load_removal(&mut self, process: &Process, game_manager_finder: &GameManagerFinder, _i: usize) -> Option<()> { + fn load_removal(&mut self, process: &Process, game_manager_finder: &GameManagerFinder) -> Option<()> { // Initialize pointers for load-remover before timer is running let maybe_ui_state = game_manager_finder.get_ui_state(process); let maybe_scene_name = game_manager_finder.get_scene_name(process); @@ -355,7 +295,15 @@ struct HitCounter { last_hazard: bool, last_dead_or_0: bool, last_exiting_level: Option, - last_index: usize, +} + +impl Resettable for HitCounter { + fn reset(&mut self) { + self.hits = 0; + asr::timer::pause_game_time(); + asr::timer::set_game_time(Duration::seconds(0)); + asr::timer::set_variable_int("hits", 0); + } } #[allow(unused)] @@ -369,27 +317,13 @@ impl HitCounter { last_hazard: false, last_dead_or_0: false, last_exiting_level: None, - last_index: 0, } } - fn reset(&mut self) { - self.hits = 0; - asr::timer::pause_game_time(); - asr::timer::set_game_time(Duration::seconds(0)); - asr::timer::set_variable_int("hits", 0); - } - - fn load_removal(&mut self, process: &Process, game_manager_finder: &GameManagerFinder, i: usize) -> Option<()> { + fn load_removal(&mut self, process: &Process, game_manager_finder: &GameManagerFinder) -> Option<()> { asr::timer::pause_game_time(); - // detect resets - if i == 0 && 0 < self.last_index { - self.reset(); - } - self.last_index = i; - // only count hits if timer is running if asr::timer::state() != TimerState::Running { return Some(()); } diff --git a/src/splits.rs b/src/splits.rs index 661aa53f..a07d6204 100644 --- a/src/splits.rs +++ b/src/splits.rs @@ -7,50 +7,8 @@ use serde::{Deserialize, Serialize}; use ugly_widget::radio_button::{RadioButtonOptions, options_str}; use ugly_widget::store::StoreWidget; -use super::hollow_knight_memory::*; - -#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] -pub enum SplitterAction { - #[default] - Pass, - Split, - Skip, - Reset, - ManualSplit, -} - -impl SplitterAction { - pub fn or_else SplitterAction>(self, f: F) -> SplitterAction { - match self { - SplitterAction::Pass => f(), - a => a, - } - } -} - -fn should_split(b: bool) -> SplitterAction { - if b { - SplitterAction::Split - } else { - SplitterAction::Pass - } -} - -fn should_skip(b: bool) -> SplitterAction { - if b { - SplitterAction::Skip - } else { - SplitterAction::Pass - } -} - -fn should_split_skip(mb: Option) -> SplitterAction { - match mb { - Some(true) => SplitterAction::Split, - Some(false) => SplitterAction::Skip, - None => SplitterAction::Pass, - } -} +use crate::hollow_knight_memory::*; +use crate::timer::*; #[derive(Clone, Debug, Default, Deserialize, Eq, Gui, Ord, PartialEq, PartialOrd, RadioButtonOptions, Serialize)] pub enum Split { diff --git a/src/timer.rs b/src/timer.rs new file mode 100644 index 00000000..1d072cd1 --- /dev/null +++ b/src/timer.rs @@ -0,0 +1,186 @@ + +use asr::timer::TimerState; +use serde::{Deserialize, Serialize}; + +pub fn is_timer_state_between_runs(s: TimerState) -> bool { + s == TimerState::NotRunning || s == TimerState::Ended +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum SplitterAction { + #[default] + Pass, + Split, + Skip, + Reset, + ManualSplit, +} + +impl SplitterAction { + pub fn or_else SplitterAction>(self, f: F) -> SplitterAction { + match self { + SplitterAction::Pass => f(), + a => a, + } + } +} + +pub fn should_split(b: bool) -> SplitterAction { + if b { + SplitterAction::Split + } else { + SplitterAction::Pass + } +} + +pub fn should_skip(b: bool) -> SplitterAction { + if b { + SplitterAction::Skip + } else { + SplitterAction::Pass + } +} + +pub fn should_split_skip(mb: Option) -> SplitterAction { + match mb { + Some(true) => SplitterAction::Split, + Some(false) => SplitterAction::Skip, + None => SplitterAction::Pass, + } +} + +pub trait Resettable { + fn reset(&mut self); +} + +/// Keep track of autosplit index here because asr::timer won't. +pub struct Timer { + /// The timer state. + state: TimerState, + /// The last observed asr::timer::state. + /// Just in case asr::timer::state is a tad out-of-date. + last_state: TimerState, + /// Index into the list of autosplits including start and end. + /// Not a segment index, since start is not a segment. + /// 0: either NotRunning, or Ended with auto-reset safe + /// [1,n): Running + /// n: Ended without knowing auto-reset safe + i: usize, + /// Number of autosplits including both start and end. + /// One more than the number of segments. + n: usize, + /// The set of timer states where it is safe to use auto-reset. + auto_reset: &'static [TimerState], +} + +impl Resettable for Timer { + fn reset(&mut self) { + asr::timer::reset(); + self.state = TimerState::NotRunning; + self.i = 0; + } +} + +impl Timer { + pub fn new(n: usize, auto_reset: &'static [TimerState]) -> Timer { + let asr_state = asr::timer::state(); + Timer { + state: asr_state, + last_state: asr_state, + i: 0, + n, + auto_reset, + } + } + + pub fn renew(&mut self, n: usize, auto_reset: &'static [TimerState]) { + self.n = n; + self.auto_reset = auto_reset; + } + + pub fn i(&self) -> usize { + self.i + } + + pub fn is_auto_reset_safe(&self) -> bool { + self.auto_reset.contains(&self.state) + } + + pub fn is_timer_state_between_runs(&self) -> bool { + is_timer_state_between_runs(self.state) + } + + pub fn update(&mut self, r: &mut R) { + let asr_state = asr::timer::state(); + if asr_state == self.state || asr_state == self.last_state { + self.last_state = asr_state; + return; + } + match asr_state { + // detect manual resets + TimerState::NotRunning => { + self.i = 0; + r.reset(); + asr::print_message("Detected a manual reset."); + } + // detect manual starts + TimerState::Running if is_timer_state_between_runs(self.state) => { + self.i = 1; + r.reset(); + asr::print_message("Detected a manual start."); + } + // detect manual end-splits + TimerState::Ended => { + if self.is_auto_reset_safe() { + // do NOT actually reset + // 0: either NotRunning, or Ended with auto-reset safe + self.i = 0; + } else { + self.i = self.n; + } + asr::print_message("Detected a manual end-split."); + } + _ => () + } + self.state = asr_state; + self.last_state = asr_state; + } + + pub fn action(&mut self, a: SplitterAction, r: &mut R) { + match a { + SplitterAction::Pass => (), + SplitterAction::Reset => { + self.reset(); + r.reset(); + } + SplitterAction::Skip => { + asr::timer::skip_split(); + self.i += 1; + } + SplitterAction::Split => { + if self.i == 0 { + asr::timer::reset(); + asr::timer::start(); + r.reset(); + self.state = TimerState::Running; + } else { + asr::timer::split(); + } + self.i += 1; + } + SplitterAction::ManualSplit => { + if 0 < self.i && self.i + 1 < self.n { + self.i += 1; + } + } + } + if self.n <= self.i { + self.state = TimerState::Ended; + if self.is_auto_reset_safe() { + // do NOT actually reset + // 0: either NotRunning, or Ended with auto-reset safe + self.i = 0; + } + } + } +}