mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-05-20 06:58:16 +02:00
Recursion in Rust can be annoyingly expensive. The recursive DFS implementation we have now is probably not terrible but these things bloat in debug builds more than you'd expect. This replaceas it with a non-recursive implementation which uses a Vec instead. Reviewed-by: Mel Henning <mhenning@darkrefraction.com> Reviewed-by: Lorenzo Rossi <git@rossilorenzo.dev> Reviewed-by: Mary Guillemard <mary@mary.zone> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/37536>
78 lines
2.1 KiB
Rust
78 lines
2.1 KiB
Rust
// Copyright © 2025 Collabora, Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
use crate::bitset::BitSet;
|
|
|
|
/// A trait implementing a depth-first search over a graph
|
|
pub trait DepthFirstSearch {
|
|
type ChildIter;
|
|
|
|
/// Pre-visit a node. This is called before any children or edges are
|
|
/// visited. Returns an iterator to this node's children in the graph.
|
|
fn pre(&mut self, id: usize) -> Self::ChildIter;
|
|
|
|
/// Visit an edge. An edge is visited before the child at the end of that
|
|
/// edge is pre-visited. Every edge is visited, even if if the node this
|
|
/// edge points to has already been visited.
|
|
fn edge(&mut self, _parent: usize, _child: usize) {
|
|
// Does nothing by default
|
|
}
|
|
|
|
/// Post-visit a node. This is called after all the children have been
|
|
/// visited.
|
|
fn post(&mut self, _id: usize) {
|
|
// Does nothing by default
|
|
}
|
|
}
|
|
|
|
struct DFSEntry<I: Iterator<Item = usize>> {
|
|
id: usize,
|
|
children: I,
|
|
}
|
|
|
|
pub fn dfs<I, D>(dfs: &mut D, start: usize)
|
|
where
|
|
I: Iterator<Item = usize>,
|
|
D: DepthFirstSearch<ChildIter = I>,
|
|
{
|
|
let mut seen = BitSet::new();
|
|
let mut stack = Vec::new();
|
|
|
|
seen.insert(start);
|
|
let children = dfs.pre(start);
|
|
|
|
stack.push(DFSEntry {
|
|
id: start,
|
|
children,
|
|
});
|
|
loop {
|
|
let Some(entry) = stack.last_mut() else {
|
|
break;
|
|
};
|
|
|
|
loop {
|
|
if let Some(id) = entry.children.next() {
|
|
dfs.edge(entry.id, id);
|
|
|
|
if seen.contains(id) {
|
|
continue;
|
|
}
|
|
|
|
seen.insert(id);
|
|
let children = dfs.pre(id);
|
|
|
|
stack.push(DFSEntry { id, children });
|
|
} else {
|
|
dfs.post(entry.id);
|
|
stack.pop();
|
|
}
|
|
|
|
// We're only looping here because we want to make the
|
|
// seen.contains() case a fast path. Both the case where we
|
|
// push the stack and recurse into a child or when we pop the
|
|
// stack want to continue in the next iteration of the outer
|
|
// loop.
|
|
break;
|
|
}
|
|
}
|
|
}
|