From 8f479020c5debd26f97dea26ecaedd38be798a96 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Wed, 18 Jan 2023 17:42:11 +0000 Subject: [PATCH] Add interleaving support to tree model. This commit adds support for interleaved regions and nodes to the tree model, but does not use it yet. This commit can be tested to verify that the changes do not impact the non-interleaved case. --- src/capture.rs | 16 +- src/tree_list_model.rs | 1462 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 1380 insertions(+), 98 deletions(-) diff --git a/src/capture.rs b/src/capture.rs index 90168f22..1db1a286 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1439,15 +1439,25 @@ impl EndpointReader { #[derive(Copy, Clone)] pub enum CompletionStatus { Complete, - Ongoing + Ongoing, + InterleavedComplete(u64), + InterleavedOngoing, } impl CompletionStatus { pub fn is_complete(&self) -> bool { use CompletionStatus::*; match self { - Complete => true, - Ongoing => false, + Complete | InterleavedComplete(_) => true, + Ongoing | InterleavedOngoing => false, + } + } + + pub fn is_interleaved(&self) -> bool { + use CompletionStatus::*; + match self { + InterleavedComplete(_) | InterleavedOngoing => true, + Complete | Ongoing => false, } } } diff --git a/src/tree_list_model.rs b/src/tree_list_model.rs index 63b8d79f..123bebcb 100644 --- a/src/tree_list_model.rs +++ b/src/tree_list_model.rs @@ -5,6 +5,7 @@ use std::collections::btree_map::Entry; use std::fmt::Debug; use std::marker::PhantomData; use std::rc::{Rc, Weak}; +use std::ops::Range; use anyhow::{Context, Error, bail}; @@ -15,7 +16,12 @@ use gtk::gio::prelude::ListModelExt; use derive_more::AddAssign; use itertools::Itertools; -use crate::capture::{CaptureReader, ItemSource}; +use crate::capture::{ + CaptureReader, + CompletionStatus, + ItemSource, + SearchResult +}; use crate::model::GenericModel; use crate::row_data::GenericRowData; use crate::item_widget::ItemWidget; @@ -41,8 +47,8 @@ trait Node { /// Whether the children of this node are displayed. fn expanded(&self) -> bool; - /// Mark this node as completed. - fn set_completed(&mut self); + /// Update this node's completion status. + fn set_completion(&mut self, completion: CompletionStatus); } struct Children { @@ -88,6 +94,9 @@ pub struct ItemNode { /// Index of this node below the parent Item. item_index: u64, + /// Completion status of this node. + completion: CompletionStatus, + /// Children of this item. children: Children, @@ -101,6 +110,11 @@ impl Children { self.expanded.contains_key(&index) } + /// Get the expanded child with the given index. + fn get_expanded(&self, index: u64) -> Option> { + self.expanded.get(&index).map(Rc::clone) + } + /// Set whether this child of the owning node is expanded. fn set_expanded(&mut self, child_rc: &ItemNodeRc, expanded: bool) { let child = child_rc.borrow(); @@ -152,8 +166,8 @@ impl Node for RootNode { true } - fn set_completed(&mut self) { - self.complete = true; + fn set_completion(&mut self, completion: CompletionStatus) { + self.complete = completion.is_complete(); } } @@ -188,14 +202,17 @@ impl Node for ItemNode where Item: Clone { } } - fn set_completed(&mut self) { - if let Some(parent_rc) = self.parent.upgrade() { - parent_rc - .borrow_mut() - .children_mut() - .incomplete - .remove(&self.item_index); + fn set_completion(&mut self, completion: CompletionStatus) { + if completion.is_complete() { + if let Some(parent_rc) = self.parent.upgrade() { + parent_rc + .borrow_mut() + .children_mut() + .incomplete + .remove(&self.item_index); + } } + self.completion = completion; } } @@ -274,10 +291,87 @@ impl ItemNode where Item: Clone { } } +#[derive(Clone)] +struct ItemNodeSet { + items: BTreeMap>, +} + +impl ItemNodeSet where Item: Clone { + fn new(node_rc: &ItemNodeRc) -> Self { + let mut new = Self { + items: BTreeMap::new() + }; + new.add(node_rc); + new + } + + fn add(&mut self, node_rc: &ItemNodeRc) { + let index = node_rc.borrow().item_index; + self.items.insert(index, node_rc.clone()); + } + + fn remove(&mut self, node_rc: &ItemNodeRc) { + let index = node_rc.borrow().item_index; + self.items.remove(&index); + } + + fn with(&self, node_rc: &ItemNodeRc) -> Self { + let mut new = self.clone(); + new.add(node_rc); + new + } + + fn without(&self, node_rc: &ItemNodeRc) -> Self { + let mut new = self.clone(); + new.remove(node_rc); + new + } + + fn any_incomplete(&self) -> bool { + self.items + .values() + .any(|node_rc| !node_rc.borrow().completion.is_complete()) + } + + fn is_empty(&self) -> bool { + self.items.is_empty() + } + + fn iter_items(&self) -> impl Iterator + '_ { + self.items + .iter() + .map(|(index, node_rc)| (*index, node_rc.borrow().item.clone())) + } +} + +impl PartialEq for ItemNodeSet { + fn eq(&self, other: &Self) -> bool { + self.items.len() == other.items.len() && + self.items.values() + .zip(other.items.values()) + .all(|(a, b)| Rc::ptr_eq(a, b)) + } +} + +impl Debug for ItemNodeSet +where Item: Clone + Debug +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) + -> Result<(), std::fmt::Error> + { + write!(f, "{:?}", + self.items + .values() + .map(|rc| rc.borrow().item.clone()) + .collect::>()) + } +} + #[derive(Clone)] enum Source { TopLevelItems(), ChildrenOf(ItemNodeRc), + InterleavedSearch(ItemNodeSet, Range), } use Source::*; @@ -301,6 +395,8 @@ where Item: Clone + Debug write!(f, "Top level items"), ChildrenOf(rc) => write!(f, "Children of {:?}", rc.borrow().item), + InterleavedSearch(expanded, range) => + write!(f, "Interleaved search in {range:?} from {expanded:?}"), }?; write!(f, ", offset {}, length {}", self.offset, self.length) } @@ -312,6 +408,16 @@ impl Region where Item: Clone { region_b: &Region ) -> Option> { match (®ion_a.source, ®ion_b.source) { + (InterleavedSearch(exp_a, range_a), + InterleavedSearch(exp_b, range_b)) if exp_a == exp_b => Some( + Region { + source: InterleavedSearch( + exp_a.clone(), + range_a.start..range_b.end), + offset: region_a.offset, + length: region_a.length + region_b.length, + } + ), (ChildrenOf(a_ref), ChildrenOf(b_ref)) if Rc::ptr_eq(a_ref, b_ref) => Some( Region { @@ -346,6 +452,17 @@ impl std::fmt::Display for ModelUpdate { } } +enum InterleavedUpdate { + AddedOngoing(ItemNodeRc, u64), + AddedComplete(ItemNodeRc, u64, u64), +} + +enum UpdateState { + Root(Vec>), + Interleaved(Option>), + Contiguous(), +} + pub struct TreeListModel { _marker: PhantomData<(Model, RowData)>, capture: RefCell, @@ -389,6 +506,10 @@ where Item: 'static + Clone + Debug, self.root.borrow().children().total_count } + fn item_count(&self) -> u64 { + self.root.borrow().children().direct_count + } + fn check(&self) -> Result<(), Error> { // Check that we have the expected number of rows in the region map. let expected_count = self.row_count(); @@ -399,8 +520,8 @@ where Item: 'static + Clone + Debug, .map(|(start, region)| start + region.length) .unwrap_or(0); if expected_count != actual_count { - bail!("Region map total row count is {}, expected {}", - actual_count, expected_count) + bail!("Region map total row count is {actual_count}, \ + expected {expected_count}") } else { Ok(()) } @@ -425,6 +546,7 @@ where Item: 'static + Clone + Debug, let rows_affected = node.children.direct_count; let expanded_children = node.children.expanded.clone(); + let interleaved = node.completion.is_interleaved(); // There cannot be any visible incomplete children at this point. node.children.incomplete.clear(); @@ -442,13 +564,39 @@ where Item: 'static + Clone + Debug, } else { #[cfg(feature="debug-region-map")] println!("\nCollapsing node at {}", position); - // If collapsing, first recursively collapse children of this node. - for (index, child_ref) in expanded_children { - let child_position = children_position + index; - #[cfg(feature="debug-region-map")] - println!("\nRecursively collapsing child at {}", - child_position); - self.set_expanded(model, &child_ref, child_position, false)?; + if interleaved { + // Collapse expanded children of this node, from last to first. + let mut end_position = self.row_count(); + for child_ref in expanded_children.into_values().rev() { + // Since this node's children are interleaved, we find each + // expanded child's position by searching the region map + // in reverse order, for the first region containing the + // expanded child's own children. The search window extends + // from the parent node to either the end of the region map, + // or the previous collapsed child, shrinking each time. + let child_position = + self.find_expanded(position..end_position, &child_ref)?; + // Update end position for next search. + end_position = child_position; + // Collapse this child. + #[cfg(feature="debug-region-map")] + println!("\nRecursively collapsing child at {}", + child_position); + self.set_expanded( + model, &child_ref, child_position, false)?; + } + } else { + // If this node's children are not interleaved, each child's + // position is at a simple offset, provided we collapse them + // from first to last. + for (index, child_ref) in expanded_children { + let child_position = children_position + index; + #[cfg(feature="debug-region-map")] + println!("\nRecursively collapsing child at {}", + child_position); + self.set_expanded( + model, &child_ref, child_position, false)?; + } } // Update the region map for the removed children. self.collapse(children_position, node_ref)? @@ -484,9 +632,67 @@ where Item: 'static + Clone + Debug, Ok(()) } + fn find_expanded(&self, range: Range, node_rc: &ItemNodeRc) + -> Result + { + self.regions + .borrow() + .range(range) + .rev() + .find_map(|(start, region)| { + match (region.offset, ®ion.source) { + (0, ChildrenOf(rc)) + if Rc::ptr_eq(rc, node_rc) + // The node appears on the row before + // its first children. + => Some(*start - 1), + _ => None + } + }) + .context("No region found for expanded child node") + } + + fn find_top_level_item(&self, node_rc: &ItemNodeRc) + -> Result + { + let index = node_rc.borrow().item_index; + for (start, region) in self.regions.borrow().iter() { + match ®ion.source { + TopLevelItems() if + region.offset <= index && + region.length > index - region.offset => + { + return Ok(start + index - region.offset); + }, + InterleavedSearch(expanded, range) if + range.start < index && + range.end > index => + { + let count_range = range.start..index; + let position = + self.count_items(&count_range)? + + self.count_within(expanded, &count_range)?; + if position >= region.offset && + position < region.offset + region.length + { + return Ok(start + position - region.offset) + } + } + _ => {} + } + } + bail!("Item not found") + } + fn expand(&self, position: u64, node_ref: &ItemNodeRc) -> Result { + // Extract some details of the node being expanded. + let node = node_ref.borrow(); + let node_start = node.item_index; + let next_item = node_start + 1; + let interleaved = node.completion.is_interleaved(); + // Find the start of the parent region. let (&parent_start, _) = self.regions .borrow() @@ -506,29 +712,179 @@ where Item: 'static + Clone + Debug, "Parent not found at position {parent_start}"))?; // Remove all following regions, to iterate over later. - let following_regions = self.regions + let mut following_regions = self.regions .borrow_mut() - .split_off(&parent_start) - .into_iter(); + .split_off(&parent_start); + + let more_after = !following_regions.is_empty(); // Split the parent region and construct a new region between. - let update = self.split_parent(parent_start, &parent, node_ref, - vec![Region { - source: parent.source.clone(), - offset: parent.offset, - length: relative_position, - }], - Region { - source: ChildrenOf(node_ref.clone()), - offset: 0, - length: node_ref.borrow().children.direct_count, + // + // Where the new region is an interleaved one, its overlap with the + // remainder of the parent is handled in the split_parent call. + let mut update = match (interleaved, &parent.source) { + // Self-contained region expanded. + (false, _) => { + self.split_parent(parent_start, &parent, node_ref, more_after, + vec![Region { + source: parent.source.clone(), + offset: parent.offset, + length: relative_position, + }], + Region { + source: ChildrenOf(node_ref.clone()), + offset: 0, + length: node.children.direct_count, + }, + vec![Region { + source: parent.source.clone(), + offset: parent.offset + relative_position, + length: parent.length - relative_position, + }] + )? }, - vec![Region { - source: parent.source.clone(), - offset: parent.offset + relative_position, - length: parent.length - relative_position, - }] - )?; + // Interleaved region expanded from within a root region. + (true, TopLevelItems()) => { + let expanded = ItemNodeSet::new(node_ref); + let range = node_start..next_item; + let added = self.count_within(&expanded, &range)?; + if parent.length == 1 && + parent_start + parent.length != self.row_count() + { + // There's nothing to split, and there must already be an + // interleaved region after this, so keep the parent as is. + let mut update = ModelUpdate::default(); + self.preserve_region( + &mut update, parent_start, &parent, false)?; + update + } else { + self.split_parent( + parent_start, &parent, node_ref, more_after, + vec![Region { + source: TopLevelItems(), + offset: parent.offset, + length: relative_position, + }], + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: added, + }, + vec![Region { + source: TopLevelItems(), + offset: parent.offset + relative_position, + length: parent.length - relative_position, + }] + )? + } + }, + // New interleaved region expanded from within an existing one. + (true, InterleavedSearch(parent_expanded, parent_range)) => { + let range_1 = parent_range.start..node_start; + let range_2 = node_start..next_item; + let range_3 = next_item..parent_range.end; + let expanded_1 = parent_expanded.clone(); + let expanded_2 = parent_expanded.with(node_ref); + let expanded_3 = parent_expanded.clone(); + let total_changed = self.count_within(parent_expanded, &range_2)?; + let rows_present = parent.length - relative_position; + let (changed, added) = if rows_present >= total_changed { + let changed = total_changed; + let added = self.count_in_range(&range_2, node_ref)?; + (changed, added) + } else { + let changed = rows_present; + let added = self.count_to_item( + parent_expanded, &range_2, rows_present - 1, node_ref)?; + (changed, added) + }; + if parent.offset != 0 { + // Update the range end of previous parts of this parent. + for region in self.regions.borrow_mut().values_mut().rev() { + match &mut region.source { + TopLevelItems() => break, + ChildrenOf(..) => continue, + InterleavedSearch(_, range) + if range.end > node_start => + { + range.end = node_start; + }, + InterleavedSearch(..) => break + } + } + } + if total_changed > rows_present { + // Update the range start of following parts of this parent. + for region in following_regions.values_mut() { + match &mut region.source { + TopLevelItems() => break, + ChildrenOf(..) => continue, + InterleavedSearch(_, range) + if range.start < node_start => + { + range.start = node_start; + region.offset -= relative_position; + }, + InterleavedSearch(..) => break + } + } + } + self.split_parent(parent_start, &parent, node_ref, more_after, + vec![ + Region { + source: InterleavedSearch(expanded_1, range_1), + offset: parent.offset, + length: relative_position - 1, + }, + Region { + source: TopLevelItems(), + offset: node_start, + length: 1, + } + ], + Region { + source: InterleavedSearch(expanded_2, range_2), + offset: 0, + length: changed + added, + }, + if rows_present > changed { + vec![ + Region { + source: TopLevelItems(), + offset: next_item, + length: 1, + }, + Region { + source: InterleavedSearch(expanded_3, range_3), + offset: 0, + length: rows_present - changed - 1, + } + ] + } else { + vec![] + } + )? + }, + // Other combinations are not supported. + (..) => bail!("Unable to expand from {parent:?}") + }; + + drop(node); + + // For an interleaved source, update all regions that it overlaps. + let mut following_regions = following_regions.into_iter(); + if interleaved { + while let Some((start, region)) = following_regions.next() { + let more_after = following_regions.len() > 0; + // Do whatever is necessary to overlap this region. + if !self.overlap_region( + &mut update, start, ®ion, node_ref, more_after)? + { + // No further overlapping regions. + break; + } + } + } // Shift all remaining regions down by the added rows. for (start, region) in following_regions { @@ -571,6 +927,20 @@ where Item: 'static + Clone + Debug, rows_removed: node_ref.borrow().children.direct_count, rows_changed: 0, } + }, + // For an interleaved source, update all overlapped regions. + InterleavedSearch(..) => { + let mut update = ModelUpdate::default(); + for (start, region) in following_regions.by_ref() { + // Do whatever is necessary to unoverlap this region. + if !self.unoverlap_region( + &mut update, start, ®ion, node_ref)? + { + // No further overlapping regions. + break; + } + } + update } }; @@ -582,6 +952,295 @@ where Item: 'static + Clone + Debug, Ok(update) } + fn overlap_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + node_ref: &ItemNodeRc, + more_after: bool) + -> Result + { + use Source::*; + + let node_range = self.node_range(node_ref); + + Ok(match ®ion.source { + TopLevelItems() if region.offset >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + InterleavedSearch(_, range) if range.start >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + ChildrenOf(_) => { + // This region is overlapped but self-contained. + self.preserve_region(update, start, region, true)? + }, + TopLevelItems() if region.length == 1 => { + // This region includes only a single root item. Check + // whether it is overlapped by the new node. + let overlapped = region.offset < node_range.end; + // Either way, the region itself is unchanged. + self.preserve_region(update, start, region, overlapped)?; + // We may need to add a new interleaved region after this item + // if it is overlapped, is the last item, and there is not + // already an interleaved region to follow it. + let item_count = self.item_count(); + let last_item = region.offset + 1 == item_count; + if overlapped && last_item && !more_after { + let range = region.offset..item_count; + let added = self.count_in_range(&range, node_ref)?; + let expanded = ItemNodeSet::new(node_ref); + let trailing_region = Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: added, + }; + #[cfg(feature="debug-region-map")] { + println!(); + println!("Inserting: {:?}", trailing_region); + } + self.insert_region( + start + region.length + update.rows_added, + trailing_region + )?; + update.rows_added += added; + } + overlapped + }, + TopLevelItems() + if region.offset + region.length <= node_range.end => + { + // This region is fully overlapped by the new node. + // Replace with a new interleaved region. + let end = region.offset + region.length; + let contains_last_item = end == self.item_count(); + let expanded = ItemNodeSet::new(node_ref); + if contains_last_item && !more_after { + // This region contains the last item, and there is not + // currently a following interleaved region. Create one + // that runs to the end of the model. + let range = region.offset..end; + let added = self.count_in_range(&range, node_ref)?; + self.replace_region(update, start, region, + vec![ + Region { + source: TopLevelItems(), + offset: region.offset, + length: 1, + }, + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: region.length - 1 + added, + } + ], + )?; + false + } else { + // There are further regions after this one, so just + // overlap up to the end of these top level items. + let range = region.offset..(end - 1); + let added = self.count_in_range(&range, node_ref)?; + self.replace_region(update, start, region, + vec![ + Region { + source: TopLevelItems(), + offset: region.offset, + length: 1, + }, + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: region.length - 2 + added, + }, + Region { + source: TopLevelItems(), + offset: region.offset + region.length - 1, + length: 1 + } + ] + )?; + true + } + }, + TopLevelItems() => { + // This region is partially overlapped by the new node. + // Split it into overlapped and unoverlapped parts. + let expanded = ItemNodeSet::new(node_ref); + let range = region.offset..node_range.end; + let changed = self.count_items(&range)?; + let added = self.count_in_range(&range, node_ref)?; + self.partial_overlap(update, start, region, + vec![ + Region { + source: TopLevelItems(), + offset: region.offset, + length: 1, + }, + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: changed + added + } + ], + vec![ + Region { + source: TopLevelItems(), + offset: region.offset + changed + 1, + length: region.length - changed - 1, + } + ] + )?; + // No longer overlapping. + false + }, + InterleavedSearch(expanded, range) + if range.end <= node_range.end => + { + // This region is fully overlapped by the new node. + // Replace with a new interleaved region. + let (added_before_offset, added_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, + region.offset + region.length)?; + let range = range.clone(); + let more_expanded = expanded.with(node_ref); + self.replace_region(update, start, region, + vec![ + Region { + source: InterleavedSearch(more_expanded, range), + offset: region.offset + added_before_offset, + length: region.length + added_after_offset, + } + ] + )?; + true + }, + InterleavedSearch(expanded, range) => { + // This region may be partially overlapped by the new node, + // depending on its starting offset and length. + let range_1 = range.start..node_range.end; + let range_2 = node_range.end..range.end; + // Work out the offset at which this source would be split. + let split_offset = self.count_items(&range_1)? + + self.count_within(expanded, &range_1)?; + if region.offset >= split_offset { + // This region begins after the split, so isn't overlapped. + self.preserve_region(update, start, region, false)? + } else if region.offset + region.length <= split_offset { + // This region ends before the split, so is fully overlapped. + let (added_before_offset, added_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, + region.offset + region.length)?; + let range = range.clone(); + let more_expanded = expanded.with(node_ref); + self.replace_region(update, start, region, + vec![ + Region { + source: InterleavedSearch(more_expanded, range), + offset: region.offset + added_before_offset, + length: region.length + added_after_offset, + } + ] + )?; + true + } else { + // This region straddles the split. Break it into overlapped + // and unoverlapped parts. + let changed = split_offset - region.offset; + let (added_before_offset, added_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, split_offset)?; + let expanded_1 = expanded.with(node_ref); + let expanded_2 = expanded.clone(); + self.partial_overlap(update, start, region, + vec![ + Region { + source: InterleavedSearch(expanded_1, range_1), + offset: region.offset + added_before_offset, + length: changed + added_after_offset, + } + ], + vec![ + Region { + source: TopLevelItems(), + offset: node_range.end, + length: 1, + }, + Region { + source: InterleavedSearch(expanded_2, range_2), + offset: 0, + length: region.length - changed - 1, + } + ] + )?; + // No longer overlapping. + false + } + } + }) + } + + fn unoverlap_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + node_ref: &ItemNodeRc) + -> Result + { + use Source::*; + + let node_range = self.node_range(node_ref); + + Ok(match ®ion.source { + TopLevelItems() if region.offset >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + InterleavedSearch(_, range) if range.start >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + ChildrenOf(_) | TopLevelItems() => { + // This region is overlapped but self-contained. + self.preserve_region(update, start, region, true)? + }, + InterleavedSearch(expanded, range) => { + // This region is overlapped. Replace with a new one. + let less_expanded = expanded.without(node_ref); + let new_region = if less_expanded.is_empty() { + // This node was the last expanded one in this region. + Region { + source: TopLevelItems(), + offset: range.start + 1, + length: self.count_items(range)?, + } + } else { + // There are other nodes expanded in this region. + let (removed_before_offset, removed_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, + region.offset + region.length)?; + let range = range.clone(); + Region { + source: InterleavedSearch(less_expanded, range), + offset: region.offset - removed_before_offset, + length: region.length - removed_after_offset, + } + }; + self.replace_region(update, start, region, vec![new_region])?; + true + } + }) + } + fn insert_region(&self, position: u64, region: Region) -> Result<(), Error> { @@ -602,13 +1261,158 @@ where Item: 'static + Clone + Debug, } } + fn preserve_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + include_as_changed: bool) + -> Result + { + let new_position = start + + update.rows_added + - update.rows_removed; + + self.insert_region(new_position, region.clone())?; + + if include_as_changed { + update.rows_changed += region.length; + } + + Ok(include_as_changed) + } + + fn replace_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + new_regions: Vec>) + -> Result<(), Error> + { + use std::cmp::Ordering::*; + + let new_length: u64 = new_regions + .iter() + .map(|region| region.length) + .sum(); + + let effect = match new_length.cmp(®ion.length) { + Greater => ModelUpdate { + rows_added: new_length - region.length, + rows_removed: 0, + rows_changed: region.length + }, + Less => ModelUpdate { + rows_added: 0, + rows_removed: region.length - new_length, + rows_changed: new_length + }, + Equal => ModelUpdate { + rows_added: 0, + rows_removed: 0, + rows_changed: region.length + }, + }; + + #[cfg(feature="debug-region-map")] + { + println!(); + println!("Replacing: {:?}", region); + for (i, new_region) in new_regions.iter().enumerate() { + if i == 0 { + println!(" with: {:?}", new_region); + } else { + println!(" {:?}", new_region); + } + } + println!(" {}", effect); + } + + let mut position = start + + update.rows_added + - update.rows_removed; + + for region in new_regions { + let length = region.length; + self.insert_region(position, region)?; + position += length; + } + + *update += effect; + + Ok(()) + } + + fn partial_overlap(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + changed_regions: Vec>, + unchanged_regions: Vec>) + -> Result<(), Error> + { + let changed_length: u64 = changed_regions + .iter() + .map(|region| region.length) + .sum(); + + let unchanged_length: u64 = unchanged_regions + .iter() + .map(|region| region.length) + .sum(); + + let total_length = changed_length + unchanged_length; + + let effect = ModelUpdate { + rows_added: total_length - region.length, + rows_removed: 0, + rows_changed: region.length - unchanged_length, + }; + + #[cfg(feature="debug-region-map")] + { + println!(); + println!("Splitting: {:?}", region); + for (i, changed_region) in changed_regions.iter().enumerate() { + if i == 0 { + println!(" changed: {:?}", changed_region); + } else { + println!(" : {:?}", changed_region); + } + } + for (i, unchanged_region) in unchanged_regions.iter().enumerate() { + if i == 0 { + println!("unchanged: {:?}", unchanged_region); + } else { + println!(" : {:?}", unchanged_region); + } + } + println!(" {}", effect); + } + + let mut position = start + update.rows_added - update.rows_removed; + for region in changed_regions + .into_iter() + .chain(unchanged_regions) + { + let length = region.length; + self.insert_region(position, region)?; + position += length; + } + + *update += effect; + + Ok(()) + } + + #[allow(clippy::too_many_arguments)] fn split_parent(&self, parent_start: u64, parent: &Region, - _node_ref: &ItemNodeRc, + node_ref: &ItemNodeRc, + more_after: bool, parts_before: Vec>, new_region: Region, - parts_after: Vec>) + mut parts_after: Vec>) -> Result { let length_before: u64 = parts_before @@ -626,7 +1430,7 @@ where Item: 'static + Clone + Debug, let rows_added = total_length - parent.length; let rows_changed = parent.length - length_before - length_after; - let update = ModelUpdate { + let mut update = ModelUpdate { rows_added, rows_removed: 0, rows_changed, @@ -657,6 +1461,7 @@ where Item: 'static + Clone + Debug, println!(" {}", &update); } + let interleaved = matches!(&new_region.source, InterleavedSearch(..)); let new_position = parent_start + length_before; let position_after = new_position + new_region.length; @@ -670,19 +1475,32 @@ where Item: 'static + Clone + Debug, self.insert_region(new_position, new_region)?; position = position_after; - for region in parts_after - .into_iter() - .filter(|region| region.length > 0) - { + + parts_after.retain(|region| region.length > 0); + let mut add_after = parts_after.into_iter(); + let mut overlap = interleaved; + while let Some(region) = add_after.next() { let length = region.length; - self.insert_region(position, region)?; + if overlap { + let more_after = more_after || add_after.len() > 0; + overlap = self.overlap_region(&mut update, + position - rows_added, + ®ion, + node_ref, + more_after)?; + } else { + self.insert_region(position, region)?; + } position += length; } Ok(update) } - fn merge_regions(&self) { + fn pre_merge(&self, printed: &mut bool) { + if *printed { + return + } #[cfg(feature="debug-region-map")] { println!(); println!("Before merge:"); @@ -690,14 +1508,18 @@ where Item: 'static + Clone + Debug, println!("{}: {:?}", start, region); } } + *printed = true; + } + fn merge_pairs(&self, printed: &mut bool) { let new_regions = self.regions - .borrow_mut() - .split_off(&0) + .borrow() + .clone() .into_iter() .coalesce(|(start_a, region_a), (start_b, region_b)| match Region::merge(®ion_a, ®ion_b) { Some(region_c) => { + self.pre_merge(printed); #[cfg(feature="debug-region-map")] { println!(); println!("Merging: {:?}", region_a); @@ -713,6 +1535,145 @@ where Item: 'static + Clone + Debug, self.regions.replace(new_regions); } + fn merge_regions(&self) { + let mut printed = false; + + // Merge adjacent regions with the same source. + self.merge_pairs(&mut printed); + + // Find starts and lengths of superfluous root regions. + let superfluous_regions: Vec<(u64, u64)> = self.regions + .borrow() + .iter() + .tuple_windows() + .filter_map(|((_, a), (b_start, b), (_, c))| + match (&a.source, &b.source, &c.source) { + (InterleavedSearch(exp_a, _), + TopLevelItems(), + InterleavedSearch(exp_c, _)) if exp_a == exp_c => { + self.pre_merge(&mut printed); + #[cfg(feature="debug-region-map")] + { + println!(); + println!("Dropping: {:?}", b); + } + Some((*b_start, b.length)) + }, + _ => None + }) + .collect(); + + // Remove superfluous regions. + for (start, length) in superfluous_regions { + let mut regions = self.regions.borrow_mut(); + regions.remove(&start); + let (_, prev_region) = regions + .range_mut(..start) + .next_back() + .unwrap(); + prev_region.length += length; + } + + // Once again merge adjacent regions with the same source. + self.merge_pairs(&mut printed); + } + + fn node_range(&self, node_ref: &ItemNodeRc) -> Range { + use CompletionStatus::*; + let node = node_ref.borrow(); + let start = node.item_index; + let end = match node.completion { + InterleavedComplete(index) => index, + InterleavedOngoing => self.item_count(), + _ => start, + }; + start..end + } + + fn count_items(&self, range: &Range) -> Result { + let length = range.end - range.start; + if length == 0 { + bail!("Range is empty") + } else { + Ok(length - 1) + } + } + + fn count_to_item(&self, + expanded: &ItemNodeSet, + range: &Range, + to_index: u64, + node_ref: &ItemNodeRc) + -> Result + { + use SearchResult::*; + let node = node_ref.borrow(); + let item_index = node.item_index; + let item = &node.item; + let mut expanded = expanded.iter_items(); + let mut cap = self.capture.borrow_mut(); + let search_result = cap.find_child(&mut expanded, range, to_index)?; + Ok(match search_result { + TopLevelItem(found_index, _) => { + let range_to_item = range.start..found_index; + cap.count_within(item_index, item, &range_to_item)? + }, + NextLevelItem(span_index, .., child) => { + let range_to_span = range.start..span_index; + cap.count_within(item_index, item, &range_to_span)? + + cap.count_before(item_index, item, span_index, &child)? + }, + }) + } + + fn count_in_range(&self, + range: &Range, + node_ref: &ItemNodeRc) + -> Result + { + let mut cap = self.capture.borrow_mut(); + let node = node_ref.borrow(); + cap.count_within(node.item_index, &node.item, range) + } + + fn count_around_offset(&self, + expanded: &ItemNodeSet, + range: &Range, + node_ref: &ItemNodeRc, + offset: u64, + end: u64) + -> Result<(u64, u64), Error> + { + let length = + self.count_items(range)? + self.count_within(expanded, range)?; + let rows_before_offset = if offset == 0 { + 0 + } else { + self.count_to_item(expanded, range, offset - 1, node_ref)? + }; + let rows_before_end = if end == length { + self.count_in_range(range, node_ref)? + } else { + self.count_to_item(expanded, range, end, node_ref)? + }; + let rows_after_offset = rows_before_end - rows_before_offset; + Ok((rows_before_offset, rows_after_offset)) + } + + fn count_within(&self, + expanded: &ItemNodeSet, + range: &Range) + -> Result + { + let mut cap = self.capture.borrow_mut(); + Ok(expanded + .iter_items() + .map(|(index, item)| cap.count_within(index, &item, range)) + .collect::, Error>>()? + .iter() + .sum()) + } + pub fn update(&self, model: &Model) -> Result { #[cfg(feature="debug-region-map")] let rows_before = self.row_count(); @@ -738,10 +1699,13 @@ where Item: 'static + Clone + Debug, node_rc: &Rc>, mut position: u64, model: &Model) - -> Result + -> Result<(u64, Option>), Error> where T: Node + 'static, Rc>: NodeRcOps, { + use InterleavedUpdate::*; + use UpdateState::*; + // Extract details about the current node. let node = node_rc.borrow(); let expanded = node.expanded(); @@ -756,11 +1720,10 @@ where Item: 'static + Clone + Debug, let mut cap = self.capture.borrow_mut(); let (completion, new_direct_count) = cap.item_children(node.item(), self.view_mode)?; - let completed = completion.is_complete(); let children_added = new_direct_count - old_direct_count; drop(node); - if let Some(item_node_rc) = node_rc.item_node_rc() { + let mut state = if let Some(item_node_rc) = node_rc.item_node_rc() { // This is an item node. let mut item_node = item_node_rc.borrow_mut(); @@ -797,29 +1760,81 @@ where Item: 'static + Clone + Debug, // Advance past this node's own row. position += 1; - } + + // Construct node state for remaining steps. + use CompletionStatus::*; + let children_to_add = if expanded && children_added > 0 { + Some(children_added) + } else { + None + }; + match (item_node.completion, children_to_add) { + (InterleavedComplete(end), Some(count)) => { + drop(item_node); + Interleaved(Some(AddedComplete(item_node_rc, count, end))) + }, + (InterleavedOngoing, Some(count)) => { + drop(item_node); + Interleaved(Some(AddedOngoing(item_node_rc, count))) + }, + (InterleavedComplete(_) | InterleavedOngoing, None) => + Interleaved(None), + (..) => + Contiguous() + } + } else { + Root(Vec::new()) + }; drop(cap); - // If completed, remove from incomplete node list. - if completed { - node_rc.borrow_mut().set_completed(); - } + // Update node's completion status. + node_rc.borrow_mut().set_completion(completion); if expanded { // Deal with incomplete children of this node. let mut last_index = 0; for (index, child_weak) in incomplete_children { if let Some(child_rc) = child_weak.upgrade() { - // Advance position up to this child. - position += node_rc - .borrow() - .children() - .rows_between(last_index, index); - // Recursively update this child. - position = self.update_node::>( - &child_rc, position, model)?; - last_index = index + 1; + // Advance position to this child. + position = match state { + Root(..) => + // Find position of top level item from region map. + self.find_top_level_item(&child_rc)?, + Interleaved(..) => { + // There can be only one incomplete child of an + // item whose children are interleaved. If it is + // expanded, we can find it from the region map. + // Otherwise, it must always be at the last row. + let end = self.row_count(); + if child_rc.borrow().expanded() { + self.find_expanded(position..end, &child_rc)? + } else { + end - 1 + } + }, + Contiguous(..) => { + // If the children of this node are contiguous, we + // can advance to another child by summing the + // total rows of the intermediate children. + let delta = node_rc + .borrow() + .children() + .rows_between(last_index, index); + last_index = index + 1; + position + delta + }, + }; + // Recursively update the child. + let (new_position, update) = + self.update_node::>( + &child_rc, position, model)?; + // If the child has a pending interleaved update, store it. + if let (Root(todo), Some(update)) = (&mut state, update) { + todo.push(update); + } + // Advance position to after this child and its children. + position = new_position; } else { // Child no longer referenced, remove it. node_rc @@ -830,26 +1845,244 @@ where Item: 'static + Clone + Debug, } } - // Advance to the end of this node's existing children. - position += node_rc - .borrow_mut() - .children_mut() - .rows_between(last_index, old_direct_count); + if matches!(state, Contiguous()) { + // Advance to the end of this node's existing children. + position += node_rc + .borrow_mut() + .children_mut() + .rows_between(last_index, old_direct_count); + } } // Now deal with any new children of this node. - if children_added > 0 { - // Update this node's child counts. + if let Root(interleaved_updates) = &state { + // Updating the root node. New rows are added after all others. + let initial_position = self.row_count(); + position = initial_position; + + // Running totals of rows pending and added. + let mut top_level_added = 0; + let mut top_level_pending = children_added; + let mut second_level_added = 0; + let mut second_level_pending = 0; + + // Collect completed items, and count pending second level items. + let mut completed = BTreeMap::new(); + for update in interleaved_updates { + match update { + AddedComplete(child_rc, children_added, end) => { + completed.insert(*end, child_rc); + second_level_pending += children_added; + }, + AddedOngoing(_child_rc, children_added) => { + second_level_pending += children_added; + } + } + } + + // Handle completed items. + let mut completed = completed.into_iter(); + if let Some((child_end, child_rc)) = completed.next() { + + // Find the last interleaved region. + let (mut expanded, range, old_length) = self.regions + .borrow_mut() + .values_mut() + .rev() + .find_map(|region| + if let InterleavedSearch(expanded, range) = + &mut region.source + { + // Copy all the fields. + let fields = ( + expanded.clone(), + range.clone(), + region.offset + region.length, + ); + // Extend the search range to the new end. + range.end = child_end; + // Return the copied fields. + Some(fields) + } else { + None + } + ) + .context("No interleaved region found")?; + + // Add a new region with the additional rows. + let full_range = range.start..child_end; + let old_top = self.count_items(&range)?; + let full_top = self.count_items(&full_range)?; + let full_second = self.count_within(&expanded, &full_range)?; + let full_length = full_top + full_second; + let new_length = full_length - old_length; + let new_top = full_top - old_top; + let new_second = new_length - new_top; + let new_region = Region { + source: InterleavedSearch(expanded.clone(), full_range), + offset: old_length, + length: new_length, + }; + self.insert_region(position, new_region)?; + position += new_length; + top_level_added += new_top; + top_level_pending -= new_top; + second_level_added += new_second; + second_level_pending -= new_second; + + // Update for next child completion. + let mut last_end = child_end; + expanded = expanded.without(child_rc); + + // Now handle any further completed children. + for (child_end, child_rc) in completed { + // Add a region for the in-between top level item. + self.insert_region(position, Region { + source: TopLevelItems(), + offset: last_end, + length: 1, + })?; + position += 1; + top_level_added += 1; + top_level_pending -= 1; + + // Add a new interleaved region. + let range = last_end..child_end; + let new_top = self.count_items(&range)?; + let new_second = self.count_within(&expanded, &range)?; + let new_length = new_top + new_second; + self.insert_region(position, Region { + source: InterleavedSearch( + expanded.clone(), range.clone()), + offset: 0, + length: new_length, + })?; + position += new_length; + top_level_added += new_top; + top_level_pending -= new_top; + second_level_added += new_second; + second_level_pending -= new_second; + + // Update for next completion. + last_end = child_end; + expanded = expanded.without(child_rc); + } + } + + // Add any further pending items at the end. + if top_level_pending > 0 || second_level_pending > 0 { + // Find the source and offset needed to extend the last region + // that contains top level items, or default to a new top level + // source if the map is empty. + let (source, offset, old_end, old_length) = self.regions + .borrow_mut() + .values_mut() + .rev() + .find_map(|region| { + let old_length = region.offset + region.length; + Some(match &mut region.source { + TopLevelItems() if second_level_pending == 0 => { + let source = region.source.clone(); + let old_end = region.offset + region.length; + let offset = old_end; + (source, offset, old_end, old_length) + }, + InterleavedSearch(expanded, range) => { + let old_end = range.end; + if expanded.any_incomplete() { + // Still ongoing. Extend its range + // and continue it with a new region. + range.end += top_level_pending; + let source = InterleavedSearch( + expanded.clone(), range.clone()); + let offset = region.offset + region.length; + (source, offset, old_end, old_length) + } else { + // Region has ended, start a new top + // level region after it. + let source = TopLevelItems(); + let offset = old_end; + (source, offset, old_end, old_length) + } + }, + _ => return None + }) + }) + .unwrap_or((TopLevelItems(), 0, 0, 0)); + + // Add a region with the new rows. + let (new_length, new_top, new_second) = match &source { + InterleavedSearch(expanded, full_range) => { + let old_range = full_range.start..old_end; + let old_top = + self.count_items(&old_range).unwrap_or(0); + let full_top = + self.count_items(full_range)?; + let full_second = + self.count_within(expanded, full_range)?; + let full_length = full_top + full_second; + let new_length = full_length - old_length; + let new_top = full_top - old_top; + let new_second = new_length - new_top; + (new_length, new_top, new_second) + }, + TopLevelItems() => + (top_level_pending, top_level_pending, 0), + _ => + unreachable!() + }; + let region = Region {source, offset, length: new_length}; + self.insert_region(position, region)?; + position += new_length; + top_level_added += new_top; + top_level_pending -= new_top; + second_level_added += new_second; + second_level_pending -= new_second; + + #[cfg(feature="debug-region-map")] { + println!(); + println!("Region map after root node update:"); + for (start, region) in self.regions.borrow().iter() { + println!("{}: {:?}", start, region); + } + } + + // We should now have added all pending rows. + assert!(top_level_pending == 0); + assert!(second_level_pending == 0); + + self.merge_regions(); + + // Update child counts. + let mut root = self.root.borrow_mut(); + root.children.direct_count += top_level_added; + root.children.total_count += + top_level_added + second_level_added; + drop(root); + + // Apply a single update to cover all the regions added. + self.apply_update(model, initial_position, ModelUpdate { + rows_added: top_level_added + second_level_added, + rows_removed: 0, + rows_changed: 0 + }); + } + } else if children_added > 0 { + // This is an item node. Update child counts. let mut node = node_rc.borrow_mut(); let children = node.children_mut(); children.direct_count += children_added; children.total_count += children_added; drop(node); - if expanded { - #[cfg(feature="debug-region-map")] - println!("\nAdding {} new children at {}", - children_added, position); + let interleaved = matches!(state, Interleaved(..)); + + if expanded && !interleaved { + #[cfg(feature="debug-region-map")] { + println!(); + println!("Adding {} new children at {}", + children_added, position); + } // Move the following regions down to make space. let following_regions = self.regions @@ -885,8 +2118,12 @@ where Item: 'static + Clone + Debug, } } - // Return the position after all of this node's rows. - Ok(position) + // Return the position after all of this node's rows, and any pending + // interleaved update to do for this node. + Ok(match state { + Interleaved(update) => (position, update), + _ => (position, None) + }) } fn fetch(&self, position: u64) -> Result, Error> { @@ -899,13 +2136,50 @@ where Item: 'static + Clone + Debug, .with_context(|| format!( "No region before position {position}"))?; - // Get the index of this row relative to the start of that region. - let relative_position = region.offset + (position - start); + // Get the index of this row relative to the start of the region source. + let row_index = region.offset + (position - start); // Get the parent for this row, according to the type of region. - let parent_ref: AnyNodeRc = match region.source { - TopLevelItems() => self.root.clone(), - ChildrenOf(node_ref) => node_ref, + let mut cap = self.capture.borrow_mut(); + let (parent_ref, item_index, item): + (AnyNodeRc, u64, Item) = + match region.source + { + TopLevelItems() => ( + self.root.clone(), + row_index, + cap.item(None, self.view_mode, row_index)?), + ChildrenOf(node_ref) => ( + node_ref.clone(), + row_index, + cap.item( + Some(&node_ref.borrow().item), + self.view_mode, + row_index)?), + InterleavedSearch(expanded, range) => { + // Run the interleaved search. + let mut expanded_items = expanded.iter_items(); + let search_result = cap.find_child( + &mut expanded_items, &range, row_index)?; + // Return a node corresponding to the search result. + use SearchResult::*; + match search_result { + // Search found a top level item. + TopLevelItem(index, item) => { + (self.root.clone(), index, item) + }, + // Search found a child of an expanded top level item. + NextLevelItem(_, parent_index, child_index, item) => { + // There must already be a node for its parent. + let parent_ref = self.root + .borrow() + .children() + .get_expanded(parent_index) + .context("Parent dropped")?; + (parent_ref, child_index, item) + } + } + } }; // Check if we already have a node for this item in the parent's @@ -914,7 +2188,7 @@ where Item: 'static + Clone + Debug, .borrow() .children() .expanded - .get(&relative_position) + .get(&item_index) { return Ok(node_rc.clone()) } @@ -923,30 +2197,28 @@ where Item: 'static + Clone + Debug, if let Some(node_rc) = parent_ref .borrow() .children() - .fetch_incomplete(relative_position) + .fetch_incomplete(item_index) { return Ok(node_rc) } // Otherwise, fetch it from the database. - let mut cap = self.capture.borrow_mut(); - let mut parent = parent_ref.borrow_mut(); - let item = - cap.item(parent.item(), self.view_mode, relative_position)?; let (completion, child_count) = cap.item_children(Some(&item), self.view_mode)?; let node = ItemNode { item, parent: Rc::downgrade(&parent_ref), - item_index: relative_position, + item_index, + completion, children: Children::new(child_count), widgets: RefCell::new(HashSet::new()), }; let node_rc = Rc::new(RefCell::new(node)); if !completion.is_complete() { - parent + parent_ref + .borrow_mut() .children_mut() - .add_incomplete(relative_position, &node_rc); + .add_incomplete(item_index, &node_rc); } Ok(node_rc) }