From 9b521f5f13092cf33b2cf43eb496f0fcb35ad4cb Mon Sep 17 00:00:00 2001 From: Shane Celis Date: Mon, 22 Apr 2024 06:32:55 -0400 Subject: [PATCH] feature: Use incremental search for key matcher. --- Cargo.toml | 3 +- src/cache.rs | 42 +++++++++++++++-- src/frame_time.rs | 2 +- src/lib.rs | 5 ++- src/plugin.rs | 109 ++++++++++++++++++++++++++++++++++++--------- tests/simulated.rs | 3 ++ 6 files changed, 137 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cbdb788..bdda4c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,8 @@ path = "examples/multiple_input.rs" [dependencies] bevy = { version = "0.13", default-features = false, features = [] } -trie-rs = { version = "0.3" } +# trie-rs = { version = "0.3" } +trie-rs = { path = "../trie-rs" } keyseq = { version = "0.2.3", features = [ "bevy" ] } [dev-dependencies] diff --git a/src/cache.rs b/src/cache.rs index a072f89..218cb3f 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,11 +1,17 @@ +//! Cache the trie for reuse. use crate::input_sequence::InputSequence; use bevy::ecs::system::Resource; -use trie_rs::map::{Trie, TrieBuilder}; +use trie_rs::{ + map::{Trie, TrieBuilder}, + inc_search::{IncSearch, + Position} +}; /// Contains the trie for the input sequences. #[derive(Resource)] pub struct InputSequenceCache { - pub(crate) trie: Option>>, + trie: Option>>, + position: Option, } impl InputSequenceCache @@ -25,13 +31,43 @@ where for sequence in sequences { builder.insert(sequence.acts.clone(), sequence.clone()); } + assert!(self.position.is_none(), "Position should be none when rebuilding trie"); builder.build() }) } + + /// Store a search. + pub fn store(&mut self, position: Position) { + self.position = Some(position); + } + + /// Recall a search OR create a new search. + // pub fn recall<'a>(&self, trie: &'a Trie>) -> IncSearch<'a, A, InputSequence> { + pub fn recall<'a, 'b>(&'b mut self, sequences: impl Iterator>) -> IncSearch<'a, A, InputSequence> + where 'b: 'a { + let position = self.position.clone(); + let trie = self.trie(sequences); + position + .map(move |p| IncSearch::resume(trie, p)) + .unwrap_or_else(move || trie.inc_search()) + } + +} + +impl InputSequenceCache +{ + /// Clears the cache. + pub fn reset(&mut self) { + self.trie = None; + self.position = None; + } } impl Default for InputSequenceCache { fn default() -> Self { - Self { trie: None } + Self { + trie: None, + position: None, + } } } diff --git a/src/frame_time.rs b/src/frame_time.rs index 9b85bb5..b09cdde 100644 --- a/src/frame_time.rs +++ b/src/frame_time.rs @@ -1,6 +1,6 @@ use crate::time_limit::TimeLimit; -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct FrameTime { pub(crate) frame: u32, pub(crate) time: f32, diff --git a/src/lib.rs b/src/lib.rs index f29db59..3e1b0e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ #![forbid(missing_docs)] pub mod action; -mod cache; +pub mod cache; mod chord; pub mod cond_system; mod covec; @@ -20,10 +20,11 @@ pub use keyseq::{ /// Convenient splat import pub mod prelude { pub use super::{action, keyseq, InputSequencePlugin, Modifiers, TimeLimit}; - pub use crate::input_sequence::{ButtonSequence, InputSequence, KeySequence}; + pub use super::input_sequence::{ButtonSequence, InputSequence, KeySequence}; pub use super::cond_system::IntoCondSystem; pub use std::time::Duration; + pub use super::chord::KeyChord; } pub use time_limit::TimeLimit; diff --git a/src/plugin.rs b/src/plugin.rs index e109a9e..3d2b11f 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -26,7 +26,10 @@ use crate::{ input_sequence::{ButtonSequence, InputSequence, KeySequence}, KeyChord, Modifiers, }; -use trie_rs::map::Trie; +use trie_rs::{ + map::Trie, + inc_search::{IncSearch, Answer}, +}; /// ButtonInput sequence plugin. pub struct InputSequencePlugin { @@ -166,7 +169,7 @@ fn detect_additions( mut cache: ResMut>, ) { if secrets.iter().next().is_some() { - cache.trie = None; + cache.reset(); } } @@ -175,7 +178,7 @@ fn detect_removals( mut removals: RemovedComponents>, ) { if removals.read().next().is_some() { - cache.trie = None; + cache.reset(); } } @@ -232,38 +235,104 @@ fn key_sequence_matcher( mut commands: Commands, ) { let mods = Modifiers::from_input(&keys); - let trie = cache.trie(secrets.iter()); let now = FrameTime { frame: frame_count.0, time: time.elapsed_seconds(), }; - for key_code in keys.get_just_pressed() { - if is_modifier(*key_code) { - continue; - } - let key = KeyChord(mods, *key_code); - last_keys.push(key, now.clone()); - let start = last_keys.1[0].clone(); - for seq in consume_input(trie, &mut last_keys.0) { + let maybe_start = last_keys.1.first().cloned(); + let mut input = keys.get_just_pressed() + .filter(|k| ! is_modifier(**k)) + .map(|k| { + let chord = KeyChord(mods, *k); + last_keys.push(chord.clone(), now.clone()); + chord + }) + .peekable(); + if input.peek().is_none() { + return; + } + let mut search = cache.recall(secrets.iter()); + let mut min_prefix = None; + + // eprintln!("maybe_start {maybe_start:?} now {now:?}"); + for seq in inc_consume_input(&mut search, + input, + &mut min_prefix) { + if let Some(ref start) = maybe_start { if seq - .time_limit - .as_ref() - .map(|limit| (&now - &start).has_timedout(limit)) - .unwrap_or(false) + .time_limit + .as_ref() + .map(|limit| (&now - &start).has_timedout(limit)) + .unwrap_or(false) { // Sequence timed out. - } else { - commands.run_system(seq.system_id); + continue; } } - last_keys.drain1_sync(); + commands.run_system(seq.system_id); } + // eprintln!("min_prefix {min_prefix:?}"); + match min_prefix { + Some(i) => { + let _ = last_keys.0.drain(0..i); + } + None => { + last_keys.0.clear(); + } + } + last_keys.drain1_sync(); + let position = search.into(); + cache.store(position); +} + +/// Incrementally consume the input. +fn inc_consume_input<'a, 'b, K, V>(search: &'b mut IncSearch<'a, K, V>, + input: impl Iterator + 'b, + min_prefix: &'b mut Option) + -> impl Iterator + 'b +where + K: Clone + Eq + Ord, + 'a: 'b, +{ + let mut i = 0; + input.filter_map(move |k| { + i += 1; + match search.query(&k) { + Some(Answer::Match) => { + let result = Some(search.value().unwrap()); + search.reset(); + *min_prefix = None; + result + } + Some(Answer::Prefix) if min_prefix.is_none() => { + *min_prefix = Some(i - 1); + None + } + Some(Answer::PrefixAndMatch) => { + Some(search.value().unwrap()) + } + Some(Answer::Prefix) => { + None + } + None => { + search.reset(); + *min_prefix = None; + // This could be the start of a new sequence. + if search.query(&k).is_none() { + // This may not be necessary. + search.reset(); + } else { + *min_prefix = Some(i - 1); + } + None + } + } + }) } fn consume_input<'a, K, V>(trie: &'a Trie, input: &mut Vec) -> impl Iterator where K: Clone + Eq + Ord, - // V: Clone, { let mut result = vec![]; let mut min_prefix = None; diff --git a/tests/simulated.rs b/tests/simulated.rs index 9418e31..78c4308 100644 --- a/tests/simulated.rs +++ b/tests/simulated.rs @@ -498,6 +498,7 @@ mod simulate_app { .time_limit(TimeLimit::Frames(1)), ); + // eprintln!("t0"); press_key(&mut app, KeyCode::KeyA); app.update(); assert!(app @@ -507,9 +508,11 @@ mod simulate_app { .next() .is_none()); + // eprintln!("t1"); clear_just_pressed(&mut app, KeyCode::KeyA); app.update(); + // eprintln!("t2"); press_key(&mut app, KeyCode::KeyB); app.update(); assert!(app