From 838b329553d079f9ed2f7df3695d19af136462a1 Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Tue, 16 Nov 2021 23:37:56 +0100 Subject: [PATCH] [WIP] Concurrent additional sources Additional entries are sources concurrently. With a split into primary and additional sources, this has the major advantage that only primary source has to provide immediate results. Additional sources start sourcing in the background and have at least the time a human needs to provide input and reaction time to process the visible results without an actual visible delay. --- src/main.rs | 12 ++++--- src/roftl.rs | 95 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7d94219..18ffede 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,15 +21,17 @@ fn main() -> Result<(), Box> { debug!{"Set up roftl"}; let mut roftl = roftl::Roftl::new() .add_source(sources::TestSource::new("ts1")) - .add_source(sources::Window::new()) .add_source(sources::Apps::new()) .add_source(sources::ShellHost::new()) .with_matcher(FuseMatcher::new()) + .with_primary_source(sources::Window::new()) .with_primary_matcher(PrefixMatcher::new()); + debug!{"Source primary roftl sources"} + roftl.source_primary(); - debug!{"Source roftl sources"} - roftl.source(); + debug!{"Source additional roftl sources"} + roftl.source_additional(); debug!{"Build window"} let event_loop = EventLoop::new(); @@ -41,8 +43,8 @@ fn main() -> Result<(), Box> { debug!{"Window id: {:?}", window.id()} - debug!{"Draw empty state to window"} - ui::draw(&window, "", vec![], 0); + debug!{"Draw primary state to window"} + ui::draw(&window, "", roftl.narrow(""), 0); debug!{"Start event loop"} let roftl_loop = RoftlLoop::new(roftl, window); diff --git a/src/roftl.rs b/src/roftl.rs index 45c7f7f..785ded8 100644 --- a/src/roftl.rs +++ b/src/roftl.rs @@ -1,3 +1,6 @@ +use std::rc::Rc; +use std::sync::RwLock; +use std::sync::Arc; use log::debug; use rayon::prelude::*; @@ -31,7 +34,9 @@ pub trait Matcher: Send + Sync { } pub struct Roftl { - sources: Vec, + sources: Arc>>, + source_chan: Option>>, + primary_source: Option, matcher: MatcherRef, // the standard matcher can be inadequate for primary mode. E.g. a // FuseMatcher would yield hard to guess and sometimes surprising results, @@ -45,7 +50,9 @@ pub struct Roftl { impl Roftl { pub fn new() -> Self { Roftl { - sources: vec![], + sources: Arc::from(RwLock::from(vec![])), + source_chan: None, + primary_source: None, matcher: PrefixMatcher::new(), primary_matcher: PrefixMatcher::new(), entries: vec![], @@ -53,12 +60,18 @@ impl Roftl { } } + pub fn with_primary_source(mut self, source: SourceRef) -> Self { + self.primary_source = Some(source); + + self + } + pub fn add_source(mut self, source: SourceRef) -> Self { - if self.sources.par_iter().any(|s| s.name().eq(source.name())) { + if self.sources.read().unwrap().par_iter().any(|s| s.name().eq(source.name())) { panic! {"Source with name '{}' already exists", source.name()} } - self.sources.push(source); + self.sources.write().unwrap().push(source); self } @@ -72,13 +85,29 @@ impl Roftl { self } - pub fn source(&mut self) { - self.entries = self.sources + pub fn source_primary(&mut self) { + self.entries = self.primary_source .par_iter_mut() .flat_map(|s| s.entries()) .collect(); - debug!("Sourced {} entries from {} sources", self.entries.len(), self.sources.len()); + debug!("Sourced {} primary entries", self.entries.len()); + } + + pub fn source_additional(&mut self) { + let (tx, rx) = std::sync::mpsc::channel(); + self.source_chan = Some(rx); + + let sources = self.sources.clone(); + std::thread::spawn(move || { + let entries: Vec = sources.write().unwrap() + .par_iter_mut() + .flat_map(|s| s.entries()) + .collect(); + + debug!("Sourced {} additional entries from {} sources", entries.len(), sources.read().unwrap().len()); + tx.send(entries).unwrap(); + }); } pub fn narrow(&mut self, input: &str) -> Vec<(&Entry, Vec)> { @@ -86,6 +115,19 @@ impl Roftl { completions.insert("e", "emacs"); completions.insert("f", "firefox"); + // check if we received anything in self. + if let Some(ref rx) = self.source_chan { + if let Ok(mut entries) = rx.try_recv() { + debug!("Processing {} additional entries", entries.len()); + self.entries.append(&mut entries); + } else { + debug!("No additional entries for processing (yet)"); + } + } + + // we need to primary_matcher here s.t. only the primary matcher + // and not self is captured by the filter_map lambda + let primary_matcher = &self.primary_matcher; let mut scored_entries: Vec<(f64, usize, &Entry, Vec)> = self.entries .par_iter() .enumerate() @@ -96,20 +138,25 @@ impl Roftl { let input = completions.get(input).unwrap_or(&input); - let match_result = self.primary_matcher.try_match(&entry.name, input); + let match_result = primary_matcher.try_match(&entry.name, input); return match_result.map(|(score, indices)| (score, idx, entry, indices) ) }) .collect(); + // we need to matcher here s.t. only the matcher + // and not self is captured by the filter_map lambda if scored_entries.is_empty() || input.starts_with(",") { let input = input.strip_prefix(",").unwrap_or(input); + let matcher = &self.matcher; + scored_entries = self.entries .par_iter() .enumerate() .filter_map(|(idx, entry)| { + if entry.source == "window" { return None } if input.is_empty() { return Some((0.0, idx, entry, vec![])) } - let match_result = self.matcher.try_match(&entry.name, input); + let match_result = matcher.try_match(&entry.name, input); return match_result.map(|(score, indices)| (score, idx, entry, indices) ) }) .collect(); @@ -131,30 +178,46 @@ impl Roftl { pub fn actions(&self, selection_id: usize) -> Vec { let (entry, source) = self.find_selection(selection_id); - source.actions(entry) + match source { + usize::MAX => self.primary_source.as_ref().unwrap().actions(entry), + _ => self.sources.read().unwrap().get(source).unwrap().actions(entry) + } } pub fn exec_default(&self, selection_id: usize) { let (entry, source) = self.find_selection(selection_id); - source.exec_default(entry); + match source { + usize::MAX => self.primary_source.as_ref().unwrap().exec_default(entry), + _ => self.sources.read().unwrap().get(source).unwrap().exec_default(entry) + } } pub fn exec_action(&self, selection_id: usize, action: u8) { let (entry, source) = self.find_selection(selection_id); - source.exec_action(entry, action); + match source { + usize::MAX => self.primary_source.as_ref().unwrap().exec_action(entry, action), + _ => self.sources.read().unwrap().get(source).unwrap().exec_action(entry, action) + } } - fn find_selection(&self, selection_id: usize) -> (&Entry, &Box) + fn find_selection(&self, selection_id: usize) -> (&Entry, usize) { let entry_id = self.narrow_map[selection_id]; let entry = &self.entries[entry_id]; debug!{"Got entry {:?} for id {} ", entry, entry_id}; - let source = self.sources + match self.primary_source { + Some(ref s) if s.name().eq(entry.source) => return (entry, usize::MAX), + _ => {} + } + + let sources = self.sources.read().unwrap(); + let source = sources .iter() - .find(|s| s.name().eq(entry.source)) + .enumerate() + .find(|(_i, s)| s.name().eq(entry.source)) .unwrap(); - (entry, source) + (entry, source.0) } }