Skip to content

Commit

Permalink
feature: Use incremental search for key matcher.
Browse files Browse the repository at this point in the history
  • Loading branch information
shanecelis committed Apr 22, 2024
1 parent 133fa26 commit 9b521f5
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 27 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
42 changes: 39 additions & 3 deletions src/cache.rs
Original file line number Diff line number Diff line change
@@ -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<A, In> {
pub(crate) trie: Option<Trie<A, InputSequence<A, In>>>,
trie: Option<Trie<A, InputSequence<A, In>>>,
position: Option<Position>,
}

impl<A, In> InputSequenceCache<A, In>
Expand All @@ -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<A, InputSequence<A, In>>) -> IncSearch<'a, A, InputSequence<A, In>> {
pub fn recall<'a, 'b>(&'b mut self, sequences: impl Iterator<Item = &'a InputSequence<A, In>>) -> IncSearch<'a, A, InputSequence<A, In>>
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<A, In> InputSequenceCache<A, In>
{
/// Clears the cache.
pub fn reset(&mut self) {
self.trie = None;
self.position = None;
}
}

impl<A, In> Default for InputSequenceCache<A, In> {
fn default() -> Self {
Self { trie: None }
Self {
trie: None,
position: None,
}
}
}
2 changes: 1 addition & 1 deletion src/frame_time.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![forbid(missing_docs)]

pub mod action;
mod cache;
pub mod cache;
mod chord;
pub mod cond_system;
mod covec;
Expand All @@ -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;
Expand Down
109 changes: 89 additions & 20 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -166,7 +169,7 @@ fn detect_additions<A: Clone + Send + Sync + 'static, In: 'static>(
mut cache: ResMut<InputSequenceCache<A, In>>,
) {
if secrets.iter().next().is_some() {
cache.trie = None;
cache.reset();
}
}

Expand All @@ -175,7 +178,7 @@ fn detect_removals<A: Clone + Send + Sync + 'static, In: 'static>(
mut removals: RemovedComponents<InputSequence<A, In>>,
) {
if removals.read().next().is_some() {
cache.trie = None;
cache.reset();
}
}

Expand Down Expand Up @@ -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<Item = K> + 'b,
min_prefix: &'b mut Option<usize>)
-> impl Iterator<Item = &'a V> + '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<K, V>, input: &mut Vec<K>) -> impl Iterator<Item = &'a V>
where
K: Clone + Eq + Ord,
// V: Clone,
{
let mut result = vec![];
let mut min_prefix = None;
Expand Down
3 changes: 3 additions & 0 deletions tests/simulated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ mod simulate_app {
.time_limit(TimeLimit::Frames(1)),
);

// eprintln!("t0");
press_key(&mut app, KeyCode::KeyA);
app.update();
assert!(app
Expand All @@ -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
Expand Down

0 comments on commit 9b521f5

Please sign in to comment.