[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.
This commit is contained in:
Armin Friedl 2021-11-16 23:37:56 +01:00
parent 496bea1df2
commit 838b329553
2 changed files with 86 additions and 21 deletions

View file

@ -21,15 +21,17 @@ fn main() -> Result<(), Box<dyn Error>> {
debug!{"Set up roftl"}; debug!{"Set up roftl"};
let mut roftl = roftl::Roftl::new() let mut roftl = roftl::Roftl::new()
.add_source(sources::TestSource::new("ts1")) .add_source(sources::TestSource::new("ts1"))
.add_source(sources::Window::new())
.add_source(sources::Apps::new()) .add_source(sources::Apps::new())
.add_source(sources::ShellHost::new()) .add_source(sources::ShellHost::new())
.with_matcher(FuseMatcher::new()) .with_matcher(FuseMatcher::new())
.with_primary_source(sources::Window::new())
.with_primary_matcher(PrefixMatcher::new()); .with_primary_matcher(PrefixMatcher::new());
debug!{"Source primary roftl sources"}
roftl.source_primary();
debug!{"Source roftl sources"} debug!{"Source additional roftl sources"}
roftl.source(); roftl.source_additional();
debug!{"Build window"} debug!{"Build window"}
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
@ -41,8 +43,8 @@ fn main() -> Result<(), Box<dyn Error>> {
debug!{"Window id: {:?}", window.id()} debug!{"Window id: {:?}", window.id()}
debug!{"Draw empty state to window"} debug!{"Draw primary state to window"}
ui::draw(&window, "", vec![], 0); ui::draw(&window, "", roftl.narrow(""), 0);
debug!{"Start event loop"} debug!{"Start event loop"}
let roftl_loop = RoftlLoop::new(roftl, window); let roftl_loop = RoftlLoop::new(roftl, window);

View file

@ -1,3 +1,6 @@
use std::rc::Rc;
use std::sync::RwLock;
use std::sync::Arc;
use log::debug; use log::debug;
use rayon::prelude::*; use rayon::prelude::*;
@ -31,7 +34,9 @@ pub trait Matcher: Send + Sync {
} }
pub struct Roftl { pub struct Roftl {
sources: Vec<SourceRef>, sources: Arc<RwLock<Vec<SourceRef>>>,
source_chan: Option<std::sync::mpsc::Receiver<Vec<Entry>>>,
primary_source: Option<SourceRef>,
matcher: MatcherRef, matcher: MatcherRef,
// the standard matcher can be inadequate for primary mode. E.g. a // the standard matcher can be inadequate for primary mode. E.g. a
// FuseMatcher would yield hard to guess and sometimes surprising results, // FuseMatcher would yield hard to guess and sometimes surprising results,
@ -45,7 +50,9 @@ pub struct Roftl {
impl Roftl { impl Roftl {
pub fn new() -> Self { pub fn new() -> Self {
Roftl { Roftl {
sources: vec![], sources: Arc::from(RwLock::from(vec![])),
source_chan: None,
primary_source: None,
matcher: PrefixMatcher::new(), matcher: PrefixMatcher::new(),
primary_matcher: PrefixMatcher::new(), primary_matcher: PrefixMatcher::new(),
entries: vec![], 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 { 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()} panic! {"Source with name '{}' already exists", source.name()}
} }
self.sources.push(source); self.sources.write().unwrap().push(source);
self self
} }
@ -72,13 +85,29 @@ impl Roftl {
self self
} }
pub fn source(&mut self) { pub fn source_primary(&mut self) {
self.entries = self.sources self.entries = self.primary_source
.par_iter_mut() .par_iter_mut()
.flat_map(|s| s.entries()) .flat_map(|s| s.entries())
.collect(); .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<Entry> = 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<usize>)> { pub fn narrow(&mut self, input: &str) -> Vec<(&Entry, Vec<usize>)> {
@ -86,6 +115,19 @@ impl Roftl {
completions.insert("e", "emacs"); completions.insert("e", "emacs");
completions.insert("f", "firefox"); 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<usize>)> = self.entries let mut scored_entries: Vec<(f64, usize, &Entry, Vec<usize>)> = self.entries
.par_iter() .par_iter()
.enumerate() .enumerate()
@ -96,20 +138,25 @@ impl Roftl {
let input = completions.get(input).unwrap_or(&input); 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) ) return match_result.map(|(score, indices)| (score, idx, entry, indices) )
}) })
.collect(); .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(",") { if scored_entries.is_empty() || input.starts_with(",") {
let input = input.strip_prefix(",").unwrap_or(input); let input = input.strip_prefix(",").unwrap_or(input);
let matcher = &self.matcher;
scored_entries = self.entries scored_entries = self.entries
.par_iter() .par_iter()
.enumerate() .enumerate()
.filter_map(|(idx, entry)| { .filter_map(|(idx, entry)| {
if entry.source == "window" { return None }
if input.is_empty() { return Some((0.0, idx, entry, vec![])) } 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) ) return match_result.map(|(score, indices)| (score, idx, entry, indices) )
}) })
.collect(); .collect();
@ -131,30 +178,46 @@ impl Roftl {
pub fn actions(&self, selection_id: usize) -> Vec<String> { pub fn actions(&self, selection_id: usize) -> Vec<String> {
let (entry, source) = self.find_selection(selection_id); 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) { pub fn exec_default(&self, selection_id: usize) {
let (entry, source) = self.find_selection(selection_id); 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) { pub fn exec_action(&self, selection_id: usize, action: u8) {
let (entry, source) = self.find_selection(selection_id); 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<dyn Source>) fn find_selection(&self, selection_id: usize) -> (&Entry, usize)
{ {
let entry_id = self.narrow_map[selection_id]; let entry_id = self.narrow_map[selection_id];
let entry = &self.entries[entry_id]; let entry = &self.entries[entry_id];
debug!{"Got entry {:?} for id {} ", entry, 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() .iter()
.find(|s| s.name().eq(entry.source)) .enumerate()
.find(|(_i, s)| s.name().eq(entry.source))
.unwrap(); .unwrap();
(entry, source) (entry, source.0)
} }
} }