Skip to content

Commit 094c1e4

Browse files
committed
Changes to use ControlFlow trait for visitors
- Changed query functions to use visitor and ControlFlow trait rather than using a FnMut trait returning a bool - Traits are implemented for FnMut and () to allow for simple use with closures - Idea taken from petgraph
1 parent 48dd239 commit 094c1e4

File tree

7 files changed

+176
-59
lines changed

7 files changed

+176
-59
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ Fast static spatial index data structure for 2D axis aligned bounding boxes util
3131
assert_eq!(query_results, vec![1]);
3232
// the query may also be done with a visiting function that can stop the query early
3333
let mut visited_results: Vec<usize> = Vec::new();
34-
let mut visitor = |box_added_pos: usize| -> bool {
34+
let mut visitor = |box_added_pos: usize| -> Control<()> {
3535
visited_results.push(box_added_pos);
36-
// return true to continue visiting results, false to stop early
37-
true
36+
// return continue to continue visiting results, break to stop early
37+
Control::Continue
3838
};
3939

4040
index.visit_query(-1.0, -1.0, -0.5, -0.5, &mut visitor);

benches/bench_static_aabb2d_index.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ fn bench_visit_query(b: &mut Bencher, index: &StaticAABB2DIndex<f64>) {
9191
b.max_y + delta,
9292
&mut |index: usize| {
9393
query_results.push(index);
94-
true
9594
},
9695
);
9796
}
@@ -150,7 +149,6 @@ fn bench_visit_query_reuse_stack(b: &mut Bencher, index: &StaticAABB2DIndex<f64>
150149
b.max_y + delta,
151150
&mut |index: usize| {
152151
query_results.push(index);
153-
true
154152
},
155153
&mut stack,
156154
);

