diff options
author | Canop <cano.petrole@gmail.com> | 2020-02-02 17:23:30 +0100 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2020-02-02 17:23:30 +0100 |
commit | acc1486032aaad94fc852f3001e6b13bbeecfe11 (patch) | |
tree | e38815eaf7a5da88bcb0c603d9f7cfedb2ddeee4 | |
parent | b10cc444d58cb617c820f4a98d7a6e3d2bd09b46 (diff) |
background computation of git repo statistics
This is still a temporary no-caching solution.
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | src/app.rs | 36 | ||||
-rw-r--r-- | src/app_state.rs | 4 | ||||
-rw-r--r-- | src/browser_states.rs | 73 | ||||
-rw-r--r-- | src/browser_verbs.rs | 6 | ||||
-rw-r--r-- | src/displayable_tree.rs | 5 | ||||
-rw-r--r-- | src/file_sizes/file_sizes_default.rs | 4 | ||||
-rw-r--r-- | src/file_sizes/file_sizes_unix.rs | 18 | ||||
-rw-r--r-- | src/file_sizes/mod.rs | 6 | ||||
-rw-r--r-- | src/flat_tree.rs | 22 | ||||
-rw-r--r-- | src/git_status_computer.rs | 69 | ||||
-rw-r--r-- | src/help_states.rs | 4 | ||||
-rw-r--r-- | src/help_verbs.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 3 | ||||
-rw-r--r-- | src/task_sync.rs | 170 | ||||
-rw-r--r-- | src/tree_build/builder.rs | 24 |
16 files changed, 357 insertions, 93 deletions
@@ -93,7 +93,7 @@ dependencies = [ [[package]] name = "broot" -version = "0.12.2" +version = "0.13.0" dependencies = [ "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -22,7 +22,7 @@ use { screens::Screen, skin::Skin, status::Status, - task_sync::TaskLifetime, + task_sync::Dam, }, crossterm::{ self, cursor, @@ -77,18 +77,18 @@ impl App { } /// execute all the pending tasks until there's none remaining or - /// the allowed lifetime is expired (usually when the user typed a new key) + /// the dam asks for interruption fn do_pending_tasks( &mut self, w: &mut impl Write, cmd: &Command, screen: &mut Screen, con: &AppContext, - tl: TaskLifetime, + dam: &mut Dam, ) -> Result<(), ProgramError> { let state = self.mut_state(); - while state.has_pending_task() & !tl.is_expired() { - state.do_pending_task(screen, &tl); + while state.has_pending_task() & !dam.has_event() { + state.do_pending_task(screen, dam); state.display(w, screen, con)?; state.write_status(w, cmd, &screen, con)?; } @@ -187,22 +187,24 @@ impl App { debug!("we're on screen"); let mut screen = Screen::new(con, skin)?; - // we listen for events in a separate thread so that we can go on listening - // when a long search is running, and interrupt it if needed let mut writer = WriteCleanup::build( writer, |w| just_queue(w, EnableMouseCapture), |w| just_queue(w, DisableMouseCapture), )?; + + // we listen for events in a separate thread so that we can go on listening + // when a long search is running, and interrupt it if needed let event_source = EventSource::new()?; let rx_events = event_source.receiver(); + let mut dam = Dam::from(rx_events); self.push(Box::new( BrowserState::new( con.launch_args.root.clone(), con.launch_args.tree_options.clone(), &screen, - &TaskLifetime::unlimited(), + &Dam::unlimited(), )? .expect("Failed to create BrowserState"), )); @@ -212,10 +214,15 @@ impl App { // if some commands were passed to the application // we execute them before even starting listening for events if let Some(unparsed_commands) = &con.launch_args.commands { - let lifetime = TaskLifetime::unlimited(); for arg_cmd in parse_command_sequence(unparsed_commands, con)? { cmd = self.apply_command(&mut writer, arg_cmd, &mut screen, con)?; - self.do_pending_tasks(&mut writer, &cmd, &mut screen, con, lifetime.clone())?; + self.do_pending_tasks( + &mut writer, + &cmd, + &mut screen, + con, + &mut dam, + )?; if self.quitting { return Ok(self.launch_at_end.take()); } @@ -229,13 +236,12 @@ impl App { screen.input_field.display_on(&mut writer)?; loop { - let tl = TaskLifetime::new(event_source.shared_event_count()); if !self.quitting { - self.do_pending_tasks(&mut writer, &cmd, &mut screen, con, tl)?; + self.do_pending_tasks(&mut writer, &cmd, &mut screen, con, &mut dam)?; } - let event = match rx_events.recv() { - Ok(event) => event, - Err(_) => { + let event = match dam.next_event() { + Some(event) => event, + None => { // this is how we quit the application, // when the input thread is properly closed break; diff --git a/src/app_state.rs b/src/app_state.rs index 5952929..1d8b1b2 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -6,7 +6,7 @@ use { errors::{ProgramError, TreeBuildError}, external::Launchable, screens::Screen, - task_sync::TaskLifetime, + task_sync::Dam, }, std::io::Write, }; @@ -59,7 +59,7 @@ pub trait AppState { fn refresh(&mut self, screen: &Screen, con: &AppContext) -> Command; - fn do_pending_task(&mut self, screen: &mut Screen, tl: &TaskLifetime); + fn do_pending_task(&mut self, screen: &mut Screen, dam: &mut Dam); fn has_pending_task(&self) -> bool; diff --git a/src/browser_states.rs b/src/browser_states.rs index 97be070..05033a1 100644 --- a/src/browser_states.rs +++ b/src/browser_states.rs @@ -7,11 +7,12 @@ use { errors::{ProgramError, TreeBuildError}, external::Launchable, flat_tree::{LineType, Tree}, + git_status_computer, help_states::HelpState, patterns::Pattern, screens::{self, Screen}, status::Status, - task_sync::TaskLifetime, + task_sync::Dam, tree_build::TreeBuilder, tree_options::TreeOptions, verb_store::PrefixSearchResult, @@ -40,11 +41,11 @@ impl BrowserState { path: PathBuf, mut options: TreeOptions, screen: &Screen, - tl: &TaskLifetime, + dam: &Dam, ) -> Result<Option<BrowserState>, TreeBuildError> { let pending_pattern = options.pattern.take(); let builder = TreeBuilder::from(path, options, BrowserState::page_height(screen) as usize)?; - Ok(builder.build(tl, false).map(move |tree| BrowserState { + Ok(builder.build(false, dam).map(move |tree| BrowserState { tree, filtered_tree: None, pending_pattern, @@ -65,7 +66,7 @@ impl BrowserState { tree.root().clone(), options, screen, - &TaskLifetime::unlimited(), + &Dam::unlimited(), ), Command::from_pattern(&tree.options.pattern), ) @@ -94,7 +95,6 @@ impl BrowserState { ) -> Result<AppStateCmdResult, ProgramError> { let tree = self.displayed_tree(); let line = tree.selected_line(); - let tl = TaskLifetime::unlimited(); match &line.line_type { LineType::File => match open::that(&line.path) { Ok(exit_status) => { @@ -112,8 +112,9 @@ impl BrowserState { target = PathBuf::from(parent); } } + let dam = Dam::unlimited(); Ok(AppStateCmdResult::from_optional_state( - BrowserState::new(target, tree.options.without_pattern(), screen, &tl), + BrowserState::new(target, tree.options.without_pattern(), screen, &dam), Command::new(), )) } @@ -217,7 +218,13 @@ fn make_opener( impl AppState for BrowserState { fn has_pending_task(&self) -> bool { - self.pending_pattern.is_some() || self.displayed_tree().has_dir_missing_size() + debug!( + "CHECK self.displayed_tree().is_missing_git_status_computation() = {}", + self.displayed_tree().is_missing_git_status_computation(), + ); + self.pending_pattern.is_some() + || self.displayed_tree().has_dir_missing_size() + || self.displayed_tree().is_missing_git_status_computation() } fn write_status( @@ -391,8 +398,9 @@ impl AppState for BrowserState { } /// do some work, totally or partially, if there's some to do. - /// Stop as soon as the lifetime is expired. - fn do_pending_task(&mut self, screen: &mut Screen, tl: &TaskLifetime) { + /// Stop as soon as the dam asks for interruption + fn do_pending_task(&mut self, screen: &mut Screen, dam: &mut Dam) { + debug!("entering do_pending_task"); if self.pending_pattern.is_some() { let pattern_str = self.pending_pattern.to_string(); let mut options = self.tree.options.clone(); @@ -410,7 +418,7 @@ impl AppState for BrowserState { Info, "tree filtering", pattern_str, - builder.build(tl, self.total_search_required), + builder.build(self.total_search_required, dam), ); // can be None if a cancellation was required self.total_search_required = false; if let Some(ref mut ft) = filtered_tree { @@ -418,8 +426,51 @@ impl AppState for BrowserState { ft.make_selection_visible(BrowserState::page_height(screen)); self.filtered_tree = filtered_tree; } + + } else if self.displayed_tree().is_missing_git_status_computation() { + let root_path = self.displayed_tree().root().to_path_buf(); + let git_status = dam.try_compute(|| + git_status_computer::compute_tree_status(root_path) + ); + debug!("computation result: {:?}", &git_status); + self.displayed_tree_mut().git_status = git_status; + debug!( + "self.displayed_tree().git_status = {:?}", + &self.displayed_tree().git_status, + ); + + debug!( + "AFTERCOMPUT self.displayed_tree().is_missing_git_status_computation() = {}", + self.displayed_tree().is_missing_git_status_computation(), + ); + + + //if self.options.show_git_file_info { + // let root_path = &self.blines[self.root_id].path; + // if let Ok(git_repo) = Repository::discover(root_path) { + // tree.git_status = time!( + // Debug, + // "TreeGitStatus::from", + // TreeGitStatus::from(&git_repo), + // ); + // let repo_root_path = git_repo.path().parent().unwrap(); + // for mut line in tree.lines.iter_mut() { + // if let Some(relative_path) = pathdiff::diff_paths(&line.path, &repo_root_path) { + // line.git_status = time!( + // Debug, + // "LineGitStatus", + // &relative_path, + // LineGitStatus::from(&git_repo, &relative_path), + // ); + // }; + // } + // } + //} + + + } else { - self.displayed_tree_mut().fetch_some_missing_dir_size(tl); + self.displayed_tree_mut().fetch_some_missing_dir_size(dam); } } diff --git a/src/browser_verbs.rs b/src/browser_verbs.rs index 6c9e6cb..998d7bc 100644 --- a/src/browser_verbs.rs +++ b/src/browser_verbs.rs @@ -9,7 +9,7 @@ use { flat_tree::Tree, help_states::HelpState, screens::Screen, - task_sync::TaskLifetime, + task_sync::Dam, tree_options::TreeOptions, verb_invocation::VerbInvocation, verbs::{Verb, VerbExecutor}, @@ -24,7 +24,7 @@ fn focus_path(path: PathBuf, screen: &mut Screen, tree: &Tree) -> AppStateCmdRes path, tree.options.clone(), screen, - &TaskLifetime::unlimited(), + &Dam::unlimited(), ), Command::from_pattern(&tree.options.pattern), ) @@ -95,7 +95,7 @@ impl VerbExecutor for BrowserState { path.to_path_buf(), self.displayed_tree().options.without_pattern(), screen, - &TaskLifetime::unlimited(), + &Dam::unlimited(), ), Command::new(), ), diff --git a/src/displayable_tree.rs b/src/displayable_tree.rs index 4a1f13b..7565879 100644 --- a/src/displayable_tree.rs +++ b/src/displayable_tree.rs @@ -3,6 +3,7 @@ use { errors::ProgramError, file_sizes::FileSize, flat_tree::{LineType, Tree, TreeLine}, + task_sync::ComputationResult, git_status_display::GitStatusDisplay, patterns::Pattern, skin::Skin, @@ -265,7 +266,7 @@ impl<'s, 't> DisplayableTree<'s, 't> { self.extend_line(f, selected)?; let title_len = title.chars().count(); if title_len < self.area.width as usize { - if let Some(git_status) = &self.tree.git_status { + if let ComputationResult::Done(git_status) = &self.tree.git_status { // git status is displayed if there's enough space for it let git_status_display = GitStatusDisplay::from( git_status, @@ -323,7 +324,7 @@ impl<'s, 't> DisplayableTree<'s, 't> { if line_index < tree.lines.len() { let line = &tree.lines[line_index]; selected = self.in_app && line_index == tree.selection; - if tree.git_status.is_some() { + if !tree.git_status.is_none() { self.write_line_git_status(f, line)?; } for depth in 0..line.depth { diff --git a/src/file_sizes/file_sizes_default.rs b/src/file_sizes/file_sizes_default.rs index ed5832d..af32a0d 100644 --- a/src/file_sizes/file_sizes_default.rs +++ b/src/file_sizes/file_sizes_default.rs @@ -1,7 +1,7 @@ //! size computation for non linux use { - crate::task_sync::TaskLifetime, + crate::task_sync::Dam, crossbeam::{channel::unbounded, sync::WaitGroup}, std::{ fs, @@ -16,7 +16,7 @@ use { // Note that this version doesn't try to compute the real size taken // on disk but report the value given by the `len` function -pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { +pub fn compute_dir_size(path: &Path, dam: &Dam) -> Option<u64> { let size = Arc::new(AtomicUsize::new(0)); // this MPMC channel contains the directory paths which must be handled diff --git a/src/file_sizes/file_sizes_unix.rs b/src/file_sizes/file_sizes_unix.rs index 8491699..186bf21 100644 --- a/src/file_sizes/file_sizes_unix.rs +++ b/src/file_sizes/file_sizes_unix.rs @@ -1,6 +1,10 @@ use { - crate::task_sync::TaskLifetime, - crossbeam::{channel::unbounded, sync::WaitGroup}, + crate::task_sync::Dam, + crossbeam::{ + channel::unbounded, + //crossbeam_utils::thread, + sync::WaitGroup, + }, std::{ collections::HashSet, fs, @@ -16,7 +20,7 @@ use { super::FileSize, }; -pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { +pub fn compute_dir_size(path: &Path, dam: &Dam) -> Option<u64> { debug!("compute size of dir {:?} --------------- ", path); let inodes = Arc::new(Mutex::new(HashSet::<u64>::default())); // to avoid counting twice an inode // the computation is done on blocks of 512 bytes @@ -39,9 +43,9 @@ pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { let busy = Arc::clone(&busy); let wg = wg.clone(); let (dirs_sender, dirs_receiver) = (dirs_sender.clone(), dirs_receiver.clone()); - let tl = tl.clone(); let inodes = inodes.clone(); - thread::spawn(move || { + let observer = dam.observer(); + thread::spawn(move|| { loop { let o = dirs_receiver.recv_timeout(period); if let Ok(Some(open_dir)) = o { @@ -67,7 +71,7 @@ pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { } else if busy.load(Ordering::Relaxed) < 1 { break; } - if tl.is_expired() { + if observer.has_event() { break; } } @@ -76,7 +80,7 @@ pub fn compute_dir_size(path: &Path, tl: &TaskLifetime) -> Option<u64> { } wg.wait(); - if tl.is_expired() { + if dam.has_event() { return None; } let blocks = blocks.load(Ordering::Relaxed); diff --git a/src/file_sizes/mod.rs b/src/file_sizes/mod.rs index d6efd74..2220445 100644 --- a/src/file_sizes/mod.rs +++ b/src/file_sizes/mod.rs @@ -5,7 +5,7 @@ /// twice an inode. /// use { - crate::task_sync::TaskLifetime, + crate::task_sync::Dam, std::{ collections::HashMap, fmt, @@ -47,12 +47,12 @@ impl FileSize { /// Return the size of the directory, either by computing it of by /// fetching it from cache. /// If the lifetime expires before complete computation, None is returned. - pub fn from_dir(path: &Path, tl: &TaskLifetime) -> Option<Self> { + pub fn from_dir(path: &Path, dam: &Dam) -> Option<Self> { let mut size_cache = SIZE_CACHE_MUTEX.lock().unwrap(); if let Some(s) = size_cache.get(path) { return Some(Self::new(*s, false)); } - if let Some(s) = time!(Debug, "size sum", path, compute_dir_size(path, tl)) { + if let Some(s) = time!(Debug, "size sum", path, compute_dir_size(path, dam)) { size_cache.insert(PathBuf::from(path), s); Some(FileSize::new(s, false)) } else { diff --git a/src/flat_tree.rs b/src/flat_tree.rs index 0ce5f0b..8d531cd 100644 --- a/src/flat_tree.rs +++ b/src/flat_tree.rs @@ -8,8 +8,11 @@ use { LineGitStatus, TreeGitStatus, }, + task_sync::{ + ComputationResult, + }, selection_type::SelectionType, - task_sync::TaskLifetime, + task_sync::Dam, tree_build::TreeBuilder, tree_options::TreeOptions, }, @@ -65,7 +68,7 @@ pub struct Tree { pub scroll: i32, // the number of lines at the top hidden because of scrolling pub nb_gitignored: u32, // number of times a gitignore pattern excluded a file pub total_search: bool, // whether the search was made on all children - pub git_status: Option<TreeGitStatus>, + pub git_status: ComputationResult<TreeGitStatus>, } impl TreeLine { @@ -191,11 +194,11 @@ impl Tree { self.root().to_path_buf(), self.options.clone(), page_height, - )?; + )?; let mut tree = builder.build( - &TaskLifetime::unlimited(), false, // on refresh we always do a non total search - ).unwrap(); // should not fail + &Dam::unlimited(), + ).unwrap(); // should not fail // we save the old selection to try restore it let selected_path = self.selected_line().path.to_path_buf(); mem::swap(&mut self.lines, &mut tree.lines); @@ -447,6 +450,11 @@ impl Tree { ) } + pub fn is_missing_git_status_computation(&self) -> bool { + self.options.show_git_file_info + && self.git_status.is_not_computed() + } + pub fn fetch_file_sizes(&mut self) { for i in 1..self.lines.len() { if self.lines[i].is_file() { @@ -460,10 +468,10 @@ impl Tree { /// /// To compute the size of all of them, this should be called until /// has_dir_missing_size returns false - pub fn fetch_some_missing_dir_size(&mut self, tl: &TaskLifetime) { + pub fn fetch_some_missing_dir_size(&mut self, dam: &Dam) { for i in 1..self.lines.len() { if self.lines[i].size.is_none() && self.lines[i].line_type == LineType::Dir { - self.lines[i].size = FileSize::from_dir(&self.lines[i].path, tl); + self.lines[i].size = FileSize::from_dir(&self.lines[i].path, dam); self.sort_siblings_by_size(); return; } diff --git a/src/git_status_computer.rs b/src/git_status_computer.rs new file mode 100644 index 0000000..4511f3e --- /dev/null +++ b/src/git_status_computer.rs @@ -0,0 +1,69 @@ + +use { + crate::{ + git_status::*, + task_sync::{ + Dam, + ComputationResult, + }, + }, + git2::{ + Repository, + }, + //rayon::{ + // ThreadPool, + // ThreadPoolBuilder, + //}, + crossbeam::{channel::bounded, sync::WaitGroup}, + std::{ + collections::HashSet, + fs, + os::unix::fs::MetadataExt, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicIsize, AtomicU64, Ordering}, + Arc, Mutex, + }, + thread, + time::Duration, + }, +}; + + +//struct Computer { +// +//} +//impl Computer { +// pub fn new() -> Self { +// +// } +//} + + +pub fn compute_tree_status(root_path: PathBuf) -> ComputationResult<TreeGitStatus> { + match Repository::discover(root_path) { + Ok(git_repo) => { + debug!("repo opened"); + for _ in 0..20 { + time!( + Debug, + "compute_tree_status", + TreeGitStatus::from(&git_repo), + ); + } + let tree_git_status = time!( + Debug, + "compute_tree_status", + TreeGitStatus::from(&git_repo), + ); + match tree_git_status { + Some(gs) => ComputationResult::Done(gs), + None => ComputationResult::None, + } + } + Err(e) => { + debug!("failed to discover repo: {:?}", e); + ComputationResult::None + } + } +} diff --git a/src/help_states.rs b/src/help_states.rs index 6e6011d..673662a 100644 --- a/src/help_states.rs +++ b/src/help_states.rs @@ -8,7 +8,7 @@ use { help_content, screens::Screen, status::Status, - task_sync::TaskLifetime, + task_sync::Dam, verb_store::PrefixSearchResult, verbs::VerbExecutor, }, @@ -88,7 +88,7 @@ impl AppState for HelpState { Command::new() } - fn do_pending_task(&mut self, _screen: &mut Screen, _tl: &TaskLifetime) { + fn do_pending_task(&mut self, _screen: &mut Screen, _dam: &mut Dam) { unreachable!(); } diff --git a/src/help_verbs.rs b/src/help_verbs.rs index 89ffcee..bccad02 100644 --- a/src/help_verbs.rs +++ b/src/help_verbs.rs @@ -10,7 +10,7 @@ use crate::{ external::{self, Launchable}, help_states::HelpState, screens::Screen, - task_sync::TaskLifetime, + task_sync::Dam, tree_options::TreeOptions, verb_invocation::VerbInvocation, verbs::{Verb, VerbExecutor}, @@ -34,7 +34,7 @@ impl VerbExecutor for HelpState { conf::dir(), TreeOptions::default(), screen, - &TaskLifetime::unlimited(), + &Dam::unlimited(), ), Command::new(), ), @@ -1,4 +1,6 @@ #[macro_use] +extern crate crossbeam; +#[macro_use] extern crate minimad; #[macro_use] extern crate lazy_static; @@ -28,6 +30,7 @@ pub mod flat_tree; pub mod fuzzy_patterns; pub mod git_ignore; pub mod git_status; +pub mod git_status_computer; pub mod git_status_display; pub mod help_content; pub mod help_states; diff --git a/src/task_sync.rs b/src/task_sync.rs index 926d9ed..db3474f 100644 --- a/src/task_sync.rs +++ b/src/task_sync.rs @@ -1,40 +1,162 @@ + use { + crossbeam::channel::{ + self, + bounded, + Receiver, + }, lazy_static::lazy_static, - std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, + std::{ + thread, }, + termimad::Event, }; -/// a TL initialized from an Arc<AtomicUsize> stays -/// alive as long as the passed arc doesn't change. -/// When it changes, is_expired returns true #[derive(Debug, Clone)] -pub struct TaskLifetime { - initial_value: usize, - external_value: Arc<AtomicUsize>, +pub enum ComputationResult<T> { + NotComputed, // not computed but will probably be + Done(T), + None, // nothing to compute, cancelled, failed, etc. +} +impl<T> ComputationResult<T> { + pub fn is_done(&self) -> bool { + match &self { + Self::Done(_) => true, + _ => false, + } + } + pub fn is_not_computed(&self) -> bool { + match &self { + Self::NotComputed => true, + _ => false, + } + } + pub fn is_none(&self) -> bool { + match &self { + Self::None => true, + _ => false, + } + } +} + +/// a dam is used in broot to manage long computations and, +/// when the user presses a key, either tell the computation +/// to stop (the computation function checking `has_event`) +/// or drop the computation. +pub struct Dam { + receiver: Receiver<Event>, + in_dam: Option<Event>, } -impl TaskLifetime { - pub fn new(external_value: Arc<AtomicUsize>) -> TaskLifetime { - TaskLifetime { - initial_value: external_value.load(Ordering::Relaxed), - external_value, +// cache problem: we must receive the result of dropped +// computattion to use it if we receive the same +// computation key later +impl Dam { + pub fn from(receiver: Receiver<Event>) -> Self { + Self { + receiver, + in_dam: None, } } - pub fn unlimited() -> TaskLifetime { - // Use a global static Arc<AtomicUsize> so that we don't have to - // allocate more than once - lazy_static! { - static ref ZERO: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(0)); + pub fn unlimited() -> Self { + Self::from(channel::never()) + } + + /// provide an observer which can be used for periodic + /// check a task can be used. + /// The observer can safely be moved to another thread + /// but Be careful not to use it + /// after the event listener started again. In any case + /// using try_compute should be prefered for immediate + /// return to the ui thread. + + pub fn observer(&self) -> DamObserver { + DamObserver::from(self) + } + + /// launch the computation on a new thread and return + /// when it finishes or when a new event appears on + /// the channel + pub fn try_compute<T: Send + 'static, F: Send + 'static + FnOnce() -> ComputationResult<T>>( + &mut self, + f: F, + ) -> ComputationResult<T> { + let (comp_sender, comp_receiver) = bounded(1); + thread::spawn(move|| { + let comp_res = time!(Debug, "comp in dam", f()); + if comp_sender.send(comp_res).is_err() { + debug!("no channel at end of computation"); + } + }); + self.select(comp_receiver) + } + + fn select<T>( + &mut self, + computation_channel: Receiver<ComputationResult<T>>, + ) -> ComputationResult<T> { + if self.in_dam.is_some() { + // should probably not happen + debug!("There's already an event in dam"); + ComputationResult::None + } else { + // + debug!("start select! in dam"); + select! { + recv(self.receiver) -> event => { + // interruption + debug!("dam interrupts computation"); + self.in_dam = event.ok(); + ComputationResult::None + } + recv(computation_channel) -> comp_res => { + // computation finished + debug!("computation passes dam"); + comp_res.unwrap_or(ComputationResult::None) + } + } } + } + + /// non blocking + pub fn has_event(&self) -> bool { + !self.receiver.is_empty() + } - TaskLifetime { - initial_value: 0, - external_value: ZERO.clone(), + /// block until next event (including the one which + /// may have been pushed back into the dam). + /// no event means the source is dead (i.e. we + /// must quit broot) + /// There's no event kept in dam after this call. + pub fn next_event(&mut self) -> Option<Event> { + if self.in_dam.is_some() { + self.in_dam.take() + } else { + match self.receiver.recv() { + Ok(event) => Some(event), + Err(_) => { + debug!("dead dam"); // should be logged once + None + } + } } } - pub fn is_expired(&self) -> bool { - self.initial_value != self.external_value.load(Ordering::Relaxed) +} + +pub struct DamObserver { + receiver: Receiver<Event>, +} +impl DamObserver { + pub fn from(dam: &Dam) -> Self { + Self { + receiver: dam.receiver.clone() + } + } + /// be careful that this can be used as a thread + /// stop condition only before the event receiver + /// start being active to avoid a race condition. + pub fn has_event(&self) -> bool { + !self.receiver.is_empty() } } + diff --git a/src/tree_build/builder.rs b/src/tree_build/builder.rs index 8ab1263..c7b1951 100644 --- a/src/tree_build/builder.rs +++ b/src/tree_build/builder.rs @@ -4,13 +4,15 @@ use { flat_tree::{Tree, TreeLine}, git_status::{ LineGitStatus, - TreeGitStatus, + }, + task_sync::{ + ComputationResult, }, git_ignore::{ GitIgnorer, GitIgnoreChain, }, - task_sync::TaskLifetime, + task_sync::Dam, tree_options::{ TreeOptions, }, @@ -221,8 +223,8 @@ impl TreeBuilder { /// If there's a pattern, we try to gather more lines that will be sorted afterwards. fn gather_lines( &mut self, - task_lifetime: &TaskLifetime, total_sea |