[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"};
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<dyn Error>> {
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);

View file

@ -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<SourceRef>,
sources: Arc<RwLock<Vec<SourceRef>>>,
source_chan: Option<std::sync::mpsc::Receiver<Vec<Entry>>>,
primary_source: Option<SourceRef>,
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<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>)> {
@ -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<usize>)> = 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<String> {
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<dyn Source>)
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)
}
}