examples/build_and_query.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ fn main() {
2020
assert_eq!(query_results, vec![1]);
2121
// the query may also be done with a visiting function that can stop the query early
2222
let mut visited_results: Vec<usize> = Vec::new();
23-
let mut visitor = |box_added_pos: usize| -> bool {
23+
let mut visitor = |box_added_pos: usize| {
2424
visited_results.push(box_added_pos);
25-
// return true to continue visiting results, false to stop early
26-
true
2725
};
2826

2927
index.visit_query(-1.0, -1.0, -0.5, -0.5, &mut visitor);

src/core.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ impl IndexableNum for f32 {}
3838
impl IndexableNum for f64 {}
3939

4040
/// Simple 2D axis aligned bounding box which holds the extents of a 2D box.
41+
#[allow(clippy::upper_case_acronyms)]
4142
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
4243
pub struct AABB<T = f64> {
4344
/// Min x extent of the axis aligned bounding box.
@@ -132,3 +133,121 @@ where
132133
self.min_x <= min_x && self.min_y <= min_y && self.max_x >= max_x && self.max_y >= max_y
133134
}
134135
}
136+
137+
/// Basic control flow enum that can be used when visiting query results.
138+
#[derive(Debug)]
139+
pub enum Control<B> {
140+
/// Indicates to the query function to continue visiting results.
141+
Continue,
142+
/// Indicates to the query function to stop visiting results and return a value.
143+
Break(B),
144+
}
145+
146+
impl<B> Default for Control<B> {
147+
fn default() -> Self {
148+
Control::Continue
149+
}
150+
}
151+
152+
/// Trait for control flow inside query functions.
153+
pub trait ControlFlow {
154+
/// Constructs state indicating to continue.
155+
fn continuing() -> Self;
156+
/// Should return true if control flow should break.
157+
fn should_break(&self) -> bool;
158+
}
159+
160+
impl<B> ControlFlow for Control<B> {
161+
#[inline]
162+
fn continuing() -> Self {
163+
Control::Continue
164+
}
165+
166+
#[inline]
167+
fn should_break(&self) -> bool {
168+
matches!(*self, Control::Break(_))
169+
}
170+
}
171+
172+
impl ControlFlow for () {
173+
#[inline]
174+
fn continuing() -> Self {}
175+
176+
#[inline]
177+
fn should_break(&self) -> bool {
178+
false
179+
}
180+
}
181+
182+
impl<C, E> ControlFlow for Result<C, E>
183+
where
184+
C: ControlFlow,
185+
{
186+
fn continuing() -> Self {
187+
Ok(C::continuing())
188+
}
189+
190+
fn should_break(&self) -> bool {
191+
matches!(self, Err(_))
192+
}
193+
}
194+
195+
/// Visitor trait used to visit the results of a StaticAABB2DIndex query.
196+
///
197+
/// This trait is blanket implemented for FnMut(usize) -> impl ControlFlow.
198+
pub trait QueryVisitor<T, C>
199+
where
200+
T: IndexableNum,
201+
C: ControlFlow,
202+
{
203+
/// Visit the index position of AABB returned by query.
204+
fn visit(&mut self, index_pos: usize) -> C;
205+
}
206+
207+
impl<T, C, F> QueryVisitor<T, C> for F
208+
where
209+
T: IndexableNum,
210+
C: ControlFlow,
211+
F: FnMut(usize) -> C,
212+
{
213+
#[inline]
214+
fn visit(&mut self, index_pos: usize) -> C {
215+
self(index_pos)
216+
}
217+
}
218+
219+
/// Visitor trait used to visit the results of a StaticAABB2DIndex nearest neighbors query.
220+
pub trait NeighborVisitor<T, C>
221+
where
222+
T: IndexableNum,
223+
C: ControlFlow,
224+
{
225+
/// Visits the result containing the index position of the AABB neighbor and its euclidean
226+
/// distance squared to the nearest neighbor input.
227+
fn visit(&mut self, index_pos: usize, dist_squared: T) -> C;
228+
}
229+
230+
impl<T, C, F> NeighborVisitor<T, C> for F
231+
where
232+
T: IndexableNum,
233+
C: ControlFlow,
234+
F: FnMut(usize, T) -> C,
235+
{
236+
#[inline]
237+
fn visit(&mut self, index_pos: usize, dist_squared: T) -> C {
238+
self(index_pos, dist_squared)
239+
}
240+
}
241+
242+
#[macro_export]
243+
macro_rules! try_control {
244+
($e:expr) => {
245+
match $e {
246+
x => {
247+
if x.should_break() {
248+
return x;
249+
}
250+
}
251+
}
252+
};
253+
}

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929
//! assert_eq!(query_results, vec![1]);
3030
//! // the query may also be done with a visiting function that can stop the query early
3131
//! let mut visited_results: Vec<usize> = Vec::new();
32-
//! let mut visitor = |box_added_pos: usize| -> bool {
32+
//! let mut visitor = |box_added_pos: usize| -> Control<()> {
3333
//! visited_results.push(box_added_pos);
34-
//! // return true to continue visiting results, false to stop early
35-
//! true
34+
//! // return continue to continue visiting results, break to stop early
35+
//! Control::Continue
3636
//! };
3737
//!
3838
//! index.visit_query(-1.0, -1.0, -0.5, -0.5, &mut visitor);

src/static_aabb2d_index.rs

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55
collections::BinaryHeap,
66
};
77

8-
use crate::{IndexableNum, AABB};
8+
use crate::{try_control, ControlFlow, IndexableNum, NeighborVisitor, QueryVisitor, AABB};
99

