[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:
parent
496bea1df2
commit
838b329553
2 changed files with 86 additions and 21 deletions
12
src/main.rs
12
src/main.rs
|
@ -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);
|
||||||
|
|
95
src/roftl.rs
95
src/roftl.rs
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue