Skip to content

Commit 01f24bd

Browse files
authored
refactor: Make consumer tree traversals iterative (#717)
1 parent e7299a7 commit 01f24bd

2 files changed

Lines changed: 207 additions & 98 deletions

File tree

consumer/src/node.rs

Lines changed: 172 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use accesskit::{
1414
};
1515
use alloc::{
1616
string::{String, ToString},
17+
vec,
1718
vec::Vec,
1819
};
1920
use core::{fmt, iter::FusedIterator};
@@ -121,13 +122,13 @@ impl<'a> Node<'a> {
121122
}
122123

123124
pub fn filtered_parent(&self, filter: &impl Fn(&Node) -> FilterResult) -> Option<Node<'a>> {
124-
self.parent().and_then(move |parent| {
125-
if filter(&parent) == FilterResult::Include {
126-
Some(parent)
127-
} else {
128-
parent.filtered_parent(filter)
125+
let mut current = self.parent()?;
126+
loop {
127+
if filter(&current) == FilterResult::Include {
128+
return Some(current);
129129
}
130-
})
130+
current = current.parent()?;
131+
}
131132
}
132133

133134
pub fn parent_and_index(self) -> Option<(Node<'a>, usize)> {
@@ -283,13 +284,16 @@ impl<'a> Node<'a> {
283284
}
284285

285286
pub fn is_descendant_of(&self, ancestor: &Node) -> bool {
286-
if self.id() == ancestor.id() {
287-
return true;
288-
}
289-
if let Some(parent) = self.parent() {
290-
return parent.is_descendant_of(ancestor);
287+
let mut current = *self;
288+
loop {
289+
if current.id() == ancestor.id() {
290+
return true;
291+
}
292+
match current.parent() {
293+
Some(parent) => current = parent,
294+
None => return false,
295+
}
291296
}
292-
false
293297
}
294298

295299
/// Returns the transform defined directly on this node, or the identity
@@ -303,22 +307,26 @@ impl<'a> Node<'a> {
303307
/// Returns the combined affine transform of this node and its ancestors,
304308
/// up to and including the root of this node's tree.
305309
pub fn transform(&self) -> Affine {
306-
self.parent()
307-
.map_or(Affine::IDENTITY, |parent| parent.transform())
308-
* self.direct_transform()
310+
let mut acc = self.direct_transform();
311+
let mut current = *self;
312+
while let Some(parent) = current.parent() {
313+
acc = parent.direct_transform() * acc;
314+
current = parent;
315+
}
316+
acc
309317
}
310318

311319
pub(crate) fn relative_transform(&self, stop_at: &Node) -> Affine {
312-
let parent_transform = if let Some(parent) = self.parent() {
320+
let mut acc = self.direct_transform();
321+
let mut current = *self;
322+
while let Some(parent) = current.parent() {
313323
if parent.id() == stop_at.id() {
314-
Affine::IDENTITY
315-
} else {
316-
parent.relative_transform(stop_at)
324+
break;
317325
}
318-
} else {
319-
Affine::IDENTITY
320-
};
321-
parent_transform * self.direct_transform()
326+
acc = parent.direct_transform() * acc;
327+
current = parent;
328+
}
329+
acc
322330
}
323331

324332
pub fn raw_bounds(&self) -> Option<Rect> {
@@ -348,23 +356,37 @@ impl<'a> Node<'a> {
348356
point: Point,
349357
filter: &impl Fn(&Node) -> FilterResult,
350358
) -> Option<(Node<'a>, Point)> {
351-
let filter_result = filter(self);
352-
353-
if filter_result == FilterResult::ExcludeSubtree {
354-
return None;
359+
// A node's `Test` frame is pushed before its children, then children in
360+
// forward order, so that children are searched last-to-first and the
361+
// node's own bounds are tested only after all descendants miss.
362+
enum Frame<'n> {
363+
Visit(Node<'n>, Point),
364+
Test(Node<'n>, Point),
355365
}
356366

357-
for child in self.children().rev() {
358-
let point = child.direct_transform().inverse() * point;
359-
if let Some(result) = child.hit_test(point, filter) {
360-
return Some(result);
361-
}
362-
}
363-
364-
if filter_result == FilterResult::Include {
365-
if let Some(rect) = &self.raw_bounds() {
366-
if rect.contains(point) {
367-
return Some((*self, point));
367+
let mut stack = Vec::with_capacity(self.children().len() + 1);
368+
stack.push(Frame::Visit(*self, point));
369+
while let Some(frame) = stack.pop() {
370+
match frame {
371+
Frame::Test(node, point) => {
372+
if let Some(rect) = &node.raw_bounds() {
373+
if rect.contains(point) {
374+
return Some((node, point));
375+
}
376+
}
377+
}
378+
Frame::Visit(node, point) => {
379+
let filter_result = filter(&node);
380+
if filter_result == FilterResult::ExcludeSubtree {
381+
continue;
382+
}
383+
if filter_result == FilterResult::Include {
384+
stack.push(Frame::Test(node, point));
385+
}
386+
for child in node.children() {
387+
let child_point = child.direct_transform().inverse() * point;
388+
stack.push(Frame::Visit(child, child_point));
389+
}
368390
}
369391
}
370392
}
@@ -1000,15 +1022,16 @@ impl<'a> Node<'a> {
10001022
&self,
10011023
filter: &impl Fn(&Node) -> FilterResult,
10021024
) -> Option<Node<'a>> {
1003-
for child in self.children() {
1004-
let result = filter(&child);
1005-
if result == FilterResult::Include {
1006-
return Some(child);
1007-
}
1008-
if result == FilterResult::ExcludeNode {
1009-
if let Some(descendant) = child.first_filtered_child(filter) {
1010-
return Some(descendant);
1025+
let mut stack = vec![self.children()];
1026+
while let Some(iter) = stack.last_mut() {
1027+
if let Some(child) = iter.next() {
1028+
match filter(&child) {
1029+
FilterResult::Include => return Some(child),
1030+
FilterResult::ExcludeNode => stack.push(child.children()),
1031+
FilterResult::ExcludeSubtree => {}
10111032
}
1033+
} else {
1034+
stack.pop();
10121035
}
10131036
}
10141037
None
@@ -1018,15 +1041,16 @@ impl<'a> Node<'a> {
10181041
&self,
10191042
filter: &impl Fn(&Node) -> FilterResult,
10201043
) -> Option<Node<'a>> {
1021-
for child in self.children().rev() {
1022-
let result = filter(&child);
1023-
if result == FilterResult::Include {
1024-
return Some(child);
1025-
}
1026-
if result == FilterResult::ExcludeNode {
1027-
if let Some(descendant) = child.last_filtered_child(filter) {
1028-
return Some(descendant);
1044+
let mut stack = vec![self.children().rev()];
1045+
while let Some(iter) = stack.last_mut() {
1046+
if let Some(child) = iter.next() {
1047+
match filter(&child) {
1048+
FilterResult::Include => return Some(child),
1049+
FilterResult::ExcludeNode => stack.push(child.children().rev()),
1050+
FilterResult::ExcludeSubtree => {}
10291051
}
1052+
} else {
1053+
stack.pop();
10301054
}
10311055
}
10321056
None
@@ -1085,8 +1109,8 @@ impl<W: fmt::Write> fmt::Write for SpacePrefixingWriter<W> {
10851109
#[cfg(test)]
10861110
mod tests {
10871111
use accesskit::{
1088-
Action, Node, NodeId, Point, Rect, Role, TextDirection, TextPosition, TextSelection, Tree,
1089-
TreeId, TreeUpdate,
1112+
Action, Affine, Node, NodeId, Point, Rect, Role, TextDirection, TextPosition,
1113+
TextSelection, Tree, TreeId, TreeUpdate, Vec2,
10901114
};
10911115
use alloc::vec;
10921116

@@ -1405,6 +1429,98 @@ mod tests {
14051429
);
14061430
}
14071431

1432+
#[test]
1433+
fn first_filtered_child() {
1434+
let tree = test_tree();
1435+
assert_eq!(
1436+
Some(PARAGRAPH_0_ID),
1437+
tree.state()
1438+
.root()
1439+
.first_filtered_child(&test_tree_filter)
1440+
.map(|node| node.id().to_components().0)
1441+
);
1442+
// Descends through ExcludeNode children (EMPTY_CONTAINER_3_0 then into
1443+
// LINK_3_1) to find the first included descendant.
1444+
assert_eq!(
1445+
Some(LABEL_3_1_0_ID),
1446+
tree.state()
1447+
.node_by_id(nid(PARAGRAPH_3_IGNORED_ID))
1448+
.unwrap()
1449+
.first_filtered_child(&test_tree_filter)
1450+
.map(|node| node.id().to_components().0)
1451+
);
1452+
assert!(
1453+
tree.state()
1454+
.node_by_id(nid(LABEL_2_0_ID))
1455+
.unwrap()
1456+
.first_filtered_child(&test_tree_filter)
1457+
.is_none()
1458+
);
1459+
}
1460+
1461+
#[test]
1462+
fn last_filtered_child() {
1463+
let tree = test_tree();
1464+
// Reverse order: skips the trailing ExcludeNode container, returns the
1465+
// included button.
1466+
assert_eq!(
1467+
Some(BUTTON_3_2_ID),
1468+
tree.state()
1469+
.node_by_id(nid(PARAGRAPH_3_IGNORED_ID))
1470+
.unwrap()
1471+
.last_filtered_child(&test_tree_filter)
1472+
.map(|node| node.id().to_components().0)
1473+
);
1474+
assert!(
1475+
tree.state()
1476+
.node_by_id(nid(LABEL_2_0_ID))
1477+
.unwrap()
1478+
.last_filtered_child(&test_tree_filter)
1479+
.is_none()
1480+
);
1481+
}
1482+
1483+
#[test]
1484+
fn transform() {
1485+
let tree = test_tree();
1486+
assert_eq!(Affine::IDENTITY, tree.state().root().transform());
1487+
// PARAGRAPH_1_IGNORED defines a translation.
1488+
let expected = Affine::translate(Vec2::new(10.0, 40.0));
1489+
assert_eq!(
1490+
expected,
1491+
tree.state()
1492+
.node_by_id(nid(PARAGRAPH_1_IGNORED_ID))
1493+
.unwrap()
1494+
.transform()
1495+
);
1496+
// LABEL_1_1 inherits its parent's translation (it has none of its own).
1497+
assert_eq!(
1498+
expected,
1499+
tree.state()
1500+
.node_by_id(nid(LABEL_1_1_ID))
1501+
.unwrap()
1502+
.transform()
1503+
);
1504+
}
1505+
1506+
#[test]
1507+
fn relative_transform() {
1508+
let tree = test_tree();
1509+
let label = tree.state().node_by_id(nid(LABEL_1_1_ID)).unwrap();
1510+
let paragraph = tree
1511+
.state()
1512+
.node_by_id(nid(PARAGRAPH_1_IGNORED_ID))
1513+
.unwrap();
1514+
// Relative to its immediate (transformed) parent: identity, since the
1515+
// parent's transform is excluded.
1516+
assert_eq!(Affine::IDENTITY, label.relative_transform(&paragraph));
1517+
// Relative to the root: includes the parent's translation.
1518+
assert_eq!(
1519+
Affine::translate(Vec2::new(10.0, 40.0)),
1520+
label.relative_transform(&tree.state().root())
1521+
);
1522+
}
1523+
14081524
#[test]
14091525
fn no_label_or_labelled_by() {
14101526
let update = TreeUpdate {

consumer/src/tree.rs

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -348,29 +348,25 @@ impl State {
348348
changes: &mut Option<&mut InternalChanges>,
349349
seen_child_ids: &HashSet<NodeId>,
350350
new_tree_root: Option<NodeId>,
351-
id: NodeId,
351+
root: NodeId,
352352
) {
353-
if let Some(changes) = changes {
354-
changes.removed_node_ids.insert(id);
355-
}
356-
let node = nodes.remove(&id).unwrap();
357-
if let Some(subtree_id) = node.data.tree_id() {
358-
grafts_to_remove.insert(subtree_id);
359-
}
360-
let (_, tree_index) = id.to_components();
361-
for child_id in node.data.children().iter() {
362-
let child_node_id = NodeId::new(*child_id, tree_index);
363-
if !seen_child_ids.contains(&child_node_id)
364-
&& new_tree_root != Some(child_node_id)
365-
{
366-
traverse_unreachable(
367-
nodes,
368-
grafts_to_remove,
369-
changes,
370-
seen_child_ids,
371-
new_tree_root,
372-
child_node_id,
373-
);
353+
let mut stack = vec![root];
354+
while let Some(id) = stack.pop() {
355+
if let Some(changes) = changes {
356+
changes.removed_node_ids.insert(id);
357+
}
358+
let node = nodes.remove(&id).unwrap();
359+
if let Some(subtree_id) = node.data.tree_id() {
360+
grafts_to_remove.insert(subtree_id);
361+
}
362+
let (_, tree_index) = id.to_components();
363+
for child_id in node.data.children().iter() {
364+
let child_node_id = NodeId::new(*child_id, tree_index);
365+
if !seen_child_ids.contains(&child_node_id)
366+
&& new_tree_root != Some(child_node_id)
367+
{
368+
stack.push(child_node_id);
369+
}
374370
}
375371
}
376372
}
@@ -392,28 +388,25 @@ impl State {
392388
subtrees_to_remove: &mut Vec<TreeId>,
393389
subtrees_queued: &mut HashSet<TreeId>,
394390
changes: &mut Option<&mut InternalChanges>,
395-
id: NodeId,
391+
root: NodeId,
396392
) {
397-
let Some(node) = nodes.remove(&id) else {
398-
return;
399-
};
400-
if let Some(changes) = changes {
401-
changes.removed_node_ids.insert(id);
402-
}
403-
if let Some(nested_subtree_id) = node.data.tree_id() {
404-
if subtrees_queued.insert(nested_subtree_id) {
405-
subtrees_to_remove.push(nested_subtree_id);
393+
let mut stack = vec![root];
394+
while let Some(id) = stack.pop() {
395+
let Some(node) = nodes.remove(&id) else {
396+
continue;
397+
};
398+
if let Some(changes) = changes {
399+
changes.removed_node_ids.insert(id);
400+
}
401+
if let Some(nested_subtree_id) = node.data.tree_id() {
402+
if subtrees_queued.insert(nested_subtree_id) {
403+
subtrees_to_remove.push(nested_subtree_id);
404+
}
405+
}
406+
let (_, tree_index) = id.to_components();
407+
for child_id in node.data.children().iter() {
408+
stack.push(NodeId::new(*child_id, tree_index));
406409
}
407-
}
408-
let (_, tree_index) = id.to_components();
409-
for child_id in node.data.children().iter() {
410-
traverse_subtree(
411-
nodes,
412-
subtrees_to_remove,
413-
subtrees_queued,
414-
changes,
415-
NodeId::new(*child_id, tree_index),
416-
);
417410
}
418411
}
419412

0 commit comments

Comments
 (0)