1010
/// Error type for errors that may be returned in attempting to build the index.
1111
#[derive(Debug, PartialEq)]
@@ -98,10 +98,10 @@ where
9898
/// assert_eq!(query_results, vec![1]);
9999
/// // the query may also be done with a visiting function that can stop the query early
100100
/// let mut visited_results: Vec<usize> = Vec::new();
101-
/// let mut visitor = |box_added_pos: usize| -> bool {
101+
/// let mut visitor = |box_added_pos: usize| -> Control<()> {
102102
/// visited_results.push(box_added_pos);
103-
/// // return true to continue visiting results, false to stop early
104-
/// true
103+
/// // return continue to continue visiting results, break to stop early
104+
/// Control::Continue
105105
/// };
106106
///
107107
/// index.visit_query(-1.0, -1.0, -0.5, -0.5, &mut visitor);
@@ -180,9 +180,7 @@ where
180180

181181
let mut n = num_items;
182182
let mut num_nodes = num_items;
183-
let mut level_bounds: Vec<usize> = Vec::new();
184-
185-
level_bounds.push(n);
183+
let mut level_bounds: Vec<usize> = vec![n];
186184

187185
// calculate the total number of nodes in the R-tree to allocate space for
188186
// and the index of each tree level (level_bounds, used in search later)
@@ -851,7 +849,6 @@ where
851849
let mut results = Vec::new();
852850
let mut visitor = |i| {
853851
results.push(i);
854-
true
855852
};
856853
self.visit_query(min_x, min_y, max_x, max_y, &mut visitor);
857854
results
@@ -900,11 +897,12 @@ where
900897

901898
/// Same as [StaticAABB2DIndex::query] but instead of returning a collection of indexes a
902899
/// `visitor` function is called for each index that would be returned. The `visitor` returns a
903-
/// bool indicating whether to continue visiting (true) or not (false).
900+
/// control flow indicating whether to continue visiting or break.
904901
#[inline]
905-
pub fn visit_query<F>(&self, min_x: T, min_y: T, max_x: T, max_y: T, visitor: &mut F)
902+
pub fn visit_query<V, C>(&self, min_x: T, min_y: T, max_x: T, max_y: T, visitor: &mut V)
906903
where
907-
F: FnMut(usize) -> bool,
904+
C: ControlFlow,
905+
V: QueryVisitor<T, C>,
908906
{
909907
let mut stack: Vec<usize> = Vec::with_capacity(16);
910908
self.visit_query_with_stack(min_x, min_y, max_x, max_y, visitor, &mut stack);
@@ -974,7 +972,6 @@ where
974972
let mut results = Vec::new();
975973
let mut visitor = |i| {
976974
results.push(i);
977-
true
978975
};
979976
self.visit_query_with_stack(min_x, min_y, max_x, max_y, &mut visitor, stack);
980977
results
@@ -983,23 +980,25 @@ where
983980
/// Same as [StaticAABB2DIndex::visit_query] but accepts an existing [Vec] to be used as a stack
984981
/// buffer when performing the query to avoid the need for allocation (this is for performance
985982
/// benefit only).
986-
pub fn visit_query_with_stack<F>(
983+
pub fn visit_query_with_stack<V, C>(
987984
&self,
988985
min_x: T,
989986
min_y: T,
990987
max_x: T,
991988
max_y: T,
992-
visitor: &mut F,
989+
visitor: &mut V,
993990
stack: &mut Vec<usize>,
994-
) where
995-
F: FnMut(usize) -> bool,
991+
) -> C
992+
where
993+
C: ControlFlow,
994+
V: QueryVisitor<T, C>,
996995
{
997996
let mut node_index = self.boxes.len() - 1;
998997
let mut level = self.level_bounds.len() - 1;
999998
// ensure the stack is empty for use
1000999
stack.clear();
10011000

1002-
'search_loop: loop {
1001+
loop {
10031002
let end = min(
10041003
node_index + self.node_size,
10051004
*get_at_index!(self.level_bounds, level),
@@ -1014,9 +1013,7 @@ where
10141013

10151014
let index = *get_at_index!(self.indices, pos);
10161015
if node_index < self.num_items {
1017-
if !visitor(index) {
1018-
break 'search_loop;
1019-
}
1016+
try_control!(visitor.visit(index))
10201017
} else {
10211018
stack.push(index);
10221019
stack.push(level - 1);
@@ -1027,17 +1024,16 @@ where
10271024
level = stack.pop().unwrap();
10281025
node_index = stack.pop().unwrap();
10291026
} else {
1030-
break 'search_loop;
1027+
return C::continuing();
10311028
}
10321029
}
10331030
}
10341031

10351032
/// Visit all neighboring items in order of minimum euclidean distance to the point defined by
1036-
/// `x` and `y` until `visitor` returns false.
1033+
/// `x` and `y` until `visitor` breaks or all items have been visited.
10371034
///
10381035
/// ## Notes
1039-
/// * The visitor function must return false to stop visiting items or all items will be
1040-
/// visited.
1036+
/// * The visitor function must break to stop visiting items or all items will be visited.
10411037
/// * The visitor function receives the index of the item being visited and the squared
10421038
/// euclidean distance to that item from the point given.
10431039
/// * Because distances are squared (`dx * dx + dy * dy`) be cautious of smaller numeric types
@@ -1046,24 +1042,27 @@ where
10461042
/// * If repeatedly calling this method then [StaticAABB2DIndex::visit_neighbors_with_queue] can
10471043
/// be used to avoid repeated allocations for the priority queue used internally.
10481044
#[inline]
1049-
pub fn visit_neighbors<F>(&self, x: T, y: T, visitor: &mut F)
1045+
pub fn visit_neighbors<V, C>(&self, x: T, y: T, visitor: &mut V)
10501046
where
1051-
F: FnMut(usize, T) -> bool,
1047+
C: ControlFlow,
1048+
V: NeighborVisitor<T, C>,
10521049
{
10531050
let mut queue = NeighborPriorityQueue::new();
10541051
self.visit_neighbors_with_queue(x, y, visitor, &mut queue);
10551052
}
10561053

10571054
/// Works the same as [StaticAABB2DIndex::visit_neighbors] but accepts an existing binary heap
10581055
/// to be used as a priority queue to avoid allocations.
1059-
pub fn visit_neighbors_with_queue<F>(
1056+
pub fn visit_neighbors_with_queue<V, C>(
10601057
&self,
10611058
x: T,
10621059
y: T,
1063-
visitor: &mut F,
1060+
visitor: &mut V,
10641061
queue: &mut NeighborPriorityQueue<T>,
1065-
) where
1066-
F: FnMut(usize, T) -> bool,
1062+
) -> C
1063+
where
1064+
C: ControlFlow,
1065+
V: NeighborVisitor<T, C>,
10671066
{
10681067
// small helper function to compute axis distance between point and bounding box axis
10691068
fn axis_dist<U>(k: U, min: U, max: U) -> U
@@ -1082,7 +1081,7 @@ where
10821081
let mut node_index = self.boxes.len() - 1;
10831082
queue.clear();
10841083

1085-
'search_loop: loop {
1084+
loop {
10861085
let upper_bound_level_index = match self.level_bounds.binary_search(&node_index) {
10871086
// level bound found, add one to get upper bound
10881087
Ok(i) => i + 1,
@@ -1113,10 +1112,7 @@ where
11131112
while let Some(state) = queue.pop() {
11141113
if state.is_leaf_node {
11151114
// visit leaf node
1116-
if !visitor(state.index, state.dist) {
1117-
// stop visiting if visitor returns false
1118-
break 'search_loop;
1119-
}
1115+
try_control!(visitor.visit(state.index, state.dist))
11201116
} else {
11211117
// update node index for next iteration
11221118
node_index = state.index;
@@ -1127,7 +1123,7 @@ where
11271123
}
11281124

11291125
if !continue_search {
1130-
break 'search_loop;
1126+
return C::continuing();
11311127
}
11321128
}
11331129
}

0 commit comments

Comments
 (0)