diff --git a/Cargo.toml b/Cargo.toml index 5af4ff47..02c9ad51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ rand_xorshift = "0.3.0" step-decoder = [] record-ui-test = ["serde", "serde_json"] test-ui-replay = ["serde", "serde_json"] +debug-region-map = [] [[test]] name = "test_replay" diff --git a/src/capture.rs b/src/capture.rs index 68fc92d4..c30ef3c3 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -219,14 +219,14 @@ pub type EndpointDataEvent = u64; pub type EndpointByteCount = u64; pub type DeviceVersion = u32; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum TrafficItem { Transfer(TransferId), Transaction(TransferId, TransactionId), Packet(TransferId, TransactionId, PacketId), } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum DeviceItem { Device(DeviceId, DeviceVersion), DeviceDescriptor(DeviceId), @@ -1063,6 +1063,16 @@ pub enum CompletionStatus { Ongoing } +impl CompletionStatus { + pub fn is_complete(&self) -> bool { + use CompletionStatus::*; + match self { + Complete => true, + Ongoing => false, + } + } +} + pub trait ItemSource { fn item(&mut self, parent: Option<&Item>, index: u64) -> Result; diff --git a/src/id.rs b/src/id.rs index 33028437..b5671d65 100644 --- a/src/id.rs +++ b/src/id.rs @@ -36,7 +36,7 @@ impl Debug for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "Id({})", self.value) + write!(f, "{}", self.value) } } diff --git a/src/model/mod.rs b/src/model/mod.rs index 8b33150d..01054600 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -61,7 +61,7 @@ impl GenericModel for TrafficModel { { let tree_opt = self.imp().tree.borrow(); let tree = tree_opt.as_ref().unwrap(); - tree.set_expanded(self, node, position, expanded) + tree.set_expanded(self, node, position as u64, expanded) } fn update(&self) -> Result { @@ -107,7 +107,7 @@ impl GenericModel for DeviceModel { { let tree_opt = self.imp().tree.borrow(); let tree = tree_opt.as_ref().unwrap(); - tree.set_expanded(self, node, position, expanded) + tree.set_expanded(self, node, position as u64, expanded) } fn update(&self) -> Result { diff --git a/src/tree_list_model.rs b/src/tree_list_model.rs index d0d1bb34..9e26e499 100644 --- a/src/tree_list_model.rs +++ b/src/tree_list_model.rs @@ -1,5 +1,8 @@ use std::cell::RefCell; +use std::cmp::min; use std::collections::{BTreeMap, HashSet}; +use std::collections::btree_map::Entry; +use std::fmt::Debug; use std::marker::PhantomData; use std::num::TryFromIntError; use std::rc::{Rc, Weak}; @@ -8,14 +11,11 @@ use gtk::prelude::{IsA, Cast, WidgetExt}; use gtk::glib::Object; use gtk::gio::prelude::ListModelExt; +use derive_more::AddAssign; +use itertools::Itertools; use thiserror::Error; -use crate::capture::{ - CaptureReader, - CaptureError, - ItemSource, - CompletionStatus, -}; +use crate::capture::{CaptureReader, CaptureError, ItemSource}; use crate::model::GenericModel; use crate::row_data::GenericRowData; use crate::expander::ExpanderWrapper; @@ -28,8 +28,13 @@ pub enum ModelError { RangeError(#[from] TryFromIntError), #[error("Node references a dropped parent")] ParentDropped, + #[error("Internal error: {0}")] + InternalError(String), } +use ModelError::InternalError; + +type RootNodeRc = Rc>>; pub type ItemNodeRc = Rc>>; pub type ItemNodeWeak = Weak>>; type AnyNodeRc = Rc>>; @@ -52,27 +57,24 @@ trait Node { /// Mark this node as completed. fn set_completed(&mut self); - - /// Access this node as an item node, if it is one. - fn item_node(&mut self) -> Option<&mut ItemNode>; } struct Children { /// Number of direct children below this node. - direct_count: u32, + direct_count: u64, /// Total number of displayed rows below this node, recursively. - total_count: u32, + total_count: u64, /// Expanded children of this item. - expanded: BTreeMap>, + expanded: BTreeMap>, /// Incomplete children of this item. - incomplete: BTreeMap>, + incomplete: BTreeMap>, } impl Children { - fn new(child_count: u32) -> Self { + fn new(child_count: u64) -> Self { Children { direct_count: child_count, total_count: child_count, @@ -98,7 +100,7 @@ pub struct ItemNode { parent: Weak>>, /// Index of this node below the parent Item. - item_index: u32, + item_index: u64, /// Children of this item. children: Children, @@ -109,15 +111,10 @@ pub struct ItemNode { impl Children { /// Whether this child is expanded. - fn expanded(&self, index: u32) -> bool { + fn expanded(&self, index: u64) -> bool { self.expanded.contains_key(&index) } - /// Iterate over the expanded children. - fn iter_expanded(&self) -> impl Iterator)> + '_ { - self.expanded.iter() - } - /// 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(); @@ -129,22 +126,22 @@ impl Children { } /// Add an incomplete child. - fn add_incomplete(&mut self, index: u32, child_rc: &ItemNodeRc) { + fn add_incomplete(&mut self, index: u64, child_rc: &ItemNodeRc) { self.incomplete.insert(index, Rc::downgrade(child_rc)); } /// Fetch an incomplete child. - fn fetch_incomplete(&self, index: u32) -> Option> { + fn fetch_incomplete(&self, index: u64) -> Option> { self.incomplete.get(&index).and_then(Weak::upgrade) } /// Get the number of rows between two children. - fn rows_between(&self, start: u32, end: u32) -> u32 { + fn rows_between(&self, start: u64, end: u64) -> u64 { (end - start) + self.expanded .range(start..end) .map(|(_, node_rc)| node_rc.borrow().children.total_count) - .sum::() + .sum::() } } @@ -172,10 +169,6 @@ impl Node for RootNode { fn set_completed(&mut self) { self.complete = true; } - - fn item_node(&mut self) -> Option<&mut ItemNode> { - None - } } impl Node for ItemNode where Item: Copy { @@ -215,21 +208,17 @@ impl Node for ItemNode where Item: Copy { .remove(&self.item_index); } } - - fn item_node(&mut self) -> Option<&mut ItemNode> { - Some(self) - } } -trait NodeRcOps { - fn update_total(&self, expanded: bool, rows_affected: u32) +trait UpdateTotal { + fn update_total(&self, expanded: bool, rows_affected: u64) -> Result<(), ModelError>; } -impl NodeRcOps for Rc> +impl UpdateTotal for Rc> where T: Node + 'static, Item: Copy + 'static { - fn update_total(&self, expanded: bool, rows_affected: u32) + fn update_total(&self, expanded: bool, rows_affected: u64) -> Result<(), ModelError> { let mut node_rc: AnyNodeRc = self.clone(); @@ -248,6 +237,35 @@ where T: Node + 'static, Item: Copy + 'static } } +trait NodeRcOps: UpdateTotal { + fn source(&self) -> Source; + fn item_node_rc(&self) -> Option>; +} + +impl NodeRcOps for RootNodeRc +where Item: Copy + 'static +{ + fn source(&self) -> Source { + TopLevelItems() + } + + fn item_node_rc(&self) -> Option> { + None + } +} + +impl NodeRcOps for ItemNodeRc +where Item: Copy + 'static +{ + fn source(&self) -> Source { + ChildrenOf(self.clone()) + } + + fn item_node_rc(&self) -> Option> { + Some(self.clone()) + } +} + impl ItemNode where Item: Copy { pub fn expanded(&self) -> bool { @@ -267,16 +285,89 @@ impl ItemNode where Item: Copy { } } +#[derive(Clone)] +enum Source { + TopLevelItems(), + ChildrenOf(ItemNodeRc), +} + +use Source::*; + +#[derive(Clone)] +struct Region { + source: Source, + offset: u64, + length: u64, +} + +impl Debug for Region +where Item: Clone + Debug +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) + -> Result<(), std::fmt::Error> + { + use Source::*; + match &self.source { + TopLevelItems() => + write!(f, "Top level items"), + ChildrenOf(rc) => + write!(f, "Children of {:?}", rc.borrow().item), + }?; + write!(f, ", offset {}, length {}", self.offset, self.length) + } +} + +impl Region where Item: Clone { + fn merge( + region_a: &Region, + region_b: &Region + ) -> Option> { + match (®ion_a.source, ®ion_b.source) { + (ChildrenOf(a_ref), ChildrenOf(b_ref)) + if Rc::ptr_eq(a_ref, b_ref) => Some( + Region { + source: region_a.source.clone(), + offset: region_a.offset, + length: region_a.length + region_b.length, + } + ), + (TopLevelItems(), TopLevelItems()) => Some( + Region { + source: TopLevelItems(), + offset: region_a.offset, + length: region_a.length + region_b.length, + } + ), + (..) => None, + } + } +} + +#[derive(Default, AddAssign)] +struct ModelUpdate { + rows_added: u64, + rows_removed: u64, + rows_changed: u64, +} + +impl std::fmt::Display for ModelUpdate { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} added, {} removed, {} changed", + self.rows_added, self.rows_removed, self.rows_changed) + } +} + pub struct TreeListModel { _marker: PhantomData<(Model, RowData)>, capture: RefCell, - root: Rc>>, + root: RootNodeRc, + regions: RefCell>>, #[cfg(any(feature="test-ui-replay", feature="record-ui-test"))] on_item_update: Rc>, } impl TreeListModel -where Item: 'static + Copy, +where Item: 'static + Copy + Debug, Model: GenericModel + ListModelExt, RowData: GenericRowData + IsA + Cast, CaptureReader: ItemSource, @@ -287,24 +378,45 @@ where Item: 'static + Copy, -> Result { let (completion, item_count) = capture.item_children(None)?; - let child_count = item_count.try_into()?; Ok(TreeListModel { _marker: PhantomData, capture: RefCell::new(capture.clone()), root: Rc::new(RefCell::new(RootNode { - children: Children::new(child_count), - complete: matches!(completion, CompletionStatus::Complete), + children: Children::new(item_count), + complete: completion.is_complete(), })), + regions: RefCell::new(BTreeMap::new()), #[cfg(any(feature="test-ui-replay", feature="record-ui-test"))] on_item_update, }) } - #[allow(clippy::only_used_in_recursion)] + fn row_count(&self) -> u64 { + self.root.borrow().children().total_count + } + + fn check(&self) -> Result<(), ModelError> { + // Check that we have the expected number of rows in the region map. + let expected_count = self.row_count(); + let actual_count = self.regions + .borrow() + .iter() + .next_back() + .map(|(start, region)| start + region.length) + .unwrap_or(0); + if expected_count != actual_count { + Err(InternalError(format!( + "Region map total row count is {}, expected {}", + actual_count, expected_count))) + } else { + Ok(()) + } + } + pub fn set_expanded(&self, model: &Model, node_ref: &ItemNodeRc, - position: u32, + position: u64, expanded: bool) -> Result<(), ModelError> { @@ -329,13 +441,28 @@ where Item: 'static + Copy, // The children of this node appear after its own row. let children_position = position + 1; - // If collapsing, first recursively collapse the children of this node. - if !expanded { + let update = if expanded { + #[cfg(feature="debug-region-map")] + println!("\nExpanding node at {}", position); + // Update the region map for the added children. + self.expand(children_position, node_ref)? + } 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)?; } - } + // Update the region map for the removed children. + self.collapse(children_position, node_ref)? + }; + + // Merge adjacent regions with the same source. + self.merge_regions(); // Add or remove this node from the parent's expanded children. parent_rc @@ -347,48 +474,307 @@ where Item: 'static + Copy, // expanded/collapsed entries. node_ref.update_total(expanded, rows_affected)?; - if expanded { - model.items_changed(children_position, 0, rows_affected); - } else { - model.items_changed(children_position, rows_affected, 0); + #[cfg(feature="debug-region-map")] { + println!(); + println!("Region map after {}:", + if expanded {"expansion"} else {"collapse"}); + for (start, region) in self.regions.borrow().iter() { + println!("{}: {:?}", start, region); + } } + self.check()?; + + // Update model. + self.apply_update(model, children_position, update); + Ok(()) } + fn expand(&self, position: u64, node_ref: &ItemNodeRc) + -> Result + { + // Find the start of the parent region. + let (&parent_start, _) = self.regions + .borrow() + .range(..position) + .next_back() + .ok_or_else(|| + InternalError(format!( + "No region before position {position}")))?; + + // Find position of the new region relative to its parent. + let relative_position = position - parent_start; + + // Remove the parent region. + let parent = self.regions + .borrow_mut() + .remove(&parent_start) + .ok_or_else(|| + InternalError(format!( + "Parent not found at position {parent_start}")))?; + + // Remove all following regions, to iterate over later. + let following_regions = self.regions + .borrow_mut() + .split_off(&parent_start) + .into_iter(); + + // 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, + }, + vec![Region { + source: parent.source.clone(), + offset: parent.offset + relative_position, + length: parent.length - relative_position, + }] + )?; + + // Shift all remaining regions down by the added rows. + for (start, region) in following_regions { + self.insert_region(start + update.rows_added, region)?; + } + + Ok(update) + } + + fn collapse(&self, position: u64, node_ref: &ItemNodeRc) + -> Result + { + // Clone the region starting at this position. + let region = self.regions + .borrow() + .get(&position) + .ok_or_else(|| + InternalError(format!( + "No region to delete at position {position}")))? + .clone(); + + // Remove it with following regions, to iterate over and replace them. + let mut following_regions = self.regions + .borrow_mut() + .split_off(&position) + .into_iter(); + + // Process the effects of removing this region. + let update = match ®ion.source { + // Root regions cannot be collapsed. + TopLevelItems() => return Err( + InternalError(String::from( + "Unable to collapse root region"))), + // Non-interleaved region is just removed. + ChildrenOf(_) => { + let (_, _region) = following_regions.next().unwrap(); + #[cfg(feature="debug-region-map")] { + println!(); + println!("Removing: {:?}", _region); + } + ModelUpdate { + rows_added: 0, + rows_removed: node_ref.borrow().children.direct_count, + rows_changed: 0, + } + } + }; + + // Shift all following regions up by the removed rows. + for (start, region) in following_regions { + self.insert_region(start - update.rows_removed, region)?; + } + + Ok(update) + } + + fn insert_region(&self, position: u64, region: Region) + -> Result<(), ModelError> + { + match self.regions.borrow_mut().entry(position) { + Entry::Occupied(mut entry) => { + let old_region = entry.get(); + if old_region.length == 0 { + entry.insert(region); + Ok(()) + } else { + Err(InternalError(format!( + "At position {position}, overwriting region"))) + } + }, + Entry::Vacant(entry) => { + entry.insert(region); + Ok(()) + } + } + } + + fn split_parent(&self, + parent_start: u64, + parent: &Region, + _node_ref: &ItemNodeRc, + parts_before: Vec>, + new_region: Region, + parts_after: Vec>) + -> Result + { + let length_before: u64 = parts_before + .iter() + .map(|region| region.length) + .sum(); + + let length_after: u64 = parts_after + .iter() + .map(|region| region.length) + .sum(); + + let total_length = length_before + new_region.length + length_after; + + let rows_added = total_length - parent.length; + let rows_changed = parent.length - length_before - length_after; + + let update = ModelUpdate { + rows_added, + rows_removed: 0, + rows_changed, + }; + + #[cfg(feature="debug-region-map")] { + println!(); + println!("Splitting: {:?}", parent); + for (i, region) in parts_before.iter().enumerate() { + if i == 0 { + println!(" before: {:?}", region); + } else { + println!(" {:?}", region); + } + } + println!(" new: {:?}", new_region); + for (i, region) in parts_after + .iter() + .filter(|region| region.length > 0) + .enumerate() + { + if i == 0 { + println!(" after: {:?}", region); + } else { + println!(" {:?}", region); + } + } + println!(" {}", &update); + } + + let new_position = parent_start + length_before; + let position_after = new_position + new_region.length; + + let mut position = parent_start; + for region in parts_before { + let length = region.length; + self.insert_region(position, region)?; + position += length; + } + + self.insert_region(new_position, new_region)?; + + position = position_after; + for region in parts_after + .into_iter() + .filter(|region| region.length > 0) + { + let length = region.length; + self.insert_region(position, region)?; + position += length; + } + + Ok(update) + } + + fn merge_regions(&self) { + #[cfg(feature="debug-region-map")] { + println!(); + println!("Before merge:"); + for (start, region) in self.regions.borrow().iter() { + println!("{}: {:?}", start, region); + } + } + + let new_regions = self.regions + .borrow_mut() + .split_off(&0) + .into_iter() + .coalesce(|(start_a, region_a), (start_b, region_b)| + match Region::merge(®ion_a, ®ion_b) { + Some(region_c) => { + #[cfg(feature="debug-region-map")] { + println!(); + println!("Merging: {:?}", region_a); + println!(" and: {:?}", region_b); + println!(" into: {:?}", region_c); + } + Ok((start_a, region_c)) + }, + None => Err(((start_a, region_a), (start_b, region_b))) + } + ) + .collect(); + self.regions.replace(new_regions); + } + pub fn update(&self, model: &Model) -> Result { + #[cfg(feature="debug-region-map")] + let rows_before = self.row_count(); self.update_node(&self.root, 0, model)?; + #[cfg(feature="debug-region-map")] { + let rows_after = self.row_count(); + let rows_added = rows_after - rows_before; + if rows_added > 0 { + println!(); + println!("Region map after update adding {} rows:", rows_added); + for (start, region) in self.regions.borrow().iter() { + println!("{}: {:?}", start, region); + } + } + } + + self.check()?; + Ok(!self.root.borrow().complete) } fn update_node(&self, node_rc: &Rc>, - mut position: u32, + mut position: u64, model: &Model) - -> Result - where T: Node + 'static + -> Result + where T: Node + 'static, + Rc>: NodeRcOps, { - use CompletionStatus::*; - // Extract details about the current node. - let mut node = node_rc.borrow_mut(); + let node = node_rc.borrow(); let expanded = node.expanded(); let children = node.children(); let old_direct_count = children.direct_count; let incomplete_children = children.incomplete .range(0..) .map(|(i, weak)| (*i, weak.clone())) - .collect::)>>(); + .collect::)>>(); // Check if this node had children added and/or was completed. let mut cap = self.capture.borrow_mut(); let (completion, new_direct_count) = cap.item_children(node.item())?; - let new_direct_count = new_direct_count as u32; - let completed = matches!(completion, Complete); + let completed = completion.is_complete(); let children_added = new_direct_count - old_direct_count; + drop(node); - // Deal with this node's own row, if it has one. - if let Some(item_node) = node.item_node() { + 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(); // Check whether this item itself should be updated. let item_updated = if children_added > 0 { @@ -407,7 +793,10 @@ where Item: 'static + Copy, // The node's description may change. let summary = cap.summary(&item_node.item)?; #[cfg(any(feature="test-ui-replay", feature="record-ui-test"))] - (self.on_item_update.borrow_mut())(position, summary.clone()); + if let Ok(position) = u32::try_from(position) { + let mut on_item_update = self.on_item_update.borrow_mut(); + on_item_update(position, summary.clone()); + } for widget in item_node.widgets.borrow().iter() { widget.set_text(summary.clone()); // If there were no previous children, the row was not @@ -426,12 +815,9 @@ where Item: 'static + Copy, // If completed, remove from incomplete node list. if completed { - node.set_completed(); + node_rc.borrow_mut().set_completed(); } - // Release our borrow on the node, as it may be needed by other calls. - drop(node); - if expanded { // Deal with incomplete children of this node. let mut last_index = 0; @@ -443,7 +829,8 @@ where Item: 'static + Copy, .children() .rows_between(last_index, index); // Recursively update this child. - position = self.update_node(&child_rc, position, model)?; + position = self.update_node::>( + &child_rc, position, model)?; last_index = index + 1; } else { // Child no longer referenced, remove it. @@ -472,11 +859,38 @@ where Item: 'static + Copy, drop(node); if expanded { + #[cfg(feature="debug-region-map")] + println!("\nAdding {} new children at {}", + children_added, position); + + // Move the following regions down to make space. + let following_regions = self.regions + .borrow_mut() + .split_off(&position); + for (start, region) in following_regions { + self.regions + .borrow_mut() + .insert(start + children_added, region); + } + + // Insert a new region with the new children. + self.insert_region(position, Region { + source: node_rc.source(), + offset: old_direct_count, + length: children_added + })?; + + self.merge_regions(); + // Update total counts for parent nodes. node_rc.update_total(true, children_added)?; // Add rows for the new children. - model.items_changed(position, 0, children_added); + self.apply_update(model, position, ModelUpdate { + rows_added: children_added, + rows_removed: 0, + rows_changed: 0 + }); // Update the position to continue from. position += children_added; @@ -487,40 +901,38 @@ where Item: 'static + Copy, Ok(position) } - fn fetch(&self, position: u32) -> Result, ModelError> { - let mut parent_ref: Rc>> = self.root.clone(); - let mut relative_position = position; - 'outer: loop { - for (_, node_rc) in parent_ref - .clone() - .borrow() - .children() - .iter_expanded() - { - let node = node_rc.borrow(); - // If the position is before this node, break out of the loop to look it up. - if relative_position < node.item_index { - break; - // If the position matches this node, return it. - } else if relative_position == node.item_index { - return Ok(node_rc.clone()); - // If the position is within this node's children, traverse down the tree and repeat. - } else if relative_position <= node.item_index + node.children.total_count { - parent_ref = node_rc.clone(); - relative_position -= node.item_index + 1; - continue 'outer; - // Otherwise, if the position is after this node, - // adjust the relative position for the node's children above. - } else { - relative_position -= node.children.total_count; - } - } - break; - } + fn fetch(&self, position: u64) -> Result, ModelError> { + // Fetch the region this row is in. + let (start, region) = self.regions + .borrow() + .range(..=position) + .next_back() + .map(|(start, region)| (*start, region.clone())) + .ok_or_else(|| + InternalError(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 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, + }; - // If we've broken out to this point, the node must be directly below `parent` - look it up. + // Check if we already have a node for this item in the parent's + // expanded children. + if let Some(node_rc) = parent_ref + .borrow() + .children() + .expanded + .get(&relative_position) + { + return Ok(node_rc.clone()) + } - // First, check if we already have an incomplete node for this item. + // Also check if we already have an incomplete node for this item. if let Some(node_rc) = parent_ref .borrow() .children() @@ -532,17 +944,17 @@ where Item: 'static + Copy, // 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(), relative_position as u64)?; + let item = cap.item(parent.item(), relative_position)?; let (completion, child_count) = cap.item_children(Some(&item))?; let node = ItemNode { item, parent: Rc::downgrade(&parent_ref), item_index: relative_position, - children: Children::new(child_count.try_into()?), + children: Children::new(child_count), widgets: RefCell::new(HashSet::new()), }; let node_rc = Rc::new(RefCell::new(node)); - if matches!(completion, CompletionStatus::Ongoing) { + if !completion.is_complete() { parent .children_mut() .add_incomplete(relative_position, &node_rc); @@ -566,17 +978,32 @@ where Item: 'static + Copy, } } + fn apply_update(&self, model: &Model, position: u64, update: ModelUpdate) + { + if let Ok(position) = u32::try_from(position) { + let rows_addressable = u32::MAX - position; + let rows_removed = clamp( + update.rows_removed + update.rows_changed, + rows_addressable); + let rows_added = clamp( + update.rows_added + update.rows_changed, + rows_addressable); + model.items_changed(position, rows_removed, rows_added); + } + } + // The following methods correspond to the ListModel interface, and can be // called by a GObject wrapper class to implement that interface. pub fn n_items(&self) -> u32 { - self.root.borrow().children.total_count + clamp(self.row_count(), u32::MAX) } pub fn item(&self, position: u32) -> Option { // First check that the position is valid (must be within the root // node's total child count). - if position >= self.root.borrow().children.total_count { + let position = position as u64; + if position >= self.row_count() { return None } let node_or_err_msg = self.fetch(position).map_err(|e| format!("{e:?}")); @@ -584,3 +1011,7 @@ where Item: 'static + Copy, Some(row_data.upcast::()) } } + +fn clamp(value: u64, max: u32) -> u32 { + min(value, max as u64) as u32 +}