Skip to main content

gpui_kit_grid/engine/
tree.rs

1//! The tree view: rows arranged by `GridRow::parent_key`. No GPUI types.
2//!
3//! [`rebuild`] works on a dense array of the snapshot's rows, in passes of
4//! O(n) apart from the parent lookups and the sort, none of them recursive:
5//!
6//! 1. the rows that pass the view's filters and each row's parent, split
7//!    across threads like the flat path;
8//! 2. cycles of parents broken: their rows become roots;
9//! 3. with a filter, the rows the [`TreeFilter`] shows, and for each the
10//!    nearest shown ancestor as its parent;
11//! 4. the shown rows sorted once by the view's sort and insertion, across
12//!    threads, then dealt to their parents in that order, so siblings are
13//!    sorted;
14//! 5. a depth-first walk from the sorted roots: `Projection::rows`, each
15//!    row's depth and subtree, and the lines its expansion shows;
16//! 6. the totals of the rows [`TreeTotals`] counts.
17//!
18//! The order the rows arrived in does not matter: a child written before
19//! its parent is a root until the parent comes, and the insert rebuilds.
20//!
21//! A removed row that fades stays a leaf under its parent while that is
22//! shown, or a root if it had no parent; otherwise it goes at once. It is
23//! never a parent: its children are orphans, roots. Neither the row count
24//! nor the totals count it.
25
26use std::{
27    cmp::Ordering,
28    fmt,
29    sync::{
30        Arc,
31        atomic::{AtomicUsize, Ordering as AtomicOrdering},
32    },
33};
34
35use super::{
36    CHUNK_ROWS, MIN_ROWS_PER_THREAD, Parallelism, Projection, Rebuild, Structure, ViewSpec,
37    VisibleRows,
38    aggregate::{ViewAggregates, flat_totals},
39    compare_sorted, display,
40    display::{AggregateValues, BuiltStructure, DisplayRow},
41    merge, run_on_threads,
42};
43#[cfg(any(test, feature = "test-support"))]
44use super::{ExpandDepth, Expansion};
45use crate::{
46    model::{GridRow, OwnedValue},
47    store::{RowId, StoreSnapshot},
48};
49
50/// Which rows of a filtered tree are shown.
51/// Without a filter and a quick filter every row passes and is shown.
52#[derive(Clone, Default)]
53#[non_exhaustive]
54pub enum TreeFilter {
55    /// A row is shown if it passes, or if one of its descendants does: the
56    /// matches keep their ancestors for context. Rows below a match that do
57    /// not pass themselves stay hidden. The default.
58    #[default]
59    KeepAncestors,
60    /// A row is shown if it and every ancestor pass: a failing row hides
61    /// its subtree.
62    HideSubtree,
63    /// The application's rule, such as "a passing row shows its whole
64    /// subtree".
65    Custom(Arc<dyn TreeFilterRule>),
66}
67
68impl TreeFilter {
69    /// Whether the rule shows a row.
70    fn is_shown(&self, row: &TreeFilterContext) -> bool {
71        match self {
72            Self::KeepAncestors => row.passes || row.has_passing_descendant,
73            Self::HideSubtree => row.passes && row.ancestors_pass,
74            Self::Custom(rule) => rule.is_shown(row),
75        }
76    }
77}
78
79impl fmt::Debug for TreeFilter {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            Self::KeepAncestors => f.write_str("KeepAncestors"),
83            Self::HideSubtree => f.write_str("HideSubtree"),
84            Self::Custom(_) => f.write_str("Custom(..)"),
85        }
86    }
87}
88
89/// A rule for [`TreeFilter::Custom`]. Called for every row in the
90/// background while the view has a filter or a quick filter; without them
91/// every row is shown and the rule is not called.
92pub trait TreeFilterRule: Send + Sync + 'static {
93    /// Returns true if the row is shown. A shown row whose parent is hidden
94    /// hangs from its nearest shown ancestor, or is a root.
95    fn is_shown(&self, row: &TreeFilterContext) -> bool;
96}
97
98/// What a tree filter rule knows about one row.
99#[derive(Clone, Copy, Debug)]
100pub struct TreeFilterContext {
101    passes: bool,
102    has_passing_descendant: bool,
103    ancestors_pass: bool,
104    has_passing_ancestor: bool,
105}
106
107impl TreeFilterContext {
108    /// A row that passes the filters or not, with what its descendants and
109    /// ancestors do.
110    pub(crate) fn new(
111        passes: bool,
112        has_passing_descendant: bool,
113        ancestors_pass: bool,
114        has_passing_ancestor: bool,
115    ) -> Self {
116        Self {
117            passes,
118            has_passing_descendant,
119            ancestors_pass,
120            has_passing_ancestor,
121        }
122    }
123
124    /// The row passes the view's filter and quick filter.
125    pub fn passes(&self) -> bool {
126        self.passes
127    }
128
129    /// Some descendant of the row passes.
130    pub fn has_passing_descendant(&self) -> bool {
131        self.has_passing_descendant
132    }
133
134    /// Every ancestor of the row passes (true for a root).
135    pub fn ancestors_pass(&self) -> bool {
136        self.ancestors_pass
137    }
138
139    /// Some ancestor of the row passes (false for a root).
140    pub fn has_passing_ancestor(&self) -> bool {
141        self.has_passing_ancestor
142    }
143}
144
145/// Which rows of a tree the totals add up. A parent often holds the sum of
146/// its children already, so adding every row would count it twice.
147///
148/// The totals add the view's aggregates ([`ViewSpec::with_aggregates`]) of
149/// the live shown rows the rule counts, in the order of
150/// `Projection::rows`; their row count is every live shown row. Fading
151/// rows count for nothing: a row whose shown children all fade is a leaf.
152#[derive(Clone, Default)]
153#[non_exhaustive]
154pub enum TreeTotals {
155    /// Shown rows with no shown children. The default.
156    #[default]
157    Leaves,
158    /// Every shown row.
159    AllRows,
160    /// The application's rule.
161    Custom(Arc<dyn TreeTotalsRule>),
162}
163
164impl TreeTotals {
165    /// Whether the rule counts a shown row.
166    fn is_counted(&self, row: &TreeTotalsContext) -> bool {
167        match self {
168            Self::Leaves => !row.is_branch,
169            Self::AllRows => true,
170            Self::Custom(rule) => rule.is_counted(row),
171        }
172    }
173}
174
175impl fmt::Debug for TreeTotals {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        match self {
178            Self::Leaves => f.write_str("Leaves"),
179            Self::AllRows => f.write_str("AllRows"),
180            Self::Custom(_) => f.write_str("Custom(..)"),
181        }
182    }
183}
184
185/// A rule for [`TreeTotals::Custom`]. Called for every shown row in the
186/// background.
187pub trait TreeTotalsRule: Send + Sync + 'static {
188    /// Returns true if the totals count the row.
189    fn is_counted(&self, row: &TreeTotalsContext) -> bool;
190}
191
192/// What a tree totals rule knows about one row.
193#[derive(Clone, Copy, Debug)]
194pub struct TreeTotalsContext {
195    depth: u16,
196    is_branch: bool,
197    passes: bool,
198}
199
200impl TreeTotalsContext {
201    /// A shown row at `depth`, with shown children or not, passing the
202    /// filters itself or kept for context.
203    pub(crate) fn new(depth: u16, is_branch: bool, passes: bool) -> Self {
204        Self {
205            depth,
206            is_branch,
207            passes,
208        }
209    }
210
211    /// The row's depth: 0 for a root.
212    pub fn depth(&self) -> u16 {
213        self.depth
214    }
215
216    /// The row has shown children.
217    pub fn is_branch(&self) -> bool {
218        self.is_branch
219    }
220
221    /// The row passes the filters itself, not only kept for context.
222    pub fn passes(&self) -> bool {
223        self.passes
224    }
225}
226
227/// How a view shows a tree.
228pub struct TreeSpec<R: GridRow> {
229    column: Option<R::Column>,
230    filter: TreeFilter,
231    totals: TreeTotals,
232}
233
234impl<R: GridRow> Clone for TreeSpec<R> {
235    fn clone(&self) -> Self {
236        Self {
237            column: self.column,
238            filter: self.filter.clone(),
239            totals: self.totals.clone(),
240        }
241    }
242}
243
244impl<R: GridRow> Default for TreeSpec<R> {
245    fn default() -> Self {
246        Self {
247            column: None,
248            filter: TreeFilter::default(),
249            totals: TreeTotals::default(),
250        }
251    }
252}
253
254impl<R: GridRow> fmt::Debug for TreeSpec<R> {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        f.debug_struct("TreeSpec")
257            .field("column", &self.column)
258            .field("filter", &self.filter)
259            .field("totals", &self.totals)
260            .finish()
261    }
262}
263
264impl<R: GridRow> TreeSpec<R> {
265    /// A tree with the defaults: the first visible column shows the
266    /// hierarchy, [`TreeFilter::KeepAncestors`], totals over the leaves.
267    pub fn new() -> Self {
268        Self::default()
269    }
270
271    /// The column that shows the indentation and the disclosure chevron.
272    pub fn with_column(mut self, column: R::Column) -> Self {
273        self.column = Some(column);
274        self
275    }
276
277    /// The column that shows the hierarchy; `None`: the first visible one.
278    pub fn column(&self) -> Option<R::Column> {
279        self.column
280    }
281
282    /// Which rows a filtered tree shows (default
283    /// [`TreeFilter::KeepAncestors`]).
284    pub fn with_filter(mut self, filter: TreeFilter) -> Self {
285        self.filter = filter;
286        self
287    }
288
289    /// Which rows a filtered tree shows.
290    pub fn filter(&self) -> &TreeFilter {
291        &self.filter
292    }
293
294    /// Which rows the totals add up (default [`TreeTotals::Leaves`]).
295    pub fn with_totals(mut self, totals: TreeTotals) -> Self {
296        self.totals = totals;
297        self
298    }
299
300    /// Which rows the totals add up.
301    pub fn totals(&self) -> &TreeTotals {
302        &self.totals
303    }
304}
305
306impl<R: GridRow> ViewSpec<R> {
307    /// Shows the rows as a tree by `GridRow::parent_key`. Replaces a
308    /// grouping, as in AG Grid and DevExpress: the last of
309    /// [`Self::with_group_by`] and `with_tree` wins.
310    ///
311    /// A row whose parent key no row has is an orphan and shows as a root,
312    /// as does a row that names itself. Rows whose parents form a cycle
313    /// show as roots too, with a warning in the log. Siblings follow the
314    /// view's sort; the filters choose rows by [`TreeSpec::with_filter`].
315    pub fn with_tree(mut self, tree: TreeSpec<R>) -> Self {
316        self.structure = Structure::Tree(tree);
317        self
318    }
319
320    /// The tree, if the view is one.
321    pub fn tree(&self) -> Option<&TreeSpec<R>> {
322        match self.structure() {
323            Structure::Tree(tree) => Some(tree),
324            Structure::Flat | Structure::Groups(_) => None,
325        }
326    }
327}
328
329#[cfg(any(test, feature = "test-support"))]
330impl<R: GridRow> ViewSpec<R> {
331    /// Lays a tree view out for `depth`, with the rows of the `toggled` keys
332    /// toggled against it: a key given twice is toggled back. The grid owns
333    /// the expansion; this sets it for tests of the engine alone.
334    #[doc(hidden)]
335    pub fn with_tree_expansion(
336        self,
337        depth: ExpandDepth,
338        toggled: impl IntoIterator<Item = R::Key>,
339    ) -> Self {
340        let expansion = toggled
341            .into_iter()
342            .fold(Expansion::new(depth), |expansion, key| {
343                // A toggle inverts the default at any depth.
344                let expanded = expansion.is_row_expanded(&key, 0);
345                expansion.with_row(key, !expanded, 0)
346            });
347        self.with_expansion(expansion)
348    }
349}
350
351/// A shown tree row with shown children.
352#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353pub struct Branch {
354    children: u32,
355    descendants: u32,
356    expanded: bool,
357}
358
359impl Branch {
360    /// The shown children.
361    pub fn child_count(&self) -> usize {
362        self.children as usize
363    }
364
365    /// The shown descendants, children included. The subtree follows the
366    /// row in `Projection::rows`: the next `descendant_count` rows.
367    pub fn descendant_count(&self) -> usize {
368        self.descendants as usize
369    }
370
371    /// Returns true if the children are shown. Answered for a row hidden by
372    /// a collapsed ancestor too.
373    pub fn is_expanded(&self) -> bool {
374        self.expanded
375    }
376}
377
378/// No row: the parent of a root, the place of a row not in the array.
379const NONE: u32 = u32::MAX;
380
381/// One shown row of a tree projection, at its place in `Projection::rows`.
382#[derive(Clone, Copy, Debug)]
383struct TreeNode {
384    /// Shown descendants: the subtree is the next `descendants` rows.
385    descendants: u32,
386    /// Shown children.
387    children: u32,
388    /// 0 for a root; a row deeper than `u16::MAX` stays at `u16::MAX`.
389    depth: u16,
390    /// The children are shown; kept for a row with no children too.
391    expanded: bool,
392    /// The row passes the filters itself, not only kept for context.
393    passes: bool,
394}
395
396/// What a tree projection knows beyond its lines.
397#[derive(Clone, Debug, Default)]
398pub(crate) struct TreeData {
399    /// One per row of `Projection::rows`, at the same place.
400    nodes: Vec<TreeNode>,
401    /// Rows that became roots because their parents formed a cycle.
402    cycles: usize,
403    /// A row that does not pass is shown only for its passing descendants
404    /// ([`TreeFilter::KeepAncestors`]): [`compact`] drops those left with
405    /// none, as a rebuild would.
406    keeps_ancestors: bool,
407}
408
409impl TreeData {
410    /// The branch at a place in `Projection::rows`, if the row has shown
411    /// children.
412    fn branch(&self, order_ix: usize) -> Option<Branch> {
413        let node = self.nodes.get(order_ix)?;
414        (node.children > 0).then_some(Branch {
415            children: node.children,
416            descendants: node.descendants,
417            expanded: node.expanded,
418        })
419    }
420
421    /// The depth of the row at a place in `Projection::rows`, shown or
422    /// under a collapsed row: what `Projection::expanded_lines` gives it.
423    pub(crate) fn depth(&self, order_ix: usize) -> u16 {
424        self.nodes.get(order_ix).map_or(0, |node| node.depth)
425    }
426
427    /// The live rows of `rows` (the projection's) that `rule` counts, in
428    /// order; `row` gives `None` for a fading row. A row is a branch to the
429    /// rule when one of its shown children is live.
430    fn counted<'a, R: GridRow>(
431        &'a self,
432        rule: &'a TreeTotals,
433        live: &'a [Option<&'a R>],
434    ) -> impl Iterator<Item = &'a R> + Clone + 'a {
435        // Each live row marks its parent: the row above it one level up.
436        let mut has_live_child = vec![false; self.nodes.len()];
437        let mut ancestors: Vec<usize> = Vec::new();
438        for (order_ix, node) in self.nodes.iter().enumerate() {
439            ancestors.truncate(usize::from(node.depth));
440            if live[order_ix].is_some()
441                && let Some(&parent) = ancestors.last()
442            {
443                has_live_child[parent] = true;
444            }
445            ancestors.push(order_ix);
446        }
447        self.nodes.iter().zip(live).zip(has_live_child).filter_map(
448            move |((node, row), is_branch)| {
449                let row = (*row)?;
450                rule.is_counted(&TreeTotalsContext::new(node.depth, is_branch, node.passes))
451                    .then_some(row)
452            },
453        )
454    }
455
456    /// The totals of the rows of `rows` (the projection's) that `rule`
457    /// counts, and the live rows; `row` gives `None` for a fading row.
458    fn totals<'a, R: GridRow>(
459        &self,
460        aggregates: &ViewAggregates<'_, R>,
461        rule: &TreeTotals,
462        rows: &[RowId],
463        row: impl Fn(RowId) -> Option<&'a R>,
464    ) -> (Vec<OwnedValue>, usize) {
465        let live: Vec<Option<&'a R>> = rows.iter().map(|&id| row(id)).collect();
466        let count = live.iter().flatten().count();
467        if aggregates.is_empty() {
468            return (Vec::new(), count);
469        }
470        let (totals, _) = flat_totals(aggregates, self.counted(rule, &live));
471        (totals, count)
472    }
473}
474
475impl Projection {
476    /// The tree row with shown children; `None` for a leaf, a row the view
477    /// does not show, and outside a tree.
478    pub fn branch(&self, id: RowId) -> Option<Branch> {
479        self.tree_data()?.branch(self.order_ix(id)?)
480    }
481
482    /// The rows of a tree shown as roots because their parents formed a
483    /// cycle; 0 outside a tree. A row that is its own parent is a root but
484    /// not a cycle.
485    pub fn tree_cycles(&self) -> usize {
486        self.tree_data().map_or(0, |tree| tree.cycles)
487    }
488}
489
490/// Rows in cycles at the last tree rebuild of this process. A warning is
491/// written when the count changes, not on every rebuild: at 10 rebuilds a
492/// second that would be noise. A count, not a clock (AGENTS.md, "Store
493/// clock only").
494static CYCLE_ROWS: AtomicUsize = AtomicUsize::new(0);
495
496/// Records `cycles` in `last`: true if there are cycles and their count
497/// changed since the last call.
498fn is_new_cycle_count(last: &AtomicUsize, cycles: usize) -> bool {
499    last.swap(cycles, AtomicOrdering::Relaxed) != cycles && cycles > 0
500}
501
502/// One row of the dense array while a tree is built.
503#[derive(Clone, Copy, Debug, Default)]
504struct Link {
505    /// The parent's place in the array, or [`NONE`]. Once the filter has
506    /// run, the nearest shown ancestor.
507    parent: u32,
508    /// The row passes the view's filter and quick filter.
509    passes: bool,
510    /// The tree filter shows the row.
511    shown: bool,
512}
513
514/// What a row's ancestors and descendants do, for the tree filter.
515#[derive(Clone, Copy, Debug)]
516struct Lineage {
517    has_passing_descendant: bool,
518    ancestors_pass: bool,
519    has_passing_ancestor: bool,
520    /// The row itself if shown, else its nearest shown ancestor or [`NONE`].
521    nearest_shown: u32,
522}
523
524/// Children lists of a forest, compressed: the children of `node` are
525/// `items[starts[node]..starts[node + 1]]`.
526struct Children {
527    starts: Vec<u32>,
528    items: Vec<u32>,
529}
530
531impl Children {
532    /// The children of `nodes` places, each list in the order of `order`;
533    /// `parent` gives a node's parent or [`NONE`].
534    fn new(
535        nodes: usize,
536        order: impl DoubleEndedIterator<Item = u32> + Clone,
537        parent: impl Fn(u32) -> u32,
538    ) -> Self {
539        let mut starts = vec![0u32; nodes + 1];
540        let mut count = 0;
541        for node in order.clone() {
542            let parent = parent(node);
543            if parent != NONE {
544                starts[parent as usize] += 1;
545                count += 1;
546            }
547        }
548        // Each entry becomes the end of its node's list...
549        let mut end = 0;
550        for start in &mut starts {
551            end += *start;
552            *start = end;
553        }
554        // ...and, filled from the back, its start.
555        let mut items = vec![0u32; count];
556        for node in order.rev() {
557            let parent = parent(node);
558            if parent != NONE {
559                let start = &mut starts[parent as usize];
560                *start -= 1;
561                items[*start as usize] = node;
562            }
563        }
564        Self { starts, items }
565    }
566
567    /// The range of `node`'s children in `items`.
568    fn range(&self, node: u32) -> (u32, u32) {
569        let node = node as usize;
570        (self.starts[node], self.starts[node + 1])
571    }
572}
573
574/// A thread per [`MIN_ROWS_PER_THREAD`] rows, at most `budget`: a tree
575/// always has work, the parent lookups.
576fn parallelism(rows: usize, budget: usize) -> Parallelism {
577    Parallelism {
578        threads: budget.min(rows / MIN_ROWS_PER_THREAD).max(1),
579        chunk_rows: CHUNK_ROWS,
580    }
581}
582
583/// Builds a tree view.
584pub(super) fn rebuild<R: GridRow>(
585    engine: &Rebuild,
586    snapshot: &StoreSnapshot<R>,
587    spec: &ViewSpec<R>,
588    tree: &TreeSpec<R>,
589) -> Projection {
590    let parallelism = parallelism(snapshot.len(), engine.max_threads());
591    let projection = build(snapshot, spec, tree, parallelism);
592    let cycles = projection.tree_cycles();
593    if is_new_cycle_count(&CYCLE_ROWS, cycles) {
594        log::warn!(
595            "gpui-kit-grid: the parents of {cycles} tree rows form cycles; those rows are shown as roots"
596        );
597    }
598    projection
599}
600
601/// Builds a tree view with `parallelism`.
602fn build<R: GridRow>(
603    snapshot: &StoreSnapshot<R>,
604    spec: &ViewSpec<R>,
605    tree: &TreeSpec<R>,
606    parallelism: Parallelism,
607) -> Projection {
608    // The live rows in a dense array, and each slot's place in it; fading
609    // rows join at the end.
610    let mut rows = Vec::with_capacity(snapshot.len());
611    rows.extend(snapshot.iter());
612    let mut places = vec![NONE; snapshot.capacity()];
613    for (place, (id, _)) in rows.iter().enumerate() {
614        places[id.ix()] = place as u32;
615    }
616
617    // Whether each row passes, and its parent: an orphan and a row that
618    // names itself are roots.
619    let mut links = map_rows(
620        &rows,
621        parallelism,
622        || VisibleRows::new(spec),
623        |visible, place, row| Link {
624            parent: row
625                .parent_key()
626                .and_then(|key| snapshot.row_id(key))
627                .and_then(|id| places.get(id.ix()).copied())
628                .filter(|&parent| parent != place as u32)
629                .unwrap_or(NONE),
630            passes: visible.contains(row),
631            shown: true,
632        },
633    );
634    let cycles = break_cycles(&mut links);
635    let mut shown = if spec.filter.is_some() || spec.quick_filter.is_some() {
636        apply_filter(&mut links, tree.filter())
637    } else {
638        links.len()
639    };
640    let fading_seqs = add_fading(snapshot, spec, &places, &mut rows, &mut links);
641    shown += fading_seqs.len();
642    drop(places);
643
644    // Siblings in the order of the view: sorted once, dealt in order.
645    let sorted = sort_shown(
646        snapshot,
647        spec,
648        &rows,
649        &links,
650        &fading_seqs,
651        shown,
652        parallelism,
653    );
654    let children = Children::new(links.len(), sorted.iter().copied(), |place| {
655        links[place as usize].parent
656    });
657
658    // Depth first from the sorted roots.
659    let expansion = spec.expansion();
660    let mut order = Vec::with_capacity(shown);
661    let mut nodes = Vec::with_capacity(shown);
662    // The rows being walked: (place in `order`, next child, end of
663    // children); the stack's length is the depth of the next row.
664    let mut stack: Vec<(u32, u32, u32)> = Vec::with_capacity(shown);
665    for &root in &sorted {
666        if links[root as usize].parent != NONE {
667            continue;
668        }
669        let mut next = Some(root);
670        loop {
671            if let Some(place) = next.take() {
672                let (id, row) = rows[place as usize];
673                let (start, end) = children.range(place);
674                let depth = stack.len().min(usize::from(u16::MAX)) as u16;
675                stack.push((order.len() as u32, start, end));
676                order.push(id);
677                nodes.push(TreeNode {
678                    descendants: 0,
679                    children: end - start,
680                    depth,
681                    expanded: end > start && expansion.is_row_expanded(row.key(), depth),
682                    passes: links[place as usize].passes,
683                });
684            }
685            let Some(top) = stack.last_mut() else {
686                break;
687            };
688            if top.1 < top.2 {
689                next = Some(children.items[top.1 as usize]);
690                top.1 += 1;
691            } else {
692                let order_ix = top.0 as usize;
693                stack.pop();
694                nodes[order_ix].descendants = (order.len() - order_ix - 1) as u32;
695            }
696        }
697    }
698    debug_assert_eq!(order.len(), shown, "every shown row hangs from a root");
699
700    let data = TreeData {
701        nodes,
702        cycles,
703        keeps_ancestors: matches!(tree.filter(), TreeFilter::KeepAncestors),
704    };
705    let aggregates = ViewAggregates::new(spec);
706    let totals = if aggregates.is_empty() {
707        AggregateValues::default()
708    } else {
709        let (totals, _) = data.totals(&aggregates, tree.totals(), &order, |id| {
710            snapshot.row(id).map(|row| &**row)
711        });
712        AggregateValues::new(aggregates.columns(), totals)
713    };
714    let lines = lay_out(&order, &data.nodes);
715    Projection::with_lines(snapshot, order, lines, BuiltStructure::Tree(data))
716        .with_row_count(shown - fading_seqs.len())
717        .with_totals(totals)
718}
719
720/// Adds the fading rows a tree shows to the dense array, after the live
721/// ones: those that pass the view's filters, each under its parent while
722/// the filter shows that, or at the root if it had no parent (or named
723/// itself). A fading row is never a parent, and it keeps no ancestor for
724/// context. Returns their insertion sequences, in order.
725fn add_fading<'a, R: GridRow>(
726    snapshot: &'a StoreSnapshot<R>,
727    spec: &ViewSpec<R>,
728    places: &[u32],
729    rows: &mut Vec<(RowId, &'a Arc<R>)>,
730    links: &mut Vec<Link>,
731) -> Vec<u64> {
732    let mut fading = snapshot.fading_rows().peekable();
733    if fading.peek().is_none() {
734        return Vec::new();
735    }
736    let mut visible = VisibleRows::new(spec);
737    let mut seqs = Vec::new();
738    for (id, row, _, seq) in fading {
739        if !visible.contains(row) {
740            continue;
741        }
742        let parent = match row.parent_key() {
743            Some(key) if key != row.key() => {
744                match snapshot
745                    .row_id(key)
746                    .and_then(|parent| places.get(parent.ix()).copied())
747                {
748                    Some(parent) if parent != NONE && links[parent as usize].shown => parent,
749                    // A hidden, removed or missing parent: it goes at once.
750                    _ => continue,
751                }
752            }
753            _ => NONE,
754        };
755        rows.push((id, row));
756        links.push(Link {
757            parent,
758            passes: true,
759            shown: true,
760        });
761        seqs.push(seq);
762    }
763    seqs
764}
765
766/// `map` over every row with its place, in order. A large array is split
767/// across threads like the flat path's: each takes chunks of rows and has
768/// its own `state` (the quick filter's buffers).
769fn map_rows<'a, R: GridRow, S, T: Copy + Default + Send>(
770    rows: &[(RowId, &'a Arc<R>)],
771    parallelism: Parallelism,
772    state: impl Fn() -> S + Sync,
773    map: impl Fn(&mut S, usize, &'a Arc<R>) -> T + Sync,
774) -> Vec<T> {
775    if parallelism.threads <= 1 {
776        let mut state = state();
777        return rows
778            .iter()
779            .enumerate()
780            .map(|(place, &(_, row))| map(&mut state, place, row))
781            .collect();
782    }
783
784    let chunk_rows = parallelism.chunk_rows;
785    let next_chunk = AtomicUsize::new(0);
786    // One thread's share: the chunks it took and their values in order.
787    let share = || {
788        let mut state = state();
789        let mut starts = Vec::new();
790        let mut values = Vec::new();
791        loop {
792            let start = next_chunk.fetch_add(chunk_rows, AtomicOrdering::Relaxed);
793            if start >= rows.len() {
794                break;
795            }
796            let chunk = &rows[start..rows.len().min(start + chunk_rows)];
797            starts.push(start);
798            values.extend(
799                chunk
800                    .iter()
801                    .enumerate()
802                    .map(|(offset, &(_, row))| map(&mut state, start + offset, row)),
803            );
804        }
805        (starts, values)
806    };
807    let mut mapped = vec![T::default(); rows.len()];
808    for (starts, values) in run_on_threads(&vec![&share; parallelism.threads]) {
809        let mut values = values.as_slice();
810        for start in starts {
811            let (chunk, rest) = values.split_at(chunk_rows.min(rows.len() - start));
812            mapped[start..start + chunk.len()].copy_from_slice(chunk);
813            values = rest;
814        }
815    }
816    mapped
817}
818
819/// Makes every row on a cycle of parents a root and returns how many there
820/// are. Their children stay their children. Each row is colored unvisited,
821/// on the path or done; from each unvisited row the walk goes up until a
822/// root, a done row or a row on the path, which closes a cycle: O(n).
823fn break_cycles(links: &mut [Link]) -> usize {
824    const UNVISITED: u8 = 0;
825    const ON_PATH: u8 = 1;
826    const DONE: u8 = 2;
827
828    let mut state = vec![UNVISITED; links.len()];
829    let mut path: Vec<u32> = Vec::with_capacity(links.len());
830    let mut broken = 0;
831    for start in 0..links.len() as u32 {
832        let mut place = start;
833        while state[place as usize] == UNVISITED {
834            state[place as usize] = ON_PATH;
835            path.push(place);
836            let parent = links[place as usize].parent;
837            if parent == NONE {
838                break;
839            }
840            if state[parent as usize] == ON_PATH {
841                // The cycle runs from `parent` to the end of the path.
842                for &member in path.iter().rev() {
843                    links[member as usize].parent = NONE;
844                    broken += 1;
845                    if member == parent {
846                        break;
847                    }
848                }
849                break;
850            }
851            place = parent;
852        }
853        for place in path.drain(..) {
854            state[place as usize] = DONE;
855        }
856    }
857    broken
858}
859
860/// Decides which rows the tree filter shows and hangs each shown row from
861/// its nearest shown ancestor. Returns the number of shown rows.
862fn apply_filter(links: &mut [Link], filter: &TreeFilter) -> usize {
863    let rows = links.len();
864    let all = 0..rows as u32;
865    let children = Children::new(rows, all.clone(), |place| links[place as usize].parent);
866
867    // Parents before children: depth first from the roots.
868    let mut preorder = Vec::with_capacity(rows);
869    let mut stack = Vec::with_capacity(rows);
870    for root in all.filter(|&place| links[place as usize].parent == NONE) {
871        stack.push(root);
872        while let Some(place) = stack.pop() {
873            preorder.push(place);
874            let (start, end) = children.range(place);
875            stack.extend(children.items[start as usize..end as usize].iter().rev());
876        }
877    }
878    debug_assert_eq!(preorder.len(), rows, "cycles are broken");
879
880    let mut lineage = vec![
881        Lineage {
882            has_passing_descendant: false,
883            ancestors_pass: true,
884            has_passing_ancestor: false,
885            nearest_shown: NONE,
886        };
887        rows
888    ];
889    // Descendants: children before parents.
890    for &place in preorder.iter().rev() {
891        let link = links[place as usize];
892        if link.parent != NONE && (link.passes || lineage[place as usize].has_passing_descendant) {
893            lineage[link.parent as usize].has_passing_descendant = true;
894        }
895    }
896    // Ancestors and the filter: parents before children. A row's own
897    // `parent` still names its parent when the row is reached.
898    let mut shown = 0;
899    for &place in &preorder {
900        let link = links[place as usize];
901        let (ancestors_pass, has_passing_ancestor, parent_shown) = match link.parent {
902            NONE => (true, false, NONE),
903            parent => {
904                let above = lineage[parent as usize];
905                let passes = links[parent as usize].passes;
906                (
907                    above.ancestors_pass && passes,
908                    above.has_passing_ancestor || passes,
909                    above.nearest_shown,
910                )
911            }
912        };
913        let row = &mut lineage[place as usize];
914        row.ancestors_pass = ancestors_pass;
915        row.has_passing_ancestor = has_passing_ancestor;
916        let is_shown = filter.is_shown(&TreeFilterContext::new(
917            link.passes,
918            row.has_passing_descendant,
919            ancestors_pass,
920            has_passing_ancestor,
921        ));
922        row.nearest_shown = if is_shown { place } else { parent_shown };
923        shown += usize::from(is_shown);
924        let link = &mut links[place as usize];
925        link.shown = is_shown;
926        link.parent = parent_shown;
927    }
928    shown
929}
930
931/// The places of the shown rows in the order of the view's sort, ties by
932/// insertion. The fading rows at the end of `rows` have the insertion
933/// sequences `fading_seqs`.
934fn sort_shown<R: GridRow>(
935    snapshot: &StoreSnapshot<R>,
936    spec: &ViewSpec<R>,
937    rows: &[(RowId, &Arc<R>)],
938    links: &[Link],
939    fading_seqs: &[u64],
940    shown: usize,
941    parallelism: Parallelism,
942) -> Vec<u32> {
943    let live = rows.len() - fading_seqs.len();
944    let seq = |place: u32, id: RowId| match (place as usize).checked_sub(live) {
945        Some(fading) => fading_seqs[fading],
946        None => snapshot.seq(id),
947    };
948    match spec.sort.split_first() {
949        // Storage order is not insertion order once slots are reused.
950        None => sorted_places(
951            rows,
952            links,
953            shown,
954            parallelism,
955            |place, id, _| (seq(place, id), place),
956            |a, b| a.0.cmp(&b.0),
957            |&(_, place)| place,
958        ),
959        Some((&(first, first_direction), rest)) => sorted_places(
960            rows,
961            links,
962            shown,
963            parallelism,
964            |place, id, row| ((row.value(first), seq(place, id), id, row), place),
965            move |a, b| compare_sorted(&a.0, &b.0, first_direction, rest),
966            |&(_, place)| place,
967        ),
968    }
969}
970
971/// The shown rows ordered by their entries, as their places. `compare` is
972/// a total order under which no two rows are equal, so the result does not
973/// depend on how the work is split: the flat path's sort, over the shown
974/// rows of the array.
975fn sorted_places<'a, R: GridRow, E: Copy + Send + Sync>(
976    rows: &[(RowId, &'a Arc<R>)],
977    links: &[Link],
978    shown: usize,
979    parallelism: Parallelism,
980    entry: impl Fn(u32, RowId, &'a Arc<R>) -> E + Sync,
981    compare: impl Fn(&E, &E) -> Ordering + Sync,
982    place: impl Fn(&E) -> u32,
983) -> Vec<u32> {
984    let extend = |entries: &mut Vec<E>, start: usize, end: usize| {
985        entries.extend(
986            (start..end)
987                .filter(|&ix| links[ix].shown)
988                .map(|ix| entry(ix as u32, rows[ix].0, rows[ix].1)),
989        );
990    };
991    if parallelism.threads <= 1 {
992        let mut entries = Vec::with_capacity(shown);
993        extend(&mut entries, 0, rows.len());
994        entries.sort_unstable_by(&compare);
995        return entries.iter().map(place).collect();
996    }
997
998    let next_chunk = AtomicUsize::new(0);
999    let sorted_run = || {
1000        let mut entries = Vec::new();
1001        loop {
1002            let start = next_chunk.fetch_add(parallelism.chunk_rows, AtomicOrdering::Relaxed);
1003            if start >= rows.len() {
1004                break;
1005            }
1006            extend(
1007                &mut entries,
1008                start,
1009                rows.len().min(start + parallelism.chunk_rows),
1010            );
1011        }
1012        entries.sort_unstable_by(&compare);
1013        entries
1014    };
1015    let mut runs = run_on_threads(&vec![&sorted_run; parallelism.threads]);
1016    runs.retain(|run| !run.is_empty());
1017
1018    // Neighbouring runs merge in pairs, a thread per pair, until two are
1019    // left for the calling thread.
1020    while runs.len() > 2 {
1021        let merges: Vec<_> = runs
1022            .chunks(2)
1023            .map(|pair| {
1024                let compare = &compare;
1025                move || match pair {
1026                    [first, second] => {
1027                        let mut merged = Vec::with_capacity(first.len() + second.len());
1028                        merge(first, second, compare, |entry| *entry, &mut merged);
1029                        merged
1030                    }
1031                    _ => pair.concat(),
1032                }
1033            })
1034            .collect();
1035        runs = run_on_threads(&merges);
1036    }
1037    let mut places = Vec::with_capacity(shown);
1038    match runs.as_slice() {
1039        [first, second] => merge(first, second, &compare, place, &mut places),
1040        runs => places.extend(runs.iter().flatten().map(place)),
1041    }
1042    places
1043}
1044
1045/// The shown lines: every row whose ancestors are all expanded.
1046fn lay_out(rows: &[RowId], nodes: &[TreeNode]) -> Vec<DisplayRow> {
1047    let mut lines = Vec::with_capacity(rows.len());
1048    let mut order_ix = 0;
1049    while let (Some(&row), Some(node)) = (rows.get(order_ix), nodes.get(order_ix)) {
1050        lines.push(DisplayRow::Data {
1051            row,
1052            depth: node.depth,
1053        });
1054        order_ix += 1;
1055        if node.children > 0 && !node.expanded {
1056            order_ix += node.descendants as usize;
1057        }
1058    }
1059    lines
1060}
1061
1062/// Drops removed rows from a tree projection without sorting: O(n).
1063///
1064/// A removed leaf only leaves its parent with one child less. The children
1065/// of a removed row become roots, as `parent_key` now names a missing row:
1066/// each with its subtree, in its old order, right after the subtree of the
1067/// root that held it. Their depths shrink; every row keeps its expansion
1068/// flag, and the lines are laid out again. Their order among the roots may
1069/// not follow the sort: the view rebuilds a tree whose branch was removed,
1070/// and this is the fallback.
1071///
1072/// With [`TreeFilter::KeepAncestors`], a row kept only for its passing
1073/// descendants goes when the last of them does, as in a rebuild.
1074///
1075/// A removed row that fades stays as a leaf where a rebuild puts it: under
1076/// its parent if that stays, at the root if it was a root. Its children
1077/// become roots all the same.
1078pub(super) fn compact<R: GridRow>(
1079    previous: &Projection,
1080    snapshot: &StoreSnapshot<R>,
1081) -> Projection {
1082    let Some(tree) = previous.tree_data() else {
1083        return display::compact(previous, snapshot);
1084    };
1085    let count = previous.rows.len();
1086    let live: Vec<bool> = previous
1087        .rows
1088        .iter()
1089        .map(|&id| snapshot.row(id).is_some())
1090        .collect();
1091    // A fading row stays if it passes the filters, as in a rebuild.
1092    let mut kept: Vec<bool> = previous
1093        .rows
1094        .iter()
1095        .zip(&live)
1096        .zip(&tree.nodes)
1097        .map(|((&id, &live), node)| live || (node.passes && snapshot.fading_row(id).is_some()))
1098        .collect();
1099
1100    // In the old order, parents come first. Each kept row's new parent is
1101    // its old one if kept and live, its root is where its subtree now
1102    // starts.
1103    let mut ancestors: Vec<u32> = Vec::with_capacity(count);
1104    let mut parents = vec![NONE; count];
1105    let mut roots = vec![NONE; count];
1106    let mut depths = vec![0u16; count];
1107    let mut fading = Vec::new();
1108    for order_ix in 0..count {
1109        ancestors.truncate(usize::from(tree.nodes[order_ix].depth));
1110        let old_parent = ancestors.last().copied().unwrap_or(NONE);
1111        ancestors.push(order_ix as u32);
1112        if !kept[order_ix] {
1113            continue;
1114        }
1115        let parent_stays =
1116            old_parent != NONE && kept[old_parent as usize] && live[old_parent as usize];
1117        if !live[order_ix] {
1118            fading.push(order_ix);
1119            if !is_fading_place(snapshot, previous, order_ix, old_parent, parent_stays) {
1120                kept[order_ix] = false;
1121                continue;
1122            }
1123        }
1124        if parent_stays {
1125            let parent = old_parent as usize;
1126            parents[order_ix] = old_parent;
1127            roots[order_ix] = roots[parent];
1128            depths[order_ix] = depths[parent].saturating_add(1);
1129        } else {
1130            roots[order_ix] = order_ix as u32;
1131        }
1132    }
1133
1134    // Children before parents: rows kept for context with no passing live
1135    // descendant left go (with their subtrees, which hold none either),
1136    // then the children and descendants of the rest are counted.
1137    let mut children = vec![0u32; count];
1138    let mut descendants = vec![0u32; count];
1139    let mut has_passing_descendant = vec![false; count];
1140    for order_ix in (0..count).rev() {
1141        if !kept[order_ix] {
1142            continue;
1143        }
1144        let passes = tree.nodes[order_ix].passes;
1145        if tree.keeps_ancestors && !passes && !has_passing_descendant[order_ix] {
1146            kept[order_ix] = false;
1147            continue;
1148        }
1149        let parent = parents[order_ix];
1150        if parent != NONE {
1151            let parent = parent as usize;
1152            children[parent] += 1;
1153            descendants[parent] += 1 + descendants[order_ix];
1154            has_passing_descendant[parent] |=
1155                live[order_ix] && (passes || has_passing_descendant[order_ix]);
1156        }
1157    }
1158    // A fading row whose parent went goes too: it was counted under that
1159    // parent alone.
1160    for order_ix in fading {
1161        let parent = parents[order_ix];
1162        if parent != NONE && !kept[parent as usize] {
1163            kept[order_ix] = false;
1164        }
1165    }
1166
1167    // Each root's subtree, in the old order of the roots; within a subtree
1168    // the old order is a depth-first order still.
1169    let mut starts = vec![0u32; count];
1170    let mut next = 0;
1171    for order_ix in 0..count {
1172        if kept[order_ix] && roots[order_ix] == order_ix as u32 {
1173            starts[order_ix] = next;
1174            next += 1 + descendants[order_ix];
1175        }
1176    }
1177    let mut placed = vec![NONE; next as usize];
1178    let mut row_count = 0;
1179    for order_ix in (0..count).filter(|&order_ix| kept[order_ix]) {
1180        let start = &mut starts[roots[order_ix] as usize];
1181        placed[*start as usize] = order_ix as u32;
1182        *start += 1;
1183        row_count += usize::from(live[order_ix]);
1184    }
1185    let rows: Vec<RowId> = placed
1186        .iter()
1187        .map(|&order_ix| previous.rows[order_ix as usize])
1188        .collect();
1189    let nodes: Vec<TreeNode> = placed
1190        .iter()
1191        .map(|&order_ix| {
1192            let order_ix = order_ix as usize;
1193            let old = tree.nodes[order_ix];
1194            TreeNode {
1195                descendants: descendants[order_ix],
1196                children: children[order_ix],
1197                depth: depths[order_ix],
1198                expanded: old.expanded,
1199                passes: old.passes,
1200            }
1201        })
1202        .collect();
1203
1204    let lines = lay_out(&rows, &nodes);
1205    Projection::with_lines(
1206        snapshot,
1207        rows,
1208        lines,
1209        BuiltStructure::Tree(TreeData {
1210            nodes,
1211            cycles: tree.cycles,
1212            keeps_ancestors: tree.keeps_ancestors,
1213        }),
1214    )
1215    .with_row_count(row_count)
1216    .with_totals(previous.totals.clone())
1217}
1218
1219/// Returns true if the fading row at `order_ix` of `previous` stays where it
1220/// is, as a rebuild would place it: under its old parent if that stays and
1221/// is the row its `parent_key` names, at the root if it was a root without
1222/// a parent (or naming itself).
1223fn is_fading_place<R: GridRow>(
1224    snapshot: &StoreSnapshot<R>,
1225    previous: &Projection,
1226    order_ix: usize,
1227    old_parent: u32,
1228    parent_stays: bool,
1229) -> bool {
1230    let Some((row, _)) = snapshot.fading_row(previous.rows[order_ix]) else {
1231        return false;
1232    };
1233    match row.parent_key() {
1234        Some(key) if key != row.key() => {
1235            parent_stays && snapshot.row_id(key) == previous.rows.get(old_parent as usize).copied()
1236        }
1237        _ => old_parent == NONE,
1238    }
1239}
1240
1241/// A tree projection with fresh totals and row count from `snapshot`: the
1242/// same rows, in the same order (the aggregate job).
1243pub(super) fn refresh<R: GridRow>(
1244    previous: &Projection,
1245    snapshot: &StoreSnapshot<R>,
1246    spec: &ViewSpec<R>,
1247) -> Projection {
1248    let Some(tree) = previous.tree_data() else {
1249        return previous.duplicate().with_store_version(snapshot.version());
1250    };
1251    let aggregates = ViewAggregates::new(spec);
1252    let default = TreeTotals::default();
1253    let rule = spec.tree().map_or(&default, TreeSpec::totals);
1254    let (totals, live) = tree.totals(&aggregates, rule, &previous.rows, |id| {
1255        snapshot.row(id).map(|row| &**row)
1256    });
1257    previous
1258        .duplicate()
1259        .with_store_version(snapshot.version())
1260        .with_row_count(live)
1261        .with_totals(AggregateValues::new(aggregates.columns(), totals))
1262}
1263
1264/// A tree projection laid out for `spec.expansion()`, by row key: the same
1265/// rows, depths and branches, with the expansion of each branch read again
1266/// and the lines laid out again. No sorting, no parent lookups: O(n). A
1267/// branch removed from `snapshot` since keeps its flag.
1268pub(super) fn relayout<R: GridRow>(
1269    previous: &Projection,
1270    snapshot: &StoreSnapshot<R>,
1271    spec: &ViewSpec<R>,
1272) -> Projection {
1273    let Some(tree) = previous.tree_data() else {
1274        return previous.duplicate();
1275    };
1276    let expansion = spec.expansion();
1277    let mut nodes = tree.nodes.clone();
1278    for (node, &id) in nodes.iter_mut().zip(&previous.rows) {
1279        if node.children > 0
1280            && let Some(row) = snapshot.row(id)
1281        {
1282            node.expanded = expansion.is_row_expanded(row.key(), node.depth);
1283        }
1284    }
1285    let lines = lay_out(&previous.rows, &nodes);
1286    Projection::with_lines(
1287        snapshot,
1288        previous.rows.clone(),
1289        lines,
1290        BuiltStructure::Tree(TreeData {
1291            nodes,
1292            cycles: tree.cycles,
1293            keeps_ancestors: tree.keeps_ancestors,
1294        }),
1295    )
1296    .with_store_version(previous.store_version)
1297    .with_row_count(previous.row_count)
1298    .with_totals(previous.totals.clone())
1299}
1300
1301#[cfg(test)]
1302mod tests {
1303    use std::{panic, time::Instant};
1304
1305    use super::{super::ProjectionEngine, super::SortDirection, *};
1306    use crate::{
1307        highlight::HighlightPolicy,
1308        model::{CellValue, ColumnIx, ColumnSet, GridColumns},
1309        quick_filter::{ContainsAllTerms, QuickFilter},
1310        store::{Ingest, Op},
1311    };
1312
1313    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1314    enum Column {
1315        Group,
1316        Price,
1317        Name,
1318    }
1319
1320    impl GridColumns for Column {
1321        fn all() -> &'static [Self] {
1322            &[Self::Group, Self::Price, Self::Name]
1323        }
1324
1325        fn id(self) -> &'static str {
1326            match self {
1327                Self::Group => "group",
1328                Self::Price => "price",
1329                Self::Name => "name",
1330            }
1331        }
1332
1333        fn ix(self) -> ColumnIx {
1334            ColumnIx::new(self as u16)
1335        }
1336    }
1337
1338    /// Few distinct values per column, so sort keys tie often.
1339    struct Node {
1340        id: u32,
1341        parent: Option<u32>,
1342        group: i64,
1343        price: f64,
1344        name: &'static str,
1345    }
1346
1347    impl Node {
1348        /// A row of a random forest of `count` keys: roots, children of
1349        /// earlier keys, self-parents, orphans and any key (cycles).
1350        fn random(rng: &mut fastrand::Rng, id: u32, count: u32) -> Self {
1351            let parent = match rng.u8(0..20) {
1352                0..=2 => None,
1353                3 => Some(id),
1354                4 => Some(count + rng.u32(0..10)),
1355                5 | 6 => Some(rng.u32(0..count.max(1))),
1356                _ => Some(rng.u32(0..id.max(1))),
1357            };
1358            Self {
1359                id,
1360                parent,
1361                group: rng.i64(0..4),
1362                price: [0.5, 1.0, f64::NAN, -0.0][rng.usize(..4)],
1363                name: ["", "a", "ab", "b", "я"][rng.usize(..5)],
1364            }
1365        }
1366    }
1367
1368    impl GridRow for Node {
1369        type Key = u32;
1370        type Column = Column;
1371
1372        fn key(&self) -> &u32 {
1373            &self.id
1374        }
1375
1376        fn value(&self, column: Column) -> CellValue<'_> {
1377            match column {
1378                Column::Group => CellValue::Int(self.group),
1379                Column::Price => CellValue::Float(self.price),
1380                Column::Name => CellValue::Text(self.name),
1381            }
1382        }
1383
1384        fn parent_key(&self) -> Option<&u32> {
1385            assert_ne!(self.id, u32::MAX, "parent_key failed");
1386            self.parent.as_ref()
1387        }
1388    }
1389
1390    /// A forest of `count` rows written in a random order, with some removed
1391    /// and written again, so slots are reused and children often come
1392    /// before their parents. Removed rows fade with `fade`.
1393    fn random_snapshot(
1394        rng: &mut fastrand::Rng,
1395        count: u32,
1396        fade: bool,
1397    ) -> Arc<StoreSnapshot<Node>> {
1398        let epoch = Instant::now();
1399        let policy = if fade {
1400            HighlightPolicy::default().with_removal_fade(std::time::Duration::from_secs(3_600))
1401        } else {
1402            HighlightPolicy::default()
1403        };
1404        let mut ingest = Ingest::<Node>::new(policy, epoch);
1405        let mut ids: Vec<u32> = (0..count).collect();
1406        rng.shuffle(&mut ids);
1407        ingest.apply(
1408            ids.iter()
1409                .map(|&id| Op::UpsertSilent(Arc::new(Node::random(rng, id, count)))),
1410            epoch,
1411        );
1412        ingest.apply(
1413            (0..count / 4).map(|_| Op::Remove(rng.u32(0..count.max(1)))),
1414            epoch,
1415        );
1416        ingest.apply(
1417            (0..count / 4).map(|_| {
1418                let id = rng.u32(0..count.max(1));
1419                Op::UpsertSilent(Arc::new(Node::random(rng, id, count)))
1420            }),
1421            epoch,
1422        );
1423        ingest.snapshot()
1424    }
1425
1426    /// A rule that shows rows that pass and their whole subtrees.
1427    struct ShowsSubtree;
1428
1429    impl TreeFilterRule for ShowsSubtree {
1430        fn is_shown(&self, row: &TreeFilterContext) -> bool {
1431            row.passes() || row.has_passing_descendant() || row.has_passing_ancestor()
1432        }
1433    }
1434
1435    fn random_spec(rng: &mut fastrand::Rng) -> ViewSpec<Node> {
1436        let columns = Column::all();
1437        let direction = |rng: &mut fastrand::Rng| {
1438            [SortDirection::Ascending, SortDirection::Descending][rng.usize(..2)]
1439        };
1440        let sort: Vec<_> = (0..rng.usize(0..=2))
1441            .map(|_| (columns[rng.usize(..columns.len())], direction(rng)))
1442            .collect();
1443        let filter = [
1444            TreeFilter::KeepAncestors,
1445            TreeFilter::HideSubtree,
1446            TreeFilter::Custom(Arc::new(ShowsSubtree)),
1447        ][rng.usize(..3)]
1448        .clone();
1449        let mut name = ColumnSet::EMPTY;
1450        name.insert(Column::Name.ix());
1451        let text = ["", "a", "b", "я"][rng.usize(..4)];
1452        let depth = [
1453            ExpandDepth::All,
1454            ExpandDepth::Levels(0),
1455            ExpandDepth::Levels(1),
1456            ExpandDepth::Levels(2),
1457        ][rng.usize(..4)];
1458        let toggled: Vec<u32> = (0..rng.usize(0..8)).map(|_| rng.u32(0..300)).collect();
1459        let spec = ViewSpec::new()
1460            .with_sort(sort)
1461            .with_quick_filter(QuickFilter::new(text, name, &ContainsAllTerms))
1462            .with_tree(TreeSpec::new().with_filter(filter))
1463            .with_tree_expansion(depth, toggled);
1464        if rng.bool() {
1465            spec.with_filter(ColumnSet::first(columns.len()), |node: &Node| {
1466                node.group != 1
1467            })
1468        } else {
1469            spec
1470        }
1471    }
1472
1473    /// Everything a tree projection shows, to compare two of them.
1474    type Shape = (
1475        Vec<RowId>,
1476        Vec<DisplayRow>,
1477        Vec<Option<Branch>>,
1478        usize,
1479        usize,
1480    );
1481
1482    fn shape(projection: &Projection) -> Shape {
1483        (
1484            projection.rows().to_vec(),
1485            (0..projection.len())
1486                .filter_map(|ix| projection.line(ix))
1487                .collect(),
1488            projection
1489                .rows()
1490                .iter()
1491                .map(|&id| projection.branch(id))
1492                .collect(),
1493            projection.tree_cycles(),
1494            projection.row_count(),
1495        )
1496    }
1497
1498    #[test]
1499    fn test_parallel_tree_matches_sequential() {
1500        // Small chunks and odd thread counts put chunk and run boundaries
1501        // everywhere: the tree must not depend on them. Removed rows fade
1502        // in odd seeds.
1503        let mut fading = 0;
1504        for seed in 0..40 {
1505            let mut rng = fastrand::Rng::with_seed(seed);
1506            let count = [0, 1, 2, 7, 100, 1_000, 3_000][rng.usize(..7)];
1507            let snapshot = random_snapshot(&mut rng, count, seed % 2 == 1);
1508            for _ in 0..6 {
1509                let spec = random_spec(&mut rng);
1510                let Structure::Tree(tree) = spec.structure() else {
1511                    unreachable!("a tree view")
1512                };
1513                let sequential = build(&snapshot, &spec, tree, parallelism(0, 1));
1514                let parallelism = Parallelism {
1515                    threads: rng.usize(2..=7),
1516                    chunk_rows: rng.usize(1..=97),
1517                };
1518                let parallel = build(&snapshot, &spec, tree, parallelism);
1519                assert!(parallel.is_tree());
1520                assert_eq!(
1521                    shape(&parallel),
1522                    shape(&sequential),
1523                    "seed {seed}, {count} rows, {parallelism:?}, sort {:?}, {:?}",
1524                    spec.sort(),
1525                    tree.filter(),
1526                );
1527                fading += parallel.rows().len() - parallel.row_count();
1528            }
1529        }
1530        assert!(fading > 100, "{fading} fading rows");
1531    }
1532
1533    /// A rule that fails on a row with a single shown descendant.
1534    struct Failing;
1535
1536    impl TreeFilterRule for Failing {
1537        fn is_shown(&self, row: &TreeFilterContext) -> bool {
1538            assert!(row.ancestors_pass(), "tree filter failed");
1539            row.passes()
1540        }
1541    }
1542
1543    #[test]
1544    fn test_tree_panic_resumes() {
1545        // A panicking `parent_key` on a worker thread and a panicking rule
1546        // fail the rebuild on the calling thread with their own messages.
1547        let epoch = Instant::now();
1548        let mut rng = fastrand::Rng::with_seed(5);
1549        let mut ingest = Ingest::<Node>::new(HighlightPolicy::default(), epoch);
1550        ingest.apply(
1551            (0..2_000).map(|id| Op::UpsertSilent(Arc::new(Node::random(&mut rng, id, 2_000)))),
1552            epoch,
1553        );
1554        let mut failing = Node::random(&mut rng, 0, 1);
1555        failing.id = u32::MAX;
1556        ingest.apply([Op::UpsertSilent(Arc::new(failing))], epoch);
1557        let snapshot = ingest.snapshot();
1558        let parallelism = Parallelism {
1559            threads: 4,
1560            chunk_rows: 16,
1561        };
1562        let message = |panic: Box<dyn std::any::Any + Send>| {
1563            panic
1564                .downcast_ref::<String>()
1565                .cloned()
1566                .or_else(|| panic.downcast_ref::<&str>().map(|text| (*text).to_owned()))
1567                .unwrap_or_default()
1568        };
1569        let spec = ViewSpec::new().with_tree(TreeSpec::new());
1570        let panic = panic::catch_unwind(panic::AssertUnwindSafe(|| {
1571            build(&snapshot, &spec, &TreeSpec::new(), parallelism)
1572        }))
1573        .expect_err("parent_key panics");
1574        assert!(message(panic).contains("parent_key failed"));
1575
1576        let mut ingest = Ingest::<Node>::new(HighlightPolicy::default(), epoch);
1577        ingest.apply(
1578            (0..2_000).map(|id| {
1579                Op::UpsertSilent(Arc::new(Node {
1580                    id,
1581                    parent: id.checked_sub(1),
1582                    group: 0,
1583                    price: 1.0,
1584                    name: "a",
1585                }))
1586            }),
1587            epoch,
1588        );
1589        let snapshot = ingest.snapshot();
1590        let tree = TreeSpec::new().with_filter(TreeFilter::Custom(Arc::new(Failing)));
1591        let spec = ViewSpec::new()
1592            .with_filter(ColumnSet::first(3), |node: &Node| node.id % 2 == 0)
1593            .with_tree(tree.clone());
1594        let panic = panic::catch_unwind(panic::AssertUnwindSafe(|| {
1595            build(&snapshot, &spec, &tree, parallelism)
1596        }))
1597        .expect_err("the rule panics");
1598        assert!(message(panic).contains("tree filter failed"));
1599    }
1600
1601    #[test]
1602    fn test_cycle_warning_once() {
1603        // The warning is written when the count of rows in cycles changes,
1604        // not on every rebuild, and not for a tree without cycles.
1605        let last = AtomicUsize::new(0);
1606        let warnings: Vec<bool> = [0, 2, 2, 2, 3, 0, 0, 3, 3]
1607            .into_iter()
1608            .map(|cycles| is_new_cycle_count(&last, cycles))
1609            .collect();
1610        assert_eq!(
1611            warnings,
1612            [false, true, false, false, true, false, false, true, false]
1613        );
1614    }
1615
1616    /// A rule that counts the roots alone.
1617    struct Roots;
1618
1619    impl TreeTotalsRule for Roots {
1620        fn is_counted(&self, row: &TreeTotalsContext) -> bool {
1621            row.depth() == 0
1622        }
1623    }
1624
1625    #[test]
1626    fn test_tree_totals_rows() {
1627        // 0 ─┬─ 1 ─── 3 "b"
1628        //    └─ 2 "b"
1629        // 4 "b"
1630        // 5 ───── 6
1631        let epoch = Instant::now();
1632        let mut ingest = Ingest::<Node>::new(HighlightPolicy::default(), epoch);
1633        let node = |id, parent, name| {
1634            Op::UpsertSilent(Arc::new(Node {
1635                id,
1636                parent,
1637                group: 0,
1638                price: 1.0,
1639                name,
1640            }))
1641        };
1642        ingest.apply(
1643            [
1644                node(0, None, ""),
1645                node(1, Some(0), ""),
1646                node(2, Some(0), "b"),
1647                node(3, Some(1), "b"),
1648                node(4, None, "b"),
1649                node(5, None, ""),
1650                node(6, Some(5), ""),
1651            ],
1652            epoch,
1653        );
1654        let snapshot = ingest.snapshot();
1655        let keys = |projection: &Projection, totals: &TreeTotals| -> Vec<u32> {
1656            let tree = projection.tree_data().expect("a tree");
1657            let live: Vec<Option<&Node>> = projection
1658                .rows()
1659                .iter()
1660                .map(|&id| snapshot.row(id).map(|row| &**row))
1661                .collect();
1662            tree.counted(totals, &live).map(|row| row.id).collect()
1663        };
1664        let engine = Rebuild::default();
1665        let all = engine.rebuild(&snapshot, &ViewSpec::new().with_tree(TreeSpec::new()));
1666        assert_eq!(keys(&all, &TreeTotals::Leaves), [3, 2, 4, 6]);
1667        assert_eq!(keys(&all, &TreeTotals::AllRows), [0, 1, 3, 2, 4, 5, 6]);
1668        assert_eq!(keys(&all, &TreeTotals::Custom(Arc::new(Roots))), [0, 4, 5]);
1669
1670        // Filtered: a row whose matches are all filtered out is a leaf, and
1671        // a rule tells the rows kept for context.
1672        let mut name = ColumnSet::EMPTY;
1673        name.insert(Column::Name.ix());
1674        let filtered = engine.rebuild(
1675            &snapshot,
1676            &ViewSpec::new()
1677                .with_quick_filter(QuickFilter::new("b", name, &ContainsAllTerms))
1678                .with_tree(TreeSpec::new()),
1679        );
1680        assert_eq!(keys(&filtered, &TreeTotals::Leaves), [3, 2, 4]);
1681        struct Passing;
1682        impl TreeTotalsRule for Passing {
1683            fn is_counted(&self, row: &TreeTotalsContext) -> bool {
1684                row.passes() && !row.is_branch()
1685            }
1686        }
1687        assert_eq!(
1688            keys(&filtered, &TreeTotals::Custom(Arc::new(Passing))),
1689            [3, 2, 4]
1690        );
1691        assert_eq!(keys(&filtered, &TreeTotals::AllRows), [0, 1, 3, 2, 4]);
1692        // The depth of every row, for the export of everything.
1693        let tree = filtered.tree_data().unwrap();
1694        let depths: Vec<u16> = (0..filtered.rows().len())
1695            .map(|ix| tree.depth(ix))
1696            .collect();
1697        assert_eq!(depths, [0, 1, 2, 1, 0]);
1698    }
1699}