From 94728eb52ad13785f3f653042f0467b46768867e Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Fri, 22 Mar 2024 10:44:08 -0500 Subject: [PATCH 01/13] implement quadtree --- Cargo.lock | 8 ++ Cargo.toml | 1 + quadtree/.gitignore | 1 + quadtree/Cargo.toml | 77 +++++++++++++++++ quadtree/README.md | 0 quadtree/clippy.toml | 3 + quadtree/rustfmt.toml | 21 +++++ quadtree/src/lib.rs | 178 ++++++++++++++++++++++++++++++++++++++++ quadtree/src/nearest.rs | 3 + 9 files changed, 292 insertions(+) create mode 100644 quadtree/.gitignore create mode 100644 quadtree/Cargo.toml create mode 100644 quadtree/README.md create mode 100644 quadtree/clippy.toml create mode 100644 quadtree/rustfmt.toml create mode 100644 quadtree/src/lib.rs create mode 100644 quadtree/src/nearest.rs diff --git a/Cargo.lock b/Cargo.lock index c18cad17..d62926bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1381,6 +1381,14 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quadtree" +version = "0.1.0" +dependencies = [ + "glam", + "itertools", +] + [[package]] name = "quote" version = "1.0.35" diff --git a/Cargo.toml b/Cargo.toml index 90a1488a..4962ef58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "server", + "quadtree" ] [profile.dev] diff --git a/quadtree/.gitignore b/quadtree/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/quadtree/.gitignore @@ -0,0 +1 @@ +/target diff --git a/quadtree/Cargo.toml b/quadtree/Cargo.toml new file mode 100644 index 00000000..93dc4c75 --- /dev/null +++ b/quadtree/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "quadtree" +version = "0.1.0" +edition = "2021" +authors = ["Andrew Gazelka "] +readme = "README.md" + +[dependencies] +glam = "0.26.0" +itertools = "0.12.1" + + +[lints.rust] +warnings = "deny" + +[lints.clippy] +cargo_common_metadata = "allow" +negative_feature_names = "deny" +redundant_feature_names = "deny" +wildcard_dependencies = "deny" + +restriction = { level = "deny", priority = -1 } +missing_docs_in_private_items = "allow" +question_mark_used = "allow" +print_stdout = "allow" +implicit_return = "allow" +shadow_reuse = "allow" +absolute_paths = "allow" +use_debug = "allow" +unwrap_used = "allow" +std_instead_of_alloc = "allow" # consider denying +default_numeric_fallback = "allow" +as_conversions = "allow" +arithmetic_side_effects = "allow" +shadow_unrelated = "allow" +unseparated_literal_suffix = "allow" +else_if_without_else = "allow" +float_arithmetic = "allow" +single_call_fn = "allow" +missing_inline_in_public_items = "allow" +exhaustive_structs = "allow" +pub_use = "allow" +let_underscore_untyped = "allow" +infinite_loop = "allow" +single_char_lifetime_names = "allow" +min_ident_chars = "allow" +std_instead_of_core = "allow" +let_underscore_must_use = "allow" +pattern_type_mismatch = "allow" +print_stderr = "allow" +missing_assert_message = "allow" +shadow_same = "allow" +mod_module_files = "deny" +self_named_module_files = "allow" + +complexity = { level = "deny", priority = -1 } + +nursery = { level = "deny", priority = -1 } +future_not_send = "allow" +redundant_pub_crate = "allow" + +pedantic = { level = "deny", priority = -1 } +uninlined_format_args = "allow" # consider denying; this is allowed because Copilot often generates code that triggers this lint +needless_pass_by_value = "allow" # consider denying +cast_lossless = "allow" +cast_possible_truncation = "allow" # consider denying +cast_precision_loss = "allow" # consider denying +missing_errors_doc = "allow" # consider denying +struct_excessive_bools = "allow" +wildcard_imports = "allow" + +perf = { level = "deny", priority = -1 } + +style = { level = "deny", priority = -1 } + +suspicious = { level = "deny", priority = -1 } +blanket_clippy_restriction_lints = "allow" diff --git a/quadtree/README.md b/quadtree/README.md new file mode 100644 index 00000000..e69de29b diff --git a/quadtree/clippy.toml b/quadtree/clippy.toml new file mode 100644 index 00000000..5ad7f93b --- /dev/null +++ b/quadtree/clippy.toml @@ -0,0 +1,3 @@ +# https://doc.rust-lang.org/nightly/clippy/lint_configuration.html +cognitive-complexity-threshold = 5 +excessive-nesting-threshold = 4 diff --git a/quadtree/rustfmt.toml b/quadtree/rustfmt.toml new file mode 100644 index 00000000..7073741e --- /dev/null +++ b/quadtree/rustfmt.toml @@ -0,0 +1,21 @@ +combine_control_expr = true +comment_width = 100 # https://lkml.org/lkml/2020/5/29/1038 +condense_wildcard_suffixes = true +control_brace_style = "AlwaysSameLine" +edition = "2021" +format_code_in_doc_comments = true +format_macro_bodies = true +format_macro_matchers = true +format_strings = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +merge_derives = false +newline_style = "Unix" +normalize_comments = true +normalize_doc_attributes = true +overflow_delimited_expr = true +reorder_impl_items = true +reorder_imports = true +unstable_features = true +wrap_comments = true + diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs new file mode 100644 index 00000000..4699191d --- /dev/null +++ b/quadtree/src/lib.rs @@ -0,0 +1,178 @@ +// https://lisyarus.github.io/blog/programming/2022/12/21/quadtrees.html + +// https://snorrwe.onrender.com/posts/morton-table/ + +use glam::Vec2; + +mod nearest; + +struct Aabb { + min: Vec2, + max: Vec2, +} + +impl Default for Aabb { + fn default() -> Self { + Self { + min: Vec2::splat(f32::INFINITY), + max: Vec2::splat(f32::NEG_INFINITY), + } + } +} + +impl Aabb { + fn new(min: Vec2, max: Vec2) -> Self { + Self { min, max } + } + + fn mid(&self) -> Vec2 { + (self.min + self.max) / 2.0 + } + + fn expand_to_fit(&mut self, point: Vec2) { + self.min = self.min.min(point); + self.max = self.max.max(point); + } + + fn from_points(points: &[Vec2]) -> Self { + let mut aabb = Self::default(); + + for point in points { + aabb.expand_to_fit(*point); + } + + aabb + } +} + +// primitive +// struct Node { +// children: [Option>; 4], +// } +// +// struct Quadtree { +// root: Node, +// aabb: Aabb, +// } + +struct NodeId(u32); + +const NULL_ID: u32 = u32::MAX; + +impl NodeId { + const NULL: Self = Self(NULL_ID); + + const fn is_null(&self) -> bool { + self.0 == NULL_ID + } +} + +impl From for NodeId { + fn from(value: usize) -> Self { + Self(value as u32) + } +} + +struct Node { + children: [[NodeId; 2]; 2], +} + +impl Default for Node { + fn default() -> Self { + Self { + children: [[NodeId::NULL, NodeId::NULL], [NodeId::NULL, NodeId::NULL]], + } + } +} + +struct Quadtree { + aabb: Aabb, + root: NodeId, + nodes: Vec, +} + +impl Default for Quadtree { + fn default() -> Self { + Self { + aabb: Aabb::default(), + root: NodeId::NULL, + nodes: Vec::new(), + } + } +} + +#[allow(clippy::indexing_slicing)] +fn build_impl(tree: &mut Quadtree, bbox: Aabb, points: &mut [Vec2]) -> NodeId { + if points.is_empty() { + return NodeId::NULL; + } + + let result = tree.nodes.len(); + tree.nodes.push(Node::default()); + + if points.len() == 1 { + return result.into(); + } + + let center = bbox.mid(); + + let bottom = |p: &Vec2| p.y < center.y; + let left = |p: &Vec2| p.x < center.x; + + // todo: why need to &mut points[..]? + let split_y = itertools::partition(&mut *points, bottom); + + let split_x_lower = itertools::partition(&mut points[..split_y], left); + let split_x_upper = itertools::partition(&mut points[split_y..], left); + + // let node = &mut tree.nodes[result]; + + let child00 = build_impl( + tree, + Aabb::new(bbox.min, center), + &mut points[..split_x_lower], + ); + tree.nodes[result].children[0][0] = child00; + + let child01 = build_impl( + tree, + Aabb::new( + Vec2::new(center.x, bbox.min.y), + Vec2::new(bbox.max.x, center.y), + ), + &mut points[split_x_lower..split_y], + ); + tree.nodes[result].children[0][1] = child01; + + let child10 = build_impl( + tree, + Aabb::new( + Vec2::new(bbox.min.x, center.y), + Vec2::new(center.x, bbox.max.y), + ), + &mut points[split_y..split_y + split_x_upper], + ); + tree.nodes[result].children[1][0] = child10; + + let child11 = build_impl( + tree, + Aabb::new(center, bbox.max), + &mut points[split_y + split_x_upper..], + ); + tree.nodes[result].children[1][1] = child11; + + result.into() +} + +impl Quadtree { + fn build

(points: &mut [Vec2]) -> Self { + let aabb = Aabb::from_points(points); + let mut quadtree = Self::default(); + + let root = build_impl(&mut quadtree, aabb, points); + + quadtree.root = root; + + quadtree + } +} diff --git a/quadtree/src/nearest.rs b/quadtree/src/nearest.rs new file mode 100644 index 00000000..be432529 --- /dev/null +++ b/quadtree/src/nearest.rs @@ -0,0 +1,3 @@ +// https://stackoverflow.com/a/32412425/4889030 +// +// https://homepage.divms.uiowa.edu/%7Ekvaradar/sp2012/daa/ann.pdf From ddc7332a05d57fbd225c066bc993ff95d2573a9c Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Tue, 26 Mar 2024 13:43:41 -0500 Subject: [PATCH 02/13] stash quadtree --- Cargo.lock | 10 +- generator-build/Cargo.toml | 4 +- quadtree/Cargo.toml | 3 +- quadtree/clippy.toml | 3 - quadtree/src/aaab.rs | 88 ++++++++ quadtree/src/idx.rs | 66 ++++++ quadtree/src/iter.rs | 120 +++++++++++ quadtree/src/lib.rs | 428 ++++++++++++++++++++++++++++--------- server/Cargo.toml | 2 +- 9 files changed, 617 insertions(+), 107 deletions(-) delete mode 100644 quadtree/clippy.toml create mode 100644 quadtree/src/aaab.rs create mode 100644 quadtree/src/idx.rs create mode 100644 quadtree/src/iter.rs diff --git a/Cargo.lock b/Cargo.lock index 6a72bad3..cf1b98a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -855,6 +855,12 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebe52f2c58f1eea4d6fd3307058cf818c74181435a3620502d1651db07ff018" +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + [[package]] name = "hashbrown" version = "0.14.3" @@ -1444,7 +1450,7 @@ dependencies = [ name = "quadtree" version = "0.1.0" dependencies = [ - "glam", + "glam 0.27.0", "itertools", ] @@ -2180,7 +2186,7 @@ name = "valence_math" version = "0.2.0-alpha.1+mc.1.20.1" source = "git+https://github.com/valence-rs/valence#7e91ca0f6d4608258524052bd521c5a6cbf221d4" dependencies = [ - "glam", + "glam 0.26.0", ] [[package]] diff --git a/generator-build/Cargo.toml b/generator-build/Cargo.toml index 729f29df..a238d6e7 100644 --- a/generator-build/Cargo.toml +++ b/generator-build/Cargo.toml @@ -10,11 +10,11 @@ publish = false anyhow = "1.0.81" heck = "0.5.0" itertools = "0.12.1" -prettyplease = "0.2.16" +prettyplease = "0.2.17" proc-macro2 = "1.0.79" quote = "1.0.35" serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.114" +serde_json = "1.0.115" syn = "2.0.55" [lints.rust] diff --git a/quadtree/Cargo.toml b/quadtree/Cargo.toml index 93dc4c75..4b0cd07a 100644 --- a/quadtree/Cargo.toml +++ b/quadtree/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Andrew Gazelka "] readme = "README.md" [dependencies] -glam = "0.26.0" +glam = "0.27.0" itertools = "0.12.1" @@ -52,6 +52,7 @@ missing_assert_message = "allow" shadow_same = "allow" mod_module_files = "deny" self_named_module_files = "allow" +missing_trait_methods = "allow" complexity = { level = "deny", priority = -1 } diff --git a/quadtree/clippy.toml b/quadtree/clippy.toml deleted file mode 100644 index 5ad7f93b..00000000 --- a/quadtree/clippy.toml +++ /dev/null @@ -1,3 +0,0 @@ -# https://doc.rust-lang.org/nightly/clippy/lint_configuration.html -cognitive-complexity-threshold = 5 -excessive-nesting-threshold = 4 diff --git a/quadtree/src/aaab.rs b/quadtree/src/aaab.rs new file mode 100644 index 00000000..f1e257a7 --- /dev/null +++ b/quadtree/src/aaab.rs @@ -0,0 +1,88 @@ +use glam::Vec2; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct Aabb { + pub min: Vec2, + pub max: Vec2, +} + +impl Default for Aabb { + fn default() -> Self { + Self { + min: Vec2::splat(f32::INFINITY), + max: Vec2::splat(f32::NEG_INFINITY), + } + } +} + +impl Aabb { + pub const fn new(min: Vec2, max: Vec2) -> Self { + Self { min, max } + } + + pub fn mid(&self) -> Vec2 { + (self.min + self.max) / 2.0 + } + + pub fn expand_to_fit(&mut self, point: Vec2) { + self.min = self.min.min(point); + self.max = self.max.max(point); + } + + pub fn from_points(points: &[Vec2]) -> Self { + let mut aabb = Self::default(); + + for point in points { + aabb.expand_to_fit(*point); + } + + aabb + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_aabb_default() { + let aabb = Aabb::default(); + assert_eq!(aabb.min, Vec2::splat(f32::INFINITY)); + assert_eq!(aabb.max, Vec2::splat(f32::NEG_INFINITY)); + } + + #[test] + fn test_aabb_new() { + let min = Vec2::new(0.0, 0.0); + let max = Vec2::new(10.0, 10.0); + let aabb = Aabb::new(min, max); + assert_eq!(aabb.min, min); + assert_eq!(aabb.max, max); + } + + #[test] + fn test_aabb_expand_to_fit() { + let mut aabb = Aabb::default(); + let point = Vec2::new(5.0, 5.0); + aabb.expand_to_fit(point); + assert_eq!(aabb.min, point); + assert_eq!(aabb.max, point); + + let new_point = Vec2::new(10.0, 10.0); + aabb.expand_to_fit(new_point); + assert_eq!(aabb.min, point); + assert_eq!(aabb.max, new_point); + } + + #[test] + fn test_aabb_from_points() { + let points = vec![ + Vec2::new(0.0, 0.0), + Vec2::new(10.0, 10.0), + Vec2::new(5.0, 5.0), + ]; + let aabb = Aabb::from_points(&points); + assert_eq!(aabb.min, Vec2::new(0.0, 0.0)); + assert_eq!(aabb.max, Vec2::new(10.0, 10.0)); + } +} diff --git a/quadtree/src/idx.rs b/quadtree/src/idx.rs new file mode 100644 index 00000000..4972a560 --- /dev/null +++ b/quadtree/src/idx.rs @@ -0,0 +1,66 @@ +#![allow(clippy::module_name_repetitions)] +use std::fmt::Debug; + +pub type Idx = u32; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct OptionalIdx(pub Idx); + +impl TryFrom for Idx { + type Error = (); + + fn try_from(value: OptionalIdx) -> Result { + if value.is_null() { + Err(()) + } else { + Ok(value.0) + } + } +} + +impl Default for OptionalIdx { + fn default() -> Self { + Self::NONE + } +} + +impl Debug for OptionalIdx { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_null() { + write!(f, "NodeId::NULL") + } else { + write!(f, "NodeId({})", self.0) + } + } +} + +pub const NULL_ID: Idx = Idx::MAX; + +impl OptionalIdx { + pub const NONE: Self = Self(NULL_ID); + + pub const fn inner(self) -> Option { + if self.is_null() { + None + } else { + Some(self.0) + } + } + + #[must_use] + pub const fn is_null(self) -> bool { + self.0 == NULL_ID + } + + #[must_use] + pub const fn some(id: Idx) -> Self { + debug_assert!(id != NULL_ID); + Self(id) + } +} + +impl From for OptionalIdx { + fn from(value: usize) -> Self { + Self(value as Idx) + } +} diff --git a/quadtree/src/iter.rs b/quadtree/src/iter.rs new file mode 100644 index 00000000..53f6f9fd --- /dev/null +++ b/quadtree/src/iter.rs @@ -0,0 +1,120 @@ +use crate::{idx::Idx, Quadtree}; + +pub struct LeafNodes<'a> { + tree: &'a Quadtree, + stack: Vec, +} + +impl<'a> LeafNodes<'a> { + #[must_use] + pub(crate) fn new(tree: &'a Quadtree, root: Idx) -> Self { + let stack = vec![root]; + Self { tree, stack } + } + + #[must_use] + pub(crate) const fn empty(tree: &'a Quadtree) -> Self { + Self { + tree, + stack: Vec::new(), + } + } +} + +impl<'a> Iterator for LeafNodes<'a> { + type Item = Idx; + + #[allow(clippy::unwrap_in_result)] + fn next(&mut self) -> Option { + while let Some(idx) = self.stack.pop() { + if self.tree.is_leaf(idx).unwrap() { + return Some(idx); + } + + let node = self.tree.get_node(idx).unwrap(); + for child in node.children_iter() { + self.stack.push(child); + } + } + + None + } +} + +#[cfg(test)] +mod tests { + use glam::Vec2; + + use super::*; + + #[test] + fn test_leaf_nodes_empty_tree() { + let points = vec![]; + let tree = Quadtree::build(points); + + #[allow(clippy::needless_collect)] + let leaf_nodes: Vec = tree.leafs().collect(); + assert!(leaf_nodes.is_empty()); + } + + #[test] + fn test_leaf_nodes_single_point() { + let points = vec![Vec2::new(1.0, 1.0)]; + let tree = Quadtree::build(points); + + let leaf_nodes: Vec = tree.leafs().collect(); + assert_eq!(leaf_nodes, vec![0]); + } + + #[test] + fn test_leaf_nodes_multiple_points() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(2.0, 2.0), + Vec2::new(3.0, 3.0), + Vec2::new(4.0, 4.0), + ]; + let tree = Quadtree::build(points.clone()); + + let leaf_nodes: Vec<_> = tree + .leafs() + .flat_map(|idx| tree.points(idx).unwrap()) + .copied() + .collect(); + + assert_eq!(leaf_nodes.len(), points.len()); + + for point in points { + assert!(leaf_nodes.contains(&point)); + } + } + + #[test] + fn test_leaf_nodes_equal_points() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(1.0, 1.0), + Vec2::new(1.0, 1.0), + Vec2::new(1.0, 1.0), + ]; + let tree = Quadtree::build(points); + + let leaf_nodes: Vec = tree.leafs().collect(); + assert_eq!(leaf_nodes, vec![0]); + } + + #[test] + #[should_panic(expected = "called `Option::unwrap()` on a `None` value")] + fn test_leaf_nodes_invalid_root() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(2.0, 2.0), + Vec2::new(3.0, 3.0), + Vec2::new(4.0, 4.0), + ]; + let tree = Quadtree::build(points); + + let leaf_nodes = LeafNodes::new(&tree, 100); + let _result: Vec<_> = leaf_nodes.collect(); + } +} diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index 4699191d..b2a5af68 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -2,118 +2,101 @@ // https://snorrwe.onrender.com/posts/morton-table/ -use glam::Vec2; - -mod nearest; - -struct Aabb { - min: Vec2, - max: Vec2, -} - -impl Default for Aabb { - fn default() -> Self { - Self { - min: Vec2::splat(f32::INFINITY), - max: Vec2::splat(f32::NEG_INFINITY), - } - } -} +use std::ops::Range; -impl Aabb { - fn new(min: Vec2, max: Vec2) -> Self { - Self { min, max } - } - - fn mid(&self) -> Vec2 { - (self.min + self.max) / 2.0 - } - - fn expand_to_fit(&mut self, point: Vec2) { - self.min = self.min.min(point); - self.max = self.max.max(point); - } +use glam::Vec2; - fn from_points(points: &[Vec2]) -> Self { - let mut aabb = Self::default(); +use crate::{ + aaab::Aabb, + idx::{Idx, OptionalIdx}, +}; - for point in points { - aabb.expand_to_fit(*point); - } +mod aaab; +mod idx; +pub mod iter; +mod nearest; - aabb - } +#[derive(Debug)] +pub struct Node { + children: [[OptionalIdx; 2]; 2], } -// primitive -// struct Node { -// children: [Option>; 4], -// } -// -// struct Quadtree { -// root: Node, -// aabb: Aabb, -// } - -struct NodeId(u32); - -const NULL_ID: u32 = u32::MAX; - -impl NodeId { - const NULL: Self = Self(NULL_ID); - - const fn is_null(&self) -> bool { - self.0 == NULL_ID +impl Node { + #[allow(dead_code)] + const fn children(&self) -> &[[OptionalIdx; 2]; 2] { + &self.children } -} -impl From for NodeId { - fn from(value: usize) -> Self { - Self(value as u32) + fn children_iter(&self) -> impl Iterator + '_ { + self.children + .iter() + .flatten() + .filter_map(|&idx| idx.try_into().ok()) } } -struct Node { - children: [[NodeId; 2]; 2], -} - impl Default for Node { fn default() -> Self { Self { - children: [[NodeId::NULL, NodeId::NULL], [NodeId::NULL, NodeId::NULL]], + children: [[OptionalIdx::NONE, OptionalIdx::NONE], [ + OptionalIdx::NONE, + OptionalIdx::NONE, + ]], } } } -struct Quadtree { +pub struct Quadtree { aabb: Aabb, - root: NodeId, + root: OptionalIdx, nodes: Vec, + points: Vec, + node_points_begin: Vec, } -impl Default for Quadtree { - fn default() -> Self { - Self { - aabb: Aabb::default(), - root: NodeId::NULL, - nodes: Vec::new(), - } +#[derive(Copy, Clone)] +struct IndexSlice { + begin: Idx, + end: Idx, +} + +impl IndexSlice { + const fn is_empty(self) -> bool { + self.begin == self.end + } + + const fn len(self) -> Idx { + self.end - self.begin + } + + const fn new(begin: Idx, end: Idx) -> Self { + Self { begin, end } } } #[allow(clippy::indexing_slicing)] -fn build_impl(tree: &mut Quadtree, bbox: Aabb, points: &mut [Vec2]) -> NodeId { - if points.is_empty() { - return NodeId::NULL; +fn build_impl(tree: &mut Quadtree, bbox: Aabb, points_idx: IndexSlice) -> OptionalIdx { + if points_idx.is_empty() { + return OptionalIdx::NONE; } - let result = tree.nodes.len(); - tree.nodes.push(Node::default()); + let result = tree.append_new_node(); + let begin = points_idx.begin as usize; + let points = &mut tree.points[points_idx.begin as usize..points_idx.end as usize]; + + if points_idx.len() == 1 { + tree.node_points_begin[result as usize] = begin as Idx; + return OptionalIdx::some(result); + } - if points.len() == 1 { - return result.into(); + // equal + if points.iter().all(|p| *p == points[0]) { + tree.node_points_begin[result as usize] = begin as Idx; + return OptionalIdx::some(result); } + tree.node_points_begin[result as usize] = begin as Idx; + let center = bbox.mid(); let bottom = |p: &Vec2| p.y < center.y; @@ -125,14 +108,16 @@ fn build_impl(tree: &mut Quadtree, bbox: Aabb, points: &mut [Vec2]) -> NodeId { let split_x_lower = itertools::partition(&mut points[..split_y], left); let split_x_upper = itertools::partition(&mut points[split_y..], left); - // let node = &mut tree.nodes[result]; - let child00 = build_impl( tree, Aabb::new(bbox.min, center), - &mut points[..split_x_lower], + // &mut points[..split_x_lower], + IndexSlice::new( + points_idx.begin as Idx, + points_idx.begin + split_x_lower as Idx, + ), ); - tree.nodes[result].children[0][0] = child00; + tree.get_node_mut(result).unwrap().children[0][0] = child00; let child01 = build_impl( tree, @@ -140,9 +125,13 @@ fn build_impl(tree: &mut Quadtree, bbox: Aabb, points: &mut [Vec2]) -> NodeId { Vec2::new(center.x, bbox.min.y), Vec2::new(bbox.max.x, center.y), ), - &mut points[split_x_lower..split_y], + // &mut points[split_x_lower..split_y], + IndexSlice::new( + points_idx.begin + split_x_lower as Idx, + points_idx.begin + split_y as Idx, + ), ); - tree.nodes[result].children[0][1] = child01; + tree.get_node_mut(result).unwrap().children[0][1] = child01; let child10 = build_impl( tree, @@ -150,29 +139,272 @@ fn build_impl(tree: &mut Quadtree, bbox: Aabb, points: &mut [Vec2]) -> NodeId { Vec2::new(bbox.min.x, center.y), Vec2::new(center.x, bbox.max.y), ), - &mut points[split_y..split_y + split_x_upper], + // &mut points[split_y..split_y + split_x_upper], + IndexSlice::new( + points_idx.begin + split_y as Idx, + points_idx.begin + split_y as Idx + split_x_upper as Idx, + ), ); - tree.nodes[result].children[1][0] = child10; + tree.get_node_mut(result).unwrap().children[1][0] = child10; let child11 = build_impl( tree, Aabb::new(center, bbox.max), - &mut points[split_y + split_x_upper..], + // &mut points[split_y + split_x_upper..], + IndexSlice::new( + points_idx.begin + split_y as Idx + split_x_upper as Idx, + points_idx.end, + ), ); - tree.nodes[result].children[1][1] = child11; + tree.get_node_mut(result).unwrap().children[1][1] = child11; - result.into() + OptionalIdx::some(result) } impl Quadtree { - fn build

(points: &mut [Vec2]) -> Self { - let aabb = Aabb::from_points(points); - let mut quadtree = Self::default(); + #[must_use] + pub const fn aaabb(&self) -> &Aabb { + &self.aabb + } + + #[allow(dead_code)] + #[must_use] + pub fn get_node(&self, id: Idx) -> Option<&Node> { + self.nodes.get(id as usize) + } + + fn get_node_mut(&mut self, id: Idx) -> Option<&mut Node> { + self.nodes.get_mut(id as usize) + } + + fn append_new_node(&mut self) -> Idx { + let result = self.nodes.len(); + + self.nodes.push(Node::default()); + self.node_points_begin.push(0); + + result as Idx + } + + #[must_use] + fn points_range_for(&self, idx: Idx) -> Option> { + let begin = *self.node_points_begin.get(idx as usize)?; + let end = *self.node_points_begin.get(idx as usize + 1)?; + + Some(begin as usize..end as usize) + } + + #[must_use] + pub fn points(&self, idx: Idx) -> Option<&[Vec2]> { + let range = self.points_range_for(idx)?; + #[allow(clippy::indexing_slicing)] + Some(&self.points[range]) + } + + #[must_use] + pub fn is_leaf(&self, idx: Idx) -> Option { + let range = self.points_range_for(idx)?; + let not_leaf = range.is_empty(); + Some(!not_leaf) + } + + #[must_use] + pub fn points_mut(&mut self, idx: Idx) -> Option<&mut [Vec2]> { + let range = self.points_range_for(idx)?; + #[allow(clippy::indexing_slicing)] + Some(&mut self.points[range]) + } + + #[must_use] + pub fn leafs(&self) -> iter::LeafNodes { + #[allow(clippy::option_if_let_else)] + match self.root.inner() { + None => iter::LeafNodes::empty(self), + Some(root) => iter::LeafNodes::new(self, root), + } + } + + #[must_use] + pub fn build(points: Vec) -> Self { + let aabb = Aabb::from_points(&points); + + let len = points.len(); + + let mut result = Self { + aabb, + root: OptionalIdx::NONE, + nodes: vec![], + points, + node_points_begin: vec![], + }; + + result.root = build_impl(&mut result, aabb, IndexSlice { + begin: 0, + end: len as Idx, + }); + + // to eliminate edge case on right edge + result.node_points_begin.push(result.points.len() as Idx); + + result + } +} + +#[cfg(test)] +#[allow(clippy::indexing_slicing)] +mod tests { + use glam::Vec2; + + use crate::{ + aaab::Aabb, + idx::{Idx, OptionalIdx}, + IndexSlice, Node, Quadtree, + }; + + #[test] + fn test_node_default() { + let node = Node::default(); + assert_eq!(node.children(), &[ + [OptionalIdx::NONE, OptionalIdx::NONE], + [OptionalIdx::NONE, OptionalIdx::NONE] + ]); + } + + #[test] + fn test_node_children_iter() { + let mut node = Node::default(); + node.children[0][0] = OptionalIdx::some(1); + node.children[1][1] = OptionalIdx::some(2); + + let children: Vec = node.children_iter().collect(); + assert_eq!(children, vec![1, 2]); + } + + #[test] + fn test_index_slice() { + let slice = IndexSlice::new(0, 5); + assert_eq!(slice.begin, 0); + assert_eq!(slice.end, 5); + assert_eq!(slice.len(), 5); + assert!(!slice.is_empty()); + + let empty_slice = IndexSlice::new(2, 2); + assert!(empty_slice.is_empty()); + } + + #[test] + fn test_quadtree_build_empty() { + let points = vec![]; + let tree = Quadtree::build(points); + + assert_eq!(tree.nodes.len(), 0); + assert_eq!(tree.points.len(), 0); + assert_eq!(tree.node_points_begin.len(), 1); + assert_eq!(tree.root, OptionalIdx::NONE); + } + + #[test] + fn test_quadtree_build_single_point() { + let points = vec![Vec2::new(1.0, 2.0)]; + let tree = Quadtree::build(points); + + assert_eq!(tree.nodes.len(), 1); + assert_eq!(tree.points.len(), 1); + + assert_eq!(tree.node_points_begin.len(), 2); + assert_eq!(tree.root, OptionalIdx::some(0)); + + let root_points = tree.points(0).unwrap(); + assert_eq!(root_points, &[Vec2::new(1.0, 2.0)]); + } + + #[test] + fn test_quadtree_build_multiple_points() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(2.0, 2.0), + Vec2::new(3.0, 3.0), + Vec2::new(4.0, 4.0), + ]; + + let tree = Quadtree::build(points); + + assert_eq!(tree.nodes.len(), 7); + assert_eq!(tree.points.len(), 4); + assert_eq!(tree.node_points_begin.len(), 8); + assert_eq!(tree.root, OptionalIdx::some(0)); + + let root_points = tree.points(0).unwrap(); + assert_eq!(root_points, &[]); + } + + #[test] + fn test_quadtree_build_equal_points() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(1.0, 1.0), + Vec2::new(1.0, 1.0), + Vec2::new(1.0, 1.0), + ]; + + let tree = Quadtree::build(points); + + assert_eq!(tree.nodes.len(), 1); + assert_eq!(tree.points.len(), 4); + assert_eq!(tree.node_points_begin.len(), 2); + assert_eq!(tree.root, OptionalIdx::some(0)); + + let root_points = tree.points(0).unwrap(); + assert_eq!(root_points.len(), 4); + assert!(root_points.iter().all(|&p| p == Vec2::new(1.0, 1.0))); + } + + #[test] + fn test_quadtree_aabb() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(2.0, 2.0), + Vec2::new(3.0, 3.0), + Vec2::new(4.0, 4.0), + ]; + + let tree = Quadtree::build(points); + + let expected_aabb = Aabb::new(Vec2::new(1.0, 1.0), Vec2::new(4.0, 4.0)); + assert_eq!(tree.aaabb(), &expected_aabb); + } + + #[test] + fn test_quadtree_points_mut() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(2.0, 2.0), + Vec2::new(3.0, 3.0), + Vec2::new(4.0, 4.0), + ]; + + let mut tree = Quadtree::build(points); + + let leaf = tree.leafs().next().unwrap(); + + let root_points = tree.points_mut(leaf).unwrap(); + root_points[0] = Vec2::new(5.0, 5.0); + + assert_eq!(tree.points(leaf).unwrap()[0], Vec2::new(5.0, 5.0)); + } - let root = build_impl(&mut quadtree, aabb, points); + #[test] + fn test_quadtree_points_out_of_range() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(2.0, 2.0), + Vec2::new(3.0, 3.0), + Vec2::new(4.0, 4.0), + ]; - quadtree.root = root; + let mut tree = Quadtree::build(points); - quadtree + assert!(tree.points(100).is_none()); + assert!(tree.points_mut(100).is_none()); } } diff --git a/server/Cargo.toml b/server/Cargo.toml index c2f4cff8..44a01ed4 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -15,7 +15,7 @@ pprof = ["dep:pprof"] [dependencies] anyhow = "1.0.81" tracing = "0.1.40" -serde_json = "1.0.114" +serde_json = "1.0.115" bytes = "1.6.0" # get terminated by signal SIGBUS (Misaligned address error) without frame-pointer From 4592338df985dd1622de7c3ff03bfdc42da0efd4 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Tue, 26 Mar 2024 14:58:14 -0500 Subject: [PATCH 03/13] fix cargo-deny config --- deny.toml | 7 +++++-- quadtree/Cargo.toml | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/deny.toml b/deny.toml index 53176547..cfe5db11 100644 --- a/deny.toml +++ b/deny.toml @@ -217,7 +217,10 @@ skip = [ "futures-lite", "fastrand", "event-listener", - "base64" + "base64", + "regex-automata", + "regex-syntax", + "glam" #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, ] # Similarly to `skip` allows you to skip certain crates during duplicate @@ -244,7 +247,7 @@ unknown-git = "warn" allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [ - "https://github.com/rj00a/evenio", + "https://github.com/andrewgazelka/evenio", "https://github.com/servo/rust-smallvec" ] diff --git a/quadtree/Cargo.toml b/quadtree/Cargo.toml index 4b0cd07a..383604c7 100644 --- a/quadtree/Cargo.toml +++ b/quadtree/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" authors = ["Andrew Gazelka "] readme = "README.md" +publish = false [dependencies] glam = "0.27.0" From 2c5b31b238d1f3b2696ae1ec686e4f2ac31bdff0 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Tue, 26 Mar 2024 18:46:52 -0500 Subject: [PATCH 04/13] add quadtree progression --- quadtree/src/aaab.rs | 135 +++++++++++++++ quadtree/src/idx.rs | 4 +- quadtree/src/lib.rs | 388 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 445 insertions(+), 82 deletions(-) diff --git a/quadtree/src/aaab.rs b/quadtree/src/aaab.rs index f1e257a7..97416011 100644 --- a/quadtree/src/aaab.rs +++ b/quadtree/src/aaab.rs @@ -15,6 +15,46 @@ impl Default for Aabb { } } +pub trait Containable { + fn contains(bounding_box: &Aabb, elem: Self) -> bool; +} + +impl Containable for Vec2 { + fn contains(bounding_box: &Aabb, elem: Self) -> bool { + let min = bounding_box.min.as_ref(); + let max = bounding_box.max.as_ref(); + let elem = elem.as_ref(); + + let mut contains = 0b1u8; + + #[allow(clippy::indexing_slicing)] + for i in 0..2 { + contains &= (elem[i] >= min[i]) as u8; + contains &= (elem[i] <= max[i]) as u8; + } + + contains == 1 + } +} + +impl Containable for Aabb { + fn contains(bounding_box: &Aabb, elem: Self) -> bool { + let this_min = bounding_box.min.as_ref(); + let this_max = bounding_box.max.as_ref(); + let other_min = elem.min.as_ref(); + let other_max = elem.max.as_ref(); + + let mut contains = 0b1u8; + + #[allow(clippy::indexing_slicing)] + for i in 0..2 { + contains &= (other_min[i] >= this_min[i]) as u8; + contains &= (other_max[i] <= this_max[i]) as u8; + } + contains == 1 + } +} + impl Aabb { pub const fn new(min: Vec2, max: Vec2) -> Self { Self { min, max } @@ -38,6 +78,29 @@ impl Aabb { aabb } + + pub fn intersects(&self, other: &Self) -> bool { + let this_min = self.min.as_ref(); + let this_max = self.max.as_ref(); + + let other_min = other.min.as_ref(); + let other_max = other.max.as_ref(); + + let mut intersects = 0b1u8; + + #[allow(clippy::indexing_slicing)] + for i in 0..2 { + intersects &= (this_min[i] <= other_max[i]) as u8; + intersects &= (this_max[i] >= other_min[i]) as u8; + } + + intersects == 1 + } + + #[allow(clippy::same_name_method)] + pub fn contains(&self, other: A) -> bool { + A::contains(self, other) + } } #[cfg(test)] @@ -74,6 +137,16 @@ mod tests { assert_eq!(aabb.max, new_point); } + #[test] + #[allow(clippy::similar_names)] + fn test_mid() { + let min = Vec2::new(0.0, 0.0); + let max = Vec2::new(2.0, 2.0); + let aabb = Aabb::new(min, max); + let mid = aabb.mid(); + assert_eq!(mid, Vec2::splat(1.0)); + } + #[test] fn test_aabb_from_points() { let points = vec![ @@ -85,4 +158,66 @@ mod tests { assert_eq!(aabb.min, Vec2::new(0.0, 0.0)); assert_eq!(aabb.max, Vec2::new(10.0, 10.0)); } + + #[test] + fn test_intersects() { + let aabb1 = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(2.0, 2.0)); + let aabb2 = Aabb::new(Vec2::new(1.0, 1.0), Vec2::new(3.0, 3.0)); + let aabb3 = Aabb::new(Vec2::new(3.0, 3.0), Vec2::new(4.0, 4.0)); + + assert!(aabb1.intersects(&aabb2)); + assert!(aabb2.intersects(&aabb1)); + assert!(!aabb1.intersects(&aabb3)); + assert!(!aabb3.intersects(&aabb1)); + } + + #[test] + fn test_no_intersection() { + let aabb1 = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)); + let aabb2 = Aabb::new(Vec2::new(2.0, 2.0), Vec2::new(3.0, 3.0)); + + assert!(!aabb1.intersects(&aabb2)); + assert!(!aabb2.intersects(&aabb1)); + } + + #[test] + fn test_intersection_on_edge() { + let aabb1 = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)); + let aabb2 = Aabb::new(Vec2::new(1.0, 1.0), Vec2::new(2.0, 2.0)); + + assert!(aabb1.intersects(&aabb2)); + assert!(aabb2.intersects(&aabb1)); + } + + #[test] + fn test_intersection() { + let aabb1 = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(3.0, 3.0)); + let aabb2 = Aabb::new(Vec2::new(1.0, 1.0), Vec2::new(2.0, 2.0)); + + assert!(aabb1.intersects(&aabb2)); + assert!(aabb2.intersects(&aabb1)); + } + + #[test] + fn test_contains_aabb() { + let aabb1 = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(3.0, 3.0)); + let aabb2 = Aabb::new(Vec2::new(1.0, 1.0), Vec2::new(2.0, 2.0)); + let aabb3 = Aabb::new(Vec2::new(2.0, 2.0), Vec2::new(4.0, 4.0)); + + assert!(aabb1.contains(aabb2)); + assert!(!aabb2.contains(aabb1)); + assert!(!aabb1.contains(aabb3)); + assert!(!aabb3.contains(aabb1)); + } + + #[test] + fn test_contains_vec2() { + let aabb = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(2.0, 2.0)); + + assert!(aabb.contains(Vec2::new(1.0, 1.0))); + assert!(aabb.contains(Vec2::new(0.0, 0.0))); + assert!(aabb.contains(Vec2::new(2.0, 2.0))); + assert!(!aabb.contains(Vec2::new(-1.0, 1.0))); + assert!(!aabb.contains(Vec2::new(1.0, 3.0))); + } } diff --git a/quadtree/src/idx.rs b/quadtree/src/idx.rs index 4972a560..754b1050 100644 --- a/quadtree/src/idx.rs +++ b/quadtree/src/idx.rs @@ -1,10 +1,10 @@ #![allow(clippy::module_name_repetitions)] use std::fmt::Debug; -pub type Idx = u32; +pub type Idx = u16; #[derive(Copy, Clone, Eq, PartialEq)] -pub struct OptionalIdx(pub Idx); +pub struct OptionalIdx(Idx); impl TryFrom for Idx { type Error = (); diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index b2a5af68..17f377a2 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -19,6 +19,7 @@ mod nearest; #[derive(Debug)] pub struct Node { children: [[OptionalIdx; 2]; 2], + parent: OptionalIdx, } impl Node { @@ -33,6 +34,11 @@ impl Node { .flatten() .filter_map(|&idx| idx.try_into().ok()) } + + #[must_use] + pub const fn parent(&self) -> Option { + self.parent.inner() + } } impl Default for Node { @@ -42,6 +48,7 @@ impl Default for Node { OptionalIdx::NONE, OptionalIdx::NONE, ]], + parent: OptionalIdx::NONE, } } } @@ -54,69 +61,81 @@ pub struct Quadtree { node_points_begin: Vec, } -#[derive(Copy, Clone)] -struct IndexSlice { - begin: Idx, - end: Idx, -} - -impl IndexSlice { - const fn is_empty(self) -> bool { - self.begin == self.end - } - - const fn len(self) -> Idx { - self.end - self.begin - } - - const fn new(begin: Idx, end: Idx) -> Self { - Self { begin, end } - } -} +pub type IndexSlice = Range; #[allow(clippy::indexing_slicing)] -fn build_impl(tree: &mut Quadtree, bbox: Aabb, points_idx: IndexSlice) -> OptionalIdx { +#[allow(unused)] +fn build_impl( + tree: &mut Quadtree, + bbox: Aabb, + points_idx: IndexSlice, + parent: OptionalIdx, +) -> OptionalIdx { if points_idx.is_empty() { return OptionalIdx::NONE; } - let result = tree.append_new_node(); - let begin = points_idx.begin as usize; - let points = &mut tree.points[points_idx.begin as usize..points_idx.end as usize]; + let result = tree.append_new_node(parent); + let start = points_idx.start as usize; + let points = &mut tree.points[points_idx.start as usize..points_idx.end as usize]; + + // println!("TOP; HAVE POINTS {:?}", points); if points_idx.len() == 1 { - tree.node_points_begin[result as usize] = begin as Idx; + tree.node_points_begin[result as usize] = start as Idx; return OptionalIdx::some(result); } // equal if points.iter().all(|p| *p == points[0]) { - tree.node_points_begin[result as usize] = begin as Idx; + tree.node_points_begin[result as usize] = start as Idx; return OptionalIdx::some(result); } - tree.node_points_begin[result as usize] = begin as Idx; + tree.node_points_begin[result as usize] = start as Idx; let center = bbox.mid(); let bottom = |p: &Vec2| p.y < center.y; let left = |p: &Vec2| p.x < center.x; + // len + // println!("points: {}", points.len()); + // println!("{:?}", points); + // todo: why need to &mut points[..]? let split_y = itertools::partition(&mut *points, bottom); - let split_x_lower = itertools::partition(&mut points[..split_y], left); - let split_x_upper = itertools::partition(&mut points[split_y..], left); + // println!(); + // println!("split_y: {} @ {}", split_y, center.y); + // println!("BOTTOM {:?}", &points[..split_y]); + // println!("TOP {:?}", &points[split_y..]); + + debug_assert!(points[..split_y].iter().all(|p| p.y < center.y)); + debug_assert!(points[split_y..].iter().all(|p| p.y >= center.y)); + + let split_x_lower = itertools::partition(&mut points[..split_y], left) as Idx; + let split_x_upper = itertools::partition(&mut points[split_y..], left) as Idx; + + // println!(); + // println!("split_x_lower: {} @ {}", split_x_lower, center.x); + // println!("LEFT {:?}", &points[..split_x_lower as usize]); + // println!("RIGHT {:?}", &points[split_x_lower as usize..split_y]); + + let split_y = split_y as Idx; + + let result_some = OptionalIdx::some(result); + + let child00_idx = points_idx.start..(points_idx.start + split_x_lower); + let child01_idx = (points_idx.start + split_x_lower)..(points_idx.start + split_y); + let child10_idx = (points_idx.start + split_y)..(points_idx.start + split_y + split_x_upper); + let child11_idx = (points_idx.start + split_y + split_x_upper)..points_idx.end; + + // println!("indices \n{:?}\n{:?}\n{:?}\n{:?}", child00_idx, child01_idx, child10_idx, + // child11_idx); + + let child00 = build_impl(tree, Aabb::new(bbox.min, center), child00_idx, result_some); - let child00 = build_impl( - tree, - Aabb::new(bbox.min, center), - // &mut points[..split_x_lower], - IndexSlice::new( - points_idx.begin as Idx, - points_idx.begin + split_x_lower as Idx, - ), - ); tree.get_node_mut(result).unwrap().children[0][0] = child00; let child01 = build_impl( @@ -125,11 +144,8 @@ fn build_impl(tree: &mut Quadtree, bbox: Aabb, points_idx: IndexSlice) -> Option Vec2::new(center.x, bbox.min.y), Vec2::new(bbox.max.x, center.y), ), - // &mut points[split_x_lower..split_y], - IndexSlice::new( - points_idx.begin + split_x_lower as Idx, - points_idx.begin + split_y as Idx, - ), + child01_idx, + result_some, ); tree.get_node_mut(result).unwrap().children[0][1] = child01; @@ -139,23 +155,12 @@ fn build_impl(tree: &mut Quadtree, bbox: Aabb, points_idx: IndexSlice) -> Option Vec2::new(bbox.min.x, center.y), Vec2::new(center.x, bbox.max.y), ), - // &mut points[split_y..split_y + split_x_upper], - IndexSlice::new( - points_idx.begin + split_y as Idx, - points_idx.begin + split_y as Idx + split_x_upper as Idx, - ), + child10_idx, + result_some, ); tree.get_node_mut(result).unwrap().children[1][0] = child10; - let child11 = build_impl( - tree, - Aabb::new(center, bbox.max), - // &mut points[split_y + split_x_upper..], - IndexSlice::new( - points_idx.begin + split_y as Idx + split_x_upper as Idx, - points_idx.end, - ), - ); + let child11 = build_impl(tree, Aabb::new(center, bbox.max), child11_idx, result_some); tree.get_node_mut(result).unwrap().children[1][1] = child11; OptionalIdx::some(result) @@ -173,14 +178,22 @@ impl Quadtree { self.nodes.get(id as usize) } + #[allow(unused)] fn get_node_mut(&mut self, id: Idx) -> Option<&mut Node> { self.nodes.get_mut(id as usize) } - fn append_new_node(&mut self) -> Idx { + fn append_new_node(&mut self, parent_idx: OptionalIdx) -> Idx { let result = self.nodes.len(); - self.nodes.push(Node::default()); + self.nodes.push(Node { + children: [[OptionalIdx::NONE, OptionalIdx::NONE], [ + OptionalIdx::NONE, + OptionalIdx::NONE, + ]], + parent: parent_idx, + }); + self.node_points_begin.push(0); result as Idx @@ -197,15 +210,24 @@ impl Quadtree { #[must_use] pub fn points(&self, idx: Idx) -> Option<&[Vec2]> { let range = self.points_range_for(idx)?; + + if range.is_empty() { + return None; + } + #[allow(clippy::indexing_slicing)] Some(&self.points[range]) } #[must_use] pub fn is_leaf(&self, idx: Idx) -> Option { - let range = self.points_range_for(idx)?; - let not_leaf = range.is_empty(); - Some(!not_leaf) + let node = self.get_node(idx)?; + + if node.children_iter().count() == 0 { + return Some(true); + } + + Some(false) } #[must_use] @@ -238,16 +260,65 @@ impl Quadtree { node_points_begin: vec![], }; - result.root = build_impl(&mut result, aabb, IndexSlice { - begin: 0, - end: len as Idx, - }); + result.root = build_impl(&mut result, aabb, 0..len as Idx, OptionalIdx::NONE); // to eliminate edge case on right edge result.node_points_begin.push(result.points.len() as Idx); result } + + #[must_use] + pub fn query_bbox(&self, bbox: &Aabb) -> Vec { + let mut result = Vec::new(); + self.query_bbox_recursive(self.root, &self.aabb, bbox, &mut result); + result + } + + fn query_bbox_recursive( + &self, + node: OptionalIdx, + node_bbox: &Aabb, + query_bbox: &Aabb, + result: &mut Vec, + ) { + let Some(node_idx) = node.inner() else { + return; + }; + + if !node_bbox.intersects(query_bbox) { + return; + } + + if let Some(points) = self.points(node_idx) { + for &point in points { + if query_bbox.contains(point) { + result.push(point); + } + } + return; + } + + let center = node_bbox.mid(); + let child_bboxes = [ + Aabb::new(node_bbox.min, center), + Aabb::new( + Vec2::new(center.x, node_bbox.min.y), + Vec2::new(node_bbox.max.x, center.y), + ), + Aabb::new( + Vec2::new(node_bbox.min.x, center.y), + Vec2::new(center.x, node_bbox.max.y), + ), + Aabb::new(center, node_bbox.max), + ]; + + let node = self.get_node(node_idx).unwrap(); + for (i, &child) in node.children.iter().flatten().enumerate() { + #[allow(clippy::indexing_slicing)] + self.query_bbox_recursive(child, &child_bboxes[i], query_bbox, result); + } + } } #[cfg(test)] @@ -258,7 +329,7 @@ mod tests { use crate::{ aaab::Aabb, idx::{Idx, OptionalIdx}, - IndexSlice, Node, Quadtree, + Node, Quadtree, }; #[test] @@ -280,18 +351,6 @@ mod tests { assert_eq!(children, vec![1, 2]); } - #[test] - fn test_index_slice() { - let slice = IndexSlice::new(0, 5); - assert_eq!(slice.begin, 0); - assert_eq!(slice.end, 5); - assert_eq!(slice.len(), 5); - assert!(!slice.is_empty()); - - let empty_slice = IndexSlice::new(2, 2); - assert!(empty_slice.is_empty()); - } - #[test] fn test_quadtree_build_empty() { let points = vec![]; @@ -334,8 +393,8 @@ mod tests { assert_eq!(tree.node_points_begin.len(), 8); assert_eq!(tree.root, OptionalIdx::some(0)); - let root_points = tree.points(0).unwrap(); - assert_eq!(root_points, &[]); + let root_points = tree.points(0); + assert!(root_points.is_none()); } #[test] @@ -407,4 +466,173 @@ mod tests { assert!(tree.points(100).is_none()); assert!(tree.points_mut(100).is_none()); } + + #[test] + fn test_parent() { + let points = vec![ + Vec2::new(1.0, 1.0), + Vec2::new(2.0, 2.0), + Vec2::new(3.0, 3.0), + Vec2::new(4.0, 4.0), + ]; + + let tree = Quadtree::build(points); + + let leaf = tree.leafs().next().unwrap(); + let leaf_points = tree.points(leaf).unwrap(); + assert_eq!(leaf_points.len(), 1); + + let leaf = tree.get_node(leaf).unwrap(); + + let parent = leaf.parent().unwrap(); + let parent = tree.get_node(parent).unwrap(); + + let grandparent = parent.parent().unwrap(); + assert_eq!(grandparent, 0); + } + + #[test] + fn test_query_bbox_empty_tree() { + let qt = Quadtree::build(vec![]); + let bbox = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 0); + } + + #[test] + fn test_query_bbox_single_point() { + let qt = Quadtree::build(vec![Vec2::new(0.5, 0.5)]); + let bbox = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + assert_eq!(result[0], Vec2::new(0.5, 0.5)); + + let bbox = Aabb::new(Vec2::new(0.6, 0.6), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 0); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn test_query_bbox_multiple_points() { + let points = vec![ + Vec2::new(0.25, 0.25), + Vec2::new(0.75, 0.25), + Vec2::new(0.25, 0.75), + Vec2::new(0.75, 0.75), + ]; + let qt = Quadtree::build(points.clone()); + + let bbox = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 4); + assert_eq!(result, points); + + let bbox = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(0.5, 0.5)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + assert_eq!(result[0], Vec2::new(0.25, 0.25)); + + let bbox = Aabb::new(Vec2::new(0.5, 0.0), Vec2::new(1.0, 0.5)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + assert_eq!(result[0], Vec2::new(0.75, 0.25)); + + let bbox = Aabb::new(Vec2::new(0.0, 0.5), Vec2::new(0.5, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + assert_eq!(result[0], Vec2::new(0.25, 0.75)); + + let bbox = Aabb::new(Vec2::new(0.5, 0.5), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + assert_eq!(result[0], Vec2::new(0.75, 0.75)); + + let bbox = Aabb::new(Vec2::new(0.25, 0.25), Vec2::new(0.75, 0.75)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 4); + assert_eq!(result, points); + } + + #[test] + fn test_query_bbox_large_tree() { + let mut points = Vec::new(); + // max 65,536 because using u16 + let width = 100; // 100 * 100 = 10_000 points + + for i in 0..width { + let x = (i as f32) / width as f32; + for j in 0..width { + let y = (j as f32) / width as f32; + points.push(Vec2::new(x, y)); + } + } + let qt = Quadtree::build(points.clone()); + + let number_points: usize = qt.leafs().map(|idx| qt.points(idx).unwrap().len()).sum(); + assert_eq!(number_points, points.len()); + + let bbox = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + + assert_eq!(result.len(), points.len()); + for point in points { + assert!(result.contains(&point)); + } + + let bbox = Aabb::new( + Vec2::new(0.25 + f32::EPSILON, 0.25 + f32::EPSILON), + Vec2::new(0.75, 0.75), + ); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 50 * 50); + + let bbox = Aabb::new( + Vec2::new(0.0 + f32::EPSILON, 0.0 + f32::EPSILON), + Vec2::new(0.01, 0.01), + ); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + } + + #[test] + fn test_query_bbox_non_overlapping() { + let points = vec![ + Vec2::new(0.25, 0.25), + Vec2::new(0.75, 0.25), + Vec2::new(0.25, 0.75), + Vec2::new(0.75, 0.75), + ]; + let qt = Quadtree::build(points); + + let bbox = Aabb::new(Vec2::new(-1.0, -1.0), Vec2::new(-0.5, -0.5)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 0); + } + + #[test] + fn test_query_bbox_edge_cases() { + let points = vec![ + Vec2::new(0.0, 0.0), + Vec2::new(1.0, 0.0), + Vec2::new(0.0, 1.0), + Vec2::new(1.0, 1.0), + ]; + let qt = Quadtree::build(points.clone()); + + let bbox = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 4); + assert_eq!(result, points); + + let bbox = Aabb::new(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + assert_eq!(result[0], Vec2::new(0.0, 0.0)); + + let bbox = Aabb::new(Vec2::new(1.0, 1.0), Vec2::new(1.0, 1.0)); + let result = qt.query_bbox(&bbox); + assert_eq!(result.len(), 1); + assert_eq!(result[0], Vec2::new(1.0, 1.0)); + } } From 322f27a50a09153f76043c6b5bfc099372797481 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Tue, 26 Mar 2024 19:47:48 -0500 Subject: [PATCH 05/13] stash --- quadtree/src/aaab.rs | 5 +++ quadtree/src/iter.rs | 12 +++---- quadtree/src/lib.rs | 70 +++++++++++++++++++++++++++++++---------- server/src/lib.rs | 38 ++++++++++++++++++++-- server/src/quad_tree.rs | 40 ----------------------- 5 files changed, 101 insertions(+), 64 deletions(-) delete mode 100644 server/src/quad_tree.rs diff --git a/quadtree/src/aaab.rs b/quadtree/src/aaab.rs index 97416011..925b8aee 100644 --- a/quadtree/src/aaab.rs +++ b/quadtree/src/aaab.rs @@ -59,6 +59,11 @@ impl Aabb { pub const fn new(min: Vec2, max: Vec2) -> Self { Self { min, max } } + + pub fn area(&self) -> f64 { + let size = self.max - self.min; + size.x as f64 * size.y as f64 + } pub fn mid(&self) -> Vec2 { (self.min + self.max) / 2.0 diff --git a/quadtree/src/iter.rs b/quadtree/src/iter.rs index 53f6f9fd..5c010643 100644 --- a/quadtree/src/iter.rs +++ b/quadtree/src/iter.rs @@ -1,19 +1,19 @@ use crate::{idx::Idx, Quadtree}; -pub struct LeafNodes<'a> { - tree: &'a Quadtree, +pub struct LeafNodes<'a, T> { + tree: &'a Quadtree, stack: Vec, } -impl<'a> LeafNodes<'a> { +impl<'a, T> LeafNodes<'a, T> { #[must_use] - pub(crate) fn new(tree: &'a Quadtree, root: Idx) -> Self { + pub(crate) fn new(tree: &'a Quadtree, root: Idx) -> Self { let stack = vec![root]; Self { tree, stack } } #[must_use] - pub(crate) const fn empty(tree: &'a Quadtree) -> Self { + pub(crate) const fn empty(tree: &'a Quadtree) -> Self { Self { tree, stack: Vec::new(), @@ -21,7 +21,7 @@ impl<'a> LeafNodes<'a> { } } -impl<'a> Iterator for LeafNodes<'a> { +impl<'a, T> Iterator for LeafNodes<'a, T> { type Item = Idx; #[allow(clippy::unwrap_in_result)] diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index 17f377a2..4a270321 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -1,5 +1,4 @@ // https://lisyarus.github.io/blog/programming/2022/12/21/quadtrees.html - // https://snorrwe.onrender.com/posts/morton-table/ use std::ops::Range; @@ -20,6 +19,7 @@ mod nearest; pub struct Node { children: [[OptionalIdx; 2]; 2], parent: OptionalIdx, + aabb: Aabb, } impl Node { @@ -53,11 +53,13 @@ impl Default for Node { } } -pub struct Quadtree { +pub struct Quadtree { aabb: Aabb, + min_area: f64, root: OptionalIdx, nodes: Vec, points: Vec, + data: Vec, node_points_begin: Vec, } @@ -65,23 +67,28 @@ pub type IndexSlice = Range; #[allow(clippy::indexing_slicing)] #[allow(unused)] -fn build_impl( - tree: &mut Quadtree, +fn build_impl( + tree: &mut Quadtree, bbox: Aabb, - points_idx: IndexSlice, + slice: IndexSlice, parent: OptionalIdx, ) -> OptionalIdx { - if points_idx.is_empty() { + if slice.is_empty() { return OptionalIdx::NONE; } let result = tree.append_new_node(parent); - let start = points_idx.start as usize; - let points = &mut tree.points[points_idx.start as usize..points_idx.end as usize]; + let start = slice.start as usize; + let points = &mut tree.points[slice.start as usize..slice.end as usize]; // println!("TOP; HAVE POINTS {:?}", points); - if points_idx.len() == 1 { + if slice.len() == 1 { + tree.node_points_begin[result as usize] = start as Idx; + return OptionalIdx::some(result); + } + + if bbox.area() < tree.min_area { tree.node_points_begin[result as usize] = start as Idx; return OptionalIdx::some(result); } @@ -126,10 +133,10 @@ fn build_impl( let result_some = OptionalIdx::some(result); - let child00_idx = points_idx.start..(points_idx.start + split_x_lower); - let child01_idx = (points_idx.start + split_x_lower)..(points_idx.start + split_y); - let child10_idx = (points_idx.start + split_y)..(points_idx.start + split_y + split_x_upper); - let child11_idx = (points_idx.start + split_y + split_x_upper)..points_idx.end; + let child00_idx = slice.start..(slice.start + split_x_lower); + let child01_idx = (slice.start + split_x_lower)..(slice.start + split_y); + let child10_idx = (slice.start + split_y)..(slice.start + split_y + split_x_upper); + let child11_idx = (slice.start + split_y + split_x_upper)..slice.end; // println!("indices \n{:?}\n{:?}\n{:?}\n{:?}", child00_idx, child01_idx, child10_idx, // child11_idx); @@ -166,7 +173,9 @@ fn build_impl( OptionalIdx::some(result) } -impl Quadtree { +type SimpleQuadtree = Quadtree<()>; + +impl Quadtree { #[must_use] pub const fn aaabb(&self) -> &Aabb { &self.aabb @@ -238,7 +247,7 @@ impl Quadtree { } #[must_use] - pub fn leafs(&self) -> iter::LeafNodes { + pub fn leafs(&self) -> iter::LeafNodes { #[allow(clippy::option_if_let_else)] match self.root.inner() { None => iter::LeafNodes::empty(self), @@ -247,16 +256,18 @@ impl Quadtree { } #[must_use] - pub fn build(points: Vec) -> Self { + pub fn build_with_min_area(points: Vec, data: Vec, min_area: f64) -> Self { let aabb = Aabb::from_points(&points); let len = points.len(); let mut result = Self { aabb, + min_area, root: OptionalIdx::NONE, nodes: vec![], points, + data, node_points_begin: vec![], }; @@ -268,6 +279,11 @@ impl Quadtree { result } + #[must_use] + pub fn build(points: Vec) -> Self { + Self::build_with_min_area(points, 0.0) + } + #[must_use] pub fn query_bbox(&self, bbox: &Aabb) -> Vec { let mut result = Vec::new(); @@ -318,6 +334,28 @@ impl Quadtree { #[allow(clippy::indexing_slicing)] self.query_bbox_recursive(child, &child_bboxes[i], query_bbox, result); } + } + + pub fn move_point(&mut self, node_idx: Idx, local_idx: usize, new_point: Vec2) { + let mut node = self.nodes.get(node_idx as usize).unwrap(); + let range = self.points_range_for(node_idx).unwrap(); + + let points = &mut self.points[range]; + + if node.aabb.contains(new_point) { + points[local_idx] = new_point; + return; + } + + // we need to expand the node + + loop { + + } + + + + } } diff --git a/server/src/lib.rs b/server/src/lib.rs index 6c0ed1f2..6b41d933 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -37,8 +37,6 @@ mod system; mod bits; -mod quad_tree; - pub mod bounding_box; // A zero-sized component, often called a "marker" or "tag". @@ -341,6 +339,42 @@ impl FullEntityPose { self.position += vec; self.bounding = self.bounding.move_by(vec); } + /// # Safety + /// This is only safe is this is not done in tandem with another `EntityReaction` +// #[instrument(skip_all)] + pub unsafe fn apply_entity_collision(&self, other: &Self, reaction: &EntityReaction) { + let dx = other.position.x - self.position.x; + let dz = other.position.z - self.position.z; + + let largest_distance = dx.abs().max(dz.abs()); + + if largest_distance >= 0.01 { + let mut vx = dx / 20.0; + let mut vz = dz / 20.0; + + if largest_distance < 1.0 { + // 1 / sqrt(x) increases exponentially as x approaches 0 + + vx /= largest_distance.sqrt(); + vz /= largest_distance.sqrt(); + } else { + vx /= largest_distance; + vz /= largest_distance; + } + + let reaction = &mut *reaction.0.get(); + + const MULT_FACTOR: f64 = 2.0; + + reaction.velocity.x -= vx * MULT_FACTOR; + reaction.velocity.z -= vz * MULT_FACTOR; + + // todo: more efficient to do this OR + // more efficient to just have par iter + // other.x_velocity += vx; + // other.z_velocity += vz; + } + } } #[derive(Debug, Default)] diff --git a/server/src/quad_tree.rs b/server/src/quad_tree.rs deleted file mode 100644 index 76eaeb83..00000000 --- a/server/src/quad_tree.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::{EntityReaction, FullEntityPose}; - -impl FullEntityPose { - /// # Safety - /// This is only safe is this is not done in tandem with another `EntityReaction` - // #[instrument(skip_all)] - pub unsafe fn apply_entity_collision(&self, other: &Self, reaction: &EntityReaction) { - let dx = other.position.x - self.position.x; - let dz = other.position.z - self.position.z; - - let largest_distance = dx.abs().max(dz.abs()); - - if largest_distance >= 0.01 { - let mut vx = dx / 20.0; - let mut vz = dz / 20.0; - - if largest_distance < 1.0 { - // 1 / sqrt(x) increases exponentially as x approaches 0 - - vx /= largest_distance.sqrt(); - vz /= largest_distance.sqrt(); - } else { - vx /= largest_distance; - vz /= largest_distance; - } - - let reaction = &mut *reaction.0.get(); - - const MULT_FACTOR: f64 = 2.0; - - reaction.velocity.x -= vx * MULT_FACTOR; - reaction.velocity.z -= vz * MULT_FACTOR; - - // todo: more efficient to do this OR - // more efficient to just have par iter - // other.x_velocity += vx; - // other.z_velocity += vz; - } - } -} From 6265204590b039c37961e9734ddc8d710090cc0b Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Wed, 27 Mar 2024 09:36:40 -0500 Subject: [PATCH 06/13] stash --- quadtree/src/lib.rs | 78 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index 4a270321..66f20120 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -175,7 +175,7 @@ fn build_impl( type SimpleQuadtree = Quadtree<()>; -impl Quadtree { +impl Quadtree { #[must_use] pub const fn aaabb(&self) -> &Aabb { &self.aabb @@ -291,6 +291,56 @@ impl Quadtree { result } + #[must_use] + pub fn insert(&self, point: Vec2) -> Option { + self.insert_recursive(self.root, &self.aabb, point) + } + + fn insert_recursive( + &self, + node: OptionalIdx, + node_bbox: &Aabb, + point: Vec2, + ) -> Option { + let Some(node_idx) = node.inner() else { + return None; + }; + + if !node_bbox.contains(point) { + return None; + } + + if self.is_leaf(node_idx).unwrap_or(false) { + return Some(node_idx); + } + + let center = node_bbox.mid(); + let child_bboxes = [ + Aabb::new(node_bbox.min, center), + Aabb::new( + Vec2::new(center.x, node_bbox.min.y), + Vec2::new(node_bbox.max.x, center.y), + ), + Aabb::new( + Vec2::new(node_bbox.min.x, center.y), + Vec2::new(center.x, node_bbox.max.y), + ), + Aabb::new(center, node_bbox.max), + ]; + + let node = self.get_node(node_idx).unwrap(); + for (i, &child) in node.children.iter().flatten().enumerate() { + #[allow(clippy::indexing_slicing)] + if let Some(child_node_idx) = + self.insert_recursive(child, &child_bboxes[i], point) + { + return Some(child_node_idx); + } + } + + None + } + fn query_bbox_recursive( &self, node: OptionalIdx, @@ -335,27 +385,31 @@ impl Quadtree { self.query_bbox_recursive(child, &child_bboxes[i], query_bbox, result); } } - + pub fn move_point(&mut self, node_idx: Idx, local_idx: usize, new_point: Vec2) { let mut node = self.nodes.get(node_idx as usize).unwrap(); let range = self.points_range_for(node_idx).unwrap(); - + let points = &mut self.points[range]; - + if node.aabb.contains(new_point) { points[local_idx] = new_point; return; } - + + // up cycle // we need to expand the node - - loop { - - } - - + let (up_idx, up_aabb) = loop { + // todo: expand if None + let parent_idx = node.parent().unwrap(); + let parent = self.nodes.get(parent_idx as usize).unwrap(); + let parent_aabb = &parent.aabb; + if parent_aabb.contains(new_point) { + break (parent_idx, parent_aabb); + } + }; - + let idx = self.insert_recursive(OptionalIdx::some(up_idx), up_aabb, new_point); } } From 8a8136f81b1828bd58c096213dc5984b31b123b3 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Wed, 27 Mar 2024 21:45:10 -0500 Subject: [PATCH 07/13] more work on quadtree --- quadtree/src/iter.rs | 12 +-- quadtree/src/lib.rs | 20 ++--- quadtree/src/rebuild.rs | 191 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 quadtree/src/rebuild.rs diff --git a/quadtree/src/iter.rs b/quadtree/src/iter.rs index 5c010643..53f6f9fd 100644 --- a/quadtree/src/iter.rs +++ b/quadtree/src/iter.rs @@ -1,19 +1,19 @@ use crate::{idx::Idx, Quadtree}; -pub struct LeafNodes<'a, T> { - tree: &'a Quadtree, +pub struct LeafNodes<'a> { + tree: &'a Quadtree, stack: Vec, } -impl<'a, T> LeafNodes<'a, T> { +impl<'a> LeafNodes<'a> { #[must_use] - pub(crate) fn new(tree: &'a Quadtree, root: Idx) -> Self { + pub(crate) fn new(tree: &'a Quadtree, root: Idx) -> Self { let stack = vec![root]; Self { tree, stack } } #[must_use] - pub(crate) const fn empty(tree: &'a Quadtree) -> Self { + pub(crate) const fn empty(tree: &'a Quadtree) -> Self { Self { tree, stack: Vec::new(), @@ -21,7 +21,7 @@ impl<'a, T> LeafNodes<'a, T> { } } -impl<'a, T> Iterator for LeafNodes<'a, T> { +impl<'a> Iterator for LeafNodes<'a> { type Item = Idx; #[allow(clippy::unwrap_in_result)] diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index 66f20120..de0b4c5e 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(unused)] // https://lisyarus.github.io/blog/programming/2022/12/21/quadtrees.html // https://snorrwe.onrender.com/posts/morton-table/ @@ -14,6 +15,7 @@ mod aaab; mod idx; pub mod iter; mod nearest; +mod rebuild; #[derive(Debug)] pub struct Node { @@ -49,17 +51,17 @@ impl Default for Node { OptionalIdx::NONE, ]], parent: OptionalIdx::NONE, + aabb: Aabb::default(), } } } -pub struct Quadtree { +pub struct Quadtree { aabb: Aabb, min_area: f64, root: OptionalIdx, nodes: Vec, points: Vec, - data: Vec, node_points_begin: Vec, } @@ -67,8 +69,8 @@ pub type IndexSlice = Range; #[allow(clippy::indexing_slicing)] #[allow(unused)] -fn build_impl( - tree: &mut Quadtree, +fn build_impl( + tree: &mut Quadtree, bbox: Aabb, slice: IndexSlice, parent: OptionalIdx, @@ -173,9 +175,7 @@ fn build_impl( OptionalIdx::some(result) } -type SimpleQuadtree = Quadtree<()>; - -impl Quadtree { +impl Quadtree { #[must_use] pub const fn aaabb(&self) -> &Aabb { &self.aabb @@ -201,6 +201,7 @@ impl Quadtree { OptionalIdx::NONE, ]], parent: parent_idx, + aabb: Default::default(), }); self.node_points_begin.push(0); @@ -247,7 +248,7 @@ impl Quadtree { } #[must_use] - pub fn leafs(&self) -> iter::LeafNodes { + pub fn leafs(&self) -> iter::LeafNodes { #[allow(clippy::option_if_let_else)] match self.root.inner() { None => iter::LeafNodes::empty(self), @@ -256,7 +257,7 @@ impl Quadtree { } #[must_use] - pub fn build_with_min_area(points: Vec, data: Vec, min_area: f64) -> Self { + pub fn build_with_min_area(points: Vec, min_area: f64) -> Self { let aabb = Aabb::from_points(&points); let len = points.len(); @@ -267,7 +268,6 @@ impl Quadtree { root: OptionalIdx::NONE, nodes: vec![], points, - data, node_points_begin: vec![], }; diff --git a/quadtree/src/rebuild.rs b/quadtree/src/rebuild.rs new file mode 100644 index 00000000..08c30f91 --- /dev/null +++ b/quadtree/src/rebuild.rs @@ -0,0 +1,191 @@ +use std::cmp::Reverse; + +#[derive(Copy, Clone)] +struct MoveElement { + remove_from_idx: usize, + insert_to_idx: usize, +} + +struct OrderedEvents { + by_removal: Vec, + by_insertion: Vec, +} + +impl From> for OrderedEvents { + fn from(changes: Vec) -> Self { + // sort by insert_to_idx + let mut by_removal = changes.clone(); + by_removal.sort_by_key(|x| Reverse(x.remove_from_idx)); + + let mut by_insertion = changes; + by_insertion.sort_by_key(|x| Reverse(x.insert_to_idx)); + + Self { + by_removal, + by_insertion, + } + } +} + +enum Event { + Removal(usize), + Insert { from: usize, to: usize }, +} + +impl Iterator for OrderedEvents { + type Item = Event; + + fn next(&mut self) -> Option { + let soonest_removal = self.by_removal.last().copied(); + let soonest_insertion = self.by_insertion.last().copied(); + + match (soonest_removal, soonest_insertion) { + (Some(removal), None) => { + self.by_removal.pop(); + Some(Event::Removal(removal.remove_from_idx)) + } + (None, Some(insertion)) => { + self.by_insertion.pop(); + let from = insertion.remove_from_idx; + let to = insertion.insert_to_idx; + Some(Event::Insert { from, to }) + } + (Some(removal), Some(insertion)) => { + if removal.remove_from_idx < insertion.insert_to_idx { + self.by_removal.pop(); + Some(Event::Removal(removal.remove_from_idx)) + } else { + self.by_insertion.pop(); + let from = insertion.remove_from_idx; + let to = insertion.insert_to_idx; + Some(Event::Insert { from, to }) + } + } + (None, None) => None, + } + } +} + +#[allow(clippy::indexing_slicing)] +fn rebuild_vec( + input: Vec, + mut changes: Vec, +) -> Vec { + let len = input.len(); + let mut result = Vec::with_capacity(len); + let mut ordered_events = OrderedEvents::from(changes); + + let mut idx = 0; + let mut offset = 0; + + let mut src_idx = 0; + + for event in ordered_events { + match event { + Event::Removal(removal) => { + result.extend_from_slice(&input[src_idx..removal]); + src_idx = removal + 1; + } + Event::Insert { from, to } => { + result.extend_from_slice(&input[src_idx..=to]); + let elem = input[from]; + result.push(elem); + src_idx = to + 1; + } + } + } + + result.extend_from_slice(&input[src_idx..]); + + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_no_changes() { + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![]; + let expected = vec![1, 2, 3, 4, 5]; + assert_eq!(rebuild_vec(input, changes), expected); + } + + #[test] + fn test_single_move() { + // 0 1 2 3 4 + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![MoveElement { + remove_from_idx: 1, + insert_to_idx: 3, + }]; + let expected = vec![1, 3, 4, 2, 5]; + assert_eq!(rebuild_vec(input, changes), expected); + } + + #[test] + fn test_multiple_moves() { + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![ + MoveElement { + remove_from_idx: 1, + insert_to_idx: 3, + }, + MoveElement { + remove_from_idx: 4, + insert_to_idx: 0, + }, + ]; + let expected = vec![1, 5, 3, 4, 2]; + assert_eq!(rebuild_vec(input, changes), expected); + } + + #[test] + fn test_duplicate_insert_indices() { + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![ + MoveElement { + remove_from_idx: 1, + insert_to_idx: 3, + }, + MoveElement { + remove_from_idx: 4, + insert_to_idx: 3, + }, + ]; + let expected = vec![1, 3, 4, 5, 2]; + assert_eq!(rebuild_vec(input, changes), expected); + } + + #[test] + fn test_large_input() { + let input = (0..10).collect::>(); + let changes = vec![ + MoveElement { + remove_from_idx: 1, + insert_to_idx: 5, + }, + MoveElement { + remove_from_idx: 2, + insert_to_idx: 8, + }, + MoveElement { + remove_from_idx: 3, + insert_to_idx: 9, + }, + ]; + + assert_eq!(rebuild_vec(input, changes), vec![ + 0, 4, 5, 1, 6, 7, 8, 2, 9, 3 + ]); + } + + #[test] + fn test_empty_input() { + let input: Vec = vec![]; + let changes = vec![]; + let expected = vec![]; + assert_eq!(rebuild_vec(input, changes), expected); + } +} From 96fe95c2f098fbb225b97fcb2c501b5f585c6f85 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Wed, 27 Mar 2024 21:47:27 -0500 Subject: [PATCH 08/13] stash --- quadtree/src/lib.rs | 2 +- quadtree/src/rebuild.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index de0b4c5e..bf0a4eb0 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -201,7 +201,7 @@ impl Quadtree { OptionalIdx::NONE, ]], parent: parent_idx, - aabb: Default::default(), + aabb: Aabb::default(), }); self.node_points_begin.push(0); diff --git a/quadtree/src/rebuild.rs b/quadtree/src/rebuild.rs index 08c30f91..3df0e34a 100644 --- a/quadtree/src/rebuild.rs +++ b/quadtree/src/rebuild.rs @@ -67,17 +67,15 @@ impl Iterator for OrderedEvents { } #[allow(clippy::indexing_slicing)] +#[allow(dead_code)] fn rebuild_vec( input: Vec, - mut changes: Vec, + changes: Vec, ) -> Vec { let len = input.len(); let mut result = Vec::with_capacity(len); - let mut ordered_events = OrderedEvents::from(changes); - - let mut idx = 0; - let mut offset = 0; - + let ordered_events = OrderedEvents::from(changes); + let mut src_idx = 0; for event in ordered_events { @@ -140,7 +138,7 @@ mod tests { let expected = vec![1, 5, 3, 4, 2]; assert_eq!(rebuild_vec(input, changes), expected); } - + #[test] fn test_duplicate_insert_indices() { let input = vec![1, 2, 3, 4, 5]; From 7f20db4bf7c216d4ed12864285b5c631d3f2ce0c Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Wed, 27 Mar 2024 21:50:51 -0500 Subject: [PATCH 09/13] stash --- quadtree/src/aaab.rs | 2 +- quadtree/src/lib.rs | 11 ++--------- quadtree/src/rebuild.rs | 8 ++------ server/src/lib.rs | 3 ++- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/quadtree/src/aaab.rs b/quadtree/src/aaab.rs index 925b8aee..ac4453c6 100644 --- a/quadtree/src/aaab.rs +++ b/quadtree/src/aaab.rs @@ -59,7 +59,7 @@ impl Aabb { pub const fn new(min: Vec2, max: Vec2) -> Self { Self { min, max } } - + pub fn area(&self) -> f64 { let size = self.max - self.min; size.x as f64 * size.y as f64 diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index bf0a4eb0..cdf5c105 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -296,12 +296,7 @@ impl Quadtree { self.insert_recursive(self.root, &self.aabb, point) } - fn insert_recursive( - &self, - node: OptionalIdx, - node_bbox: &Aabb, - point: Vec2, - ) -> Option { + fn insert_recursive(&self, node: OptionalIdx, node_bbox: &Aabb, point: Vec2) -> Option { let Some(node_idx) = node.inner() else { return None; }; @@ -331,9 +326,7 @@ impl Quadtree { let node = self.get_node(node_idx).unwrap(); for (i, &child) in node.children.iter().flatten().enumerate() { #[allow(clippy::indexing_slicing)] - if let Some(child_node_idx) = - self.insert_recursive(child, &child_bboxes[i], point) - { + if let Some(child_node_idx) = self.insert_recursive(child, &child_bboxes[i], point) { return Some(child_node_idx); } } diff --git a/quadtree/src/rebuild.rs b/quadtree/src/rebuild.rs index 3df0e34a..027c050c 100644 --- a/quadtree/src/rebuild.rs +++ b/quadtree/src/rebuild.rs @@ -13,7 +13,6 @@ struct OrderedEvents { impl From> for OrderedEvents { fn from(changes: Vec) -> Self { - // sort by insert_to_idx let mut by_removal = changes.clone(); by_removal.sort_by_key(|x| Reverse(x.remove_from_idx)); @@ -68,14 +67,11 @@ impl Iterator for OrderedEvents { #[allow(clippy::indexing_slicing)] #[allow(dead_code)] -fn rebuild_vec( - input: Vec, - changes: Vec, -) -> Vec { +fn rebuild_vec(input: Vec, changes: Vec) -> Vec { let len = input.len(); let mut result = Vec::with_capacity(len); let ordered_events = OrderedEvents::from(changes); - + let mut src_idx = 0; for event in ordered_events { diff --git a/server/src/lib.rs b/server/src/lib.rs index 6b41d933..5752cd5f 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -339,9 +339,10 @@ impl FullEntityPose { self.position += vec; self.bounding = self.bounding.move_by(vec); } + /// # Safety /// This is only safe is this is not done in tandem with another `EntityReaction` -// #[instrument(skip_all)] + // #[instrument(skip_all)] pub unsafe fn apply_entity_collision(&self, other: &Self, reaction: &EntityReaction) { let dx = other.position.x - self.position.x; let dz = other.position.z - self.position.z; From 947e035bd546f2a3a3944f8e3e0b54748a651978 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Thu, 28 Mar 2024 19:02:27 -0500 Subject: [PATCH 10/13] stash --- Cargo.lock | 1 + quadtree/Cargo.toml | 1 + quadtree/src/lib.rs | 10 ++--- quadtree/src/rebuild.rs | 91 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 90 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf1b98a4..c65de64c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,6 +1452,7 @@ version = "0.1.0" dependencies = [ "glam 0.27.0", "itertools", + "num-traits", ] [[package]] diff --git a/quadtree/Cargo.toml b/quadtree/Cargo.toml index 383604c7..73e410d4 100644 --- a/quadtree/Cargo.toml +++ b/quadtree/Cargo.toml @@ -9,6 +9,7 @@ publish = false [dependencies] glam = "0.27.0" itertools = "0.12.1" +num-traits = "0.2.18" [lints.rust] diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index cdf5c105..7d8d219a 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -296,10 +296,9 @@ impl Quadtree { self.insert_recursive(self.root, &self.aabb, point) } + #[allow(clippy::unwrap_in_result)] fn insert_recursive(&self, node: OptionalIdx, node_bbox: &Aabb, point: Vec2) -> Option { - let Some(node_idx) = node.inner() else { - return None; - }; + let node_idx = node.inner()?; if !node_bbox.contains(point) { return None; @@ -379,8 +378,9 @@ impl Quadtree { } } + #[allow(clippy::missing_panics_doc, clippy::indexing_slicing)] pub fn move_point(&mut self, node_idx: Idx, local_idx: usize, new_point: Vec2) { - let mut node = self.nodes.get(node_idx as usize).unwrap(); + let mut node = &self.nodes[node_idx as usize]; let range = self.points_range_for(node_idx).unwrap(); let points = &mut self.points[range]; @@ -395,7 +395,7 @@ impl Quadtree { let (up_idx, up_aabb) = loop { // todo: expand if None let parent_idx = node.parent().unwrap(); - let parent = self.nodes.get(parent_idx as usize).unwrap(); + let parent = &self.nodes[parent_idx as usize]; let parent_aabb = &parent.aabb; if parent_aabb.contains(new_point) { break (parent_idx, parent_aabb); diff --git a/quadtree/src/rebuild.rs b/quadtree/src/rebuild.rs index 027c050c..75cc16c0 100644 --- a/quadtree/src/rebuild.rs +++ b/quadtree/src/rebuild.rs @@ -1,4 +1,6 @@ -use std::cmp::Reverse; +use std::{cmp::Reverse, iter::Peekable, ops::AddAssign}; + +use num_traits::PrimInt; #[derive(Copy, Clone)] struct MoveElement { @@ -65,26 +67,77 @@ impl Iterator for OrderedEvents { } } +struct TrackingUpdate { + tracking: I, +} + +impl<'a, T, I> TrackingUpdate> +where + T: PrimInt + AddAssign + 'a, + I: Iterator + 'a, +{ + fn update(&mut self, until_idx: usize, offset: isize) { + while let Some(idx) = self.tracking.peek() { + if idx.to_usize().unwrap() > until_idx { + break; + } + + let offset = T::from(offset).unwrap(); + + *self.tracking.next().unwrap() += offset; + } + } + + fn new(tracking: I) -> Self +where { + Self { + tracking: tracking.peekable(), + } + } +} + #[allow(clippy::indexing_slicing)] #[allow(dead_code)] -fn rebuild_vec(input: Vec, changes: Vec) -> Vec { +fn rebuild_vec( + input: Vec, + changes: Vec, + tracking: &mut [Idx], // 3 9 10 +) -> Vec +where + T: Copy, + Idx: PrimInt + AddAssign, +{ + // todo: assert tracking sorted + let len = input.len(); let mut result = Vec::with_capacity(len); let ordered_events = OrderedEvents::from(changes); + let mut tracking = tracking.iter_mut(); + let mut tracking = TrackingUpdate::new(tracking); + let mut src_idx = 0; + let mut offset = 0isize; for event in ordered_events { match event { Event::Removal(removal) => { result.extend_from_slice(&input[src_idx..removal]); src_idx = removal + 1; + + tracking.update(removal, offset); + + offset -= 1; } Event::Insert { from, to } => { result.extend_from_slice(&input[src_idx..=to]); let elem = input[from]; result.push(elem); src_idx = to + 1; + + tracking.update(to, offset); + + offset += 1; } } } @@ -102,8 +155,12 @@ mod tests { fn test_no_changes() { let input = vec![1, 2, 3, 4, 5]; let changes = vec![]; + let mut tracking = vec![0]; + let expected = vec![1, 2, 3, 4, 5]; - assert_eq!(rebuild_vec(input, changes), expected); + + assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(tracking, vec![0]); } #[test] @@ -114,8 +171,11 @@ mod tests { remove_from_idx: 1, insert_to_idx: 3, }]; + let mut tracking = vec![0, 1, 2, 3, 4]; + let expected = vec![1, 3, 4, 2, 5]; - assert_eq!(rebuild_vec(input, changes), expected); + assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(tracking, vec![0, 1, 1, 2, 4]); } #[test] @@ -131,8 +191,10 @@ mod tests { insert_to_idx: 0, }, ]; + let mut tracking = vec![0, 1, 2, 3, 4]; let expected = vec![1, 5, 3, 4, 2]; - assert_eq!(rebuild_vec(input, changes), expected); + assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(tracking, vec![0, 2, 2, 3, 5]); } #[test] @@ -148,8 +210,10 @@ mod tests { insert_to_idx: 3, }, ]; + let mut tracking = vec![0, 1, 2, 3, 4]; let expected = vec![1, 3, 4, 5, 2]; - assert_eq!(rebuild_vec(input, changes), expected); + assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(tracking, vec![0, 1, 1, 2, 5]); } #[test] @@ -169,17 +233,28 @@ mod tests { insert_to_idx: 9, }, ]; + let mut tracking = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + assert_eq!( + rebuild_vec(input.clone(), changes.clone(), &mut tracking), + vec![0, 4, 5, 1, 6, 7, 8, 2, 9, 3] + ); + assert_eq!(tracking, vec![0, 1, 1, 1, 1, 2, 4, 5, 6, 8, 10]); - assert_eq!(rebuild_vec(input, changes), vec![ + let mut tracking = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + assert_eq!(rebuild_vec(input, changes, &mut tracking), vec![ 0, 4, 5, 1, 6, 7, 8, 2, 9, 3 ]); + assert_eq!(tracking, vec![0, 1, 1, 1, 1, 2, 4, 5, 6, 8]); } #[test] fn test_empty_input() { let input: Vec = vec![]; let changes = vec![]; + let mut tracking: Vec = vec![]; let expected = vec![]; - assert_eq!(rebuild_vec(input, changes), expected); + assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(tracking, vec![]); } } From 5ff427b59b0077ea36e6e16521e555945ad3e9c7 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Thu, 28 Mar 2024 23:56:33 -0500 Subject: [PATCH 11/13] stash --- .github/workflows/build.yml | 2 +- Cargo.lock | 2 + Cargo.toml | 8 +- quadtree/Cargo.toml | 71 +++------------ quadtree/benches/rebuild.rs | 55 ++++++++++++ quadtree/src/aaab.rs | 16 ++-- quadtree/src/idx.rs | 6 -- quadtree/src/lib.rs | 51 ++++------- quadtree/src/rebuild.rs | 167 ++++++++++++++++++++++++++++++++---- server/Cargo.toml | 1 - 10 files changed, 244 insertions(+), 135 deletions(-) create mode 100644 quadtree/benches/rebuild.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 768eac68..0462dcd8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,6 +94,6 @@ jobs: components: clippy - name: Clippy check - run: cargo clippy --workspace --tests --examples --all-features + run: cargo clippy --workspace --tests --examples --all-features -- -D warnings diff --git a/Cargo.lock b/Cargo.lock index c65de64c..3418e2db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1450,9 +1450,11 @@ dependencies = [ name = "quadtree" version = "0.1.0" dependencies = [ + "divan", "glam 0.27.0", "itertools", "num-traits", + "rand 0.8.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f9113133..18011997 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ "quadtree", "chunk", "generator-build", - "generator" + "generator", ] [profile.dev] @@ -19,19 +19,15 @@ opt-level = 1 opt-level = 3 [profile.release] -#debug = true lto = "fat" codegen-units = 1 -#strip = false -#panic = "abort" [profile.bench] -#debug = true lto = "fat" codegen-units = 1 -#panic = "abort" [workspace.dependencies] chunk = { path = "chunk" } generator-build = { path = "generator-build" } generator = { path = "generator" } +quadtree = { path = "quadtree" } diff --git a/quadtree/Cargo.toml b/quadtree/Cargo.toml index 73e410d4..d5263f1a 100644 --- a/quadtree/Cargo.toml +++ b/quadtree/Cargo.toml @@ -11,70 +11,21 @@ glam = "0.27.0" itertools = "0.12.1" num-traits = "0.2.18" +[dev-dependencies] +divan = "0.1.14" +rand = "0.8.5" -[lints.rust] -warnings = "deny" +[[bench]] +name = "rebuild" +harness = false [lints.clippy] -cargo_common_metadata = "allow" -negative_feature_names = "deny" -redundant_feature_names = "deny" -wildcard_dependencies = "deny" - -restriction = { level = "deny", priority = -1 } -missing_docs_in_private_items = "allow" -question_mark_used = "allow" -print_stdout = "allow" -implicit_return = "allow" -shadow_reuse = "allow" -absolute_paths = "allow" -use_debug = "allow" -unwrap_used = "allow" -std_instead_of_alloc = "allow" # consider denying -default_numeric_fallback = "allow" -as_conversions = "allow" -arithmetic_side_effects = "allow" -shadow_unrelated = "allow" -unseparated_literal_suffix = "allow" -else_if_without_else = "allow" -float_arithmetic = "allow" -single_call_fn = "allow" -missing_inline_in_public_items = "allow" -exhaustive_structs = "allow" -pub_use = "allow" -let_underscore_untyped = "allow" -infinite_loop = "allow" -single_char_lifetime_names = "allow" -min_ident_chars = "allow" -std_instead_of_core = "allow" -let_underscore_must_use = "allow" -pattern_type_mismatch = "allow" -print_stderr = "allow" -missing_assert_message = "allow" -shadow_same = "allow" -mod_module_files = "deny" -self_named_module_files = "allow" -missing_trait_methods = "allow" - -complexity = { level = "deny", priority = -1 } +complexity = "deny" nursery = { level = "deny", priority = -1 } -future_not_send = "allow" redundant_pub_crate = "allow" -pedantic = { level = "deny", priority = -1 } -uninlined_format_args = "allow" # consider denying; this is allowed because Copilot often generates code that triggers this lint -needless_pass_by_value = "allow" # consider denying -cast_lossless = "allow" -cast_possible_truncation = "allow" # consider denying -cast_precision_loss = "allow" # consider denying -missing_errors_doc = "allow" # consider denying -struct_excessive_bools = "allow" -wildcard_imports = "allow" - -perf = { level = "deny", priority = -1 } - -style = { level = "deny", priority = -1 } - -suspicious = { level = "deny", priority = -1 } -blanket_clippy_restriction_lints = "allow" +pedantic = "deny" +perf = "deny" +style = "deny" +suspicious = "deny" diff --git a/quadtree/benches/rebuild.rs b/quadtree/benches/rebuild.rs new file mode 100644 index 00000000..f7b3567d --- /dev/null +++ b/quadtree/benches/rebuild.rs @@ -0,0 +1,55 @@ +use divan::{AllocProfiler, Bencher}; +use rand::prelude::SliceRandom; + +#[global_allocator] +static ALLOC: AllocProfiler = AllocProfiler::system(); + +fn main() { + divan::main(); +} + +// Register a `fibonacci` function and benchmark it over multiple cases. + +const LENS: &[usize] = &[8, 64, 1024, 4096, 16384]; + +#[divan::bench( + args = LENS, +)] +fn rebuild_single(bencher: Bencher, len: usize) { + let v = (0..len).collect::>(); + + let changes = vec![quadtree::rebuild::MoveElement { + remove_from_idx: 1, + insert_to_idx: 3, + }]; + + bencher.bench(|| { + quadtree::rebuild::apply_vec(&v, &changes, &mut [0, 1, 2, 3, 4]); + }); +} + +#[divan::bench( +args = LENS, +)] +fn rebuild_every(bencher: Bencher, len: usize) { + let v = (0..len).collect::>(); + + let arr = 0..len; + + // shuffle the array + let mut arr = arr.collect::>(); + arr.shuffle(&mut rand::thread_rng()); + + let changes = arr + .iter() + .enumerate() + .map(|(i, &x)| quadtree::rebuild::MoveElement { + remove_from_idx: i, + insert_to_idx: x, + }) + .collect::>(); + + bencher.counter(len).bench(|| { + quadtree::rebuild::apply_vec(&v, &changes, &mut [0, 1, 2, 3, 4]); + }); +} diff --git a/quadtree/src/aaab.rs b/quadtree/src/aaab.rs index ac4453c6..f88368b0 100644 --- a/quadtree/src/aaab.rs +++ b/quadtree/src/aaab.rs @@ -27,10 +27,9 @@ impl Containable for Vec2 { let mut contains = 0b1u8; - #[allow(clippy::indexing_slicing)] for i in 0..2 { - contains &= (elem[i] >= min[i]) as u8; - contains &= (elem[i] <= max[i]) as u8; + contains &= u8::from(elem[i] >= min[i]); + contains &= u8::from(elem[i] <= max[i]); } contains == 1 @@ -46,10 +45,9 @@ impl Containable for Aabb { let mut contains = 0b1u8; - #[allow(clippy::indexing_slicing)] for i in 0..2 { - contains &= (other_min[i] >= this_min[i]) as u8; - contains &= (other_max[i] <= this_max[i]) as u8; + contains &= u8::from(other_min[i] >= this_min[i]); + contains &= u8::from(other_max[i] <= this_max[i]); } contains == 1 } @@ -62,7 +60,7 @@ impl Aabb { pub fn area(&self) -> f64 { let size = self.max - self.min; - size.x as f64 * size.y as f64 + f64::from(size.x) * f64::from(size.y) } pub fn mid(&self) -> Vec2 { @@ -95,8 +93,8 @@ impl Aabb { #[allow(clippy::indexing_slicing)] for i in 0..2 { - intersects &= (this_min[i] <= other_max[i]) as u8; - intersects &= (this_max[i] >= other_min[i]) as u8; + intersects &= u8::from(this_min[i] <= other_max[i]); + intersects &= u8::from(this_max[i] >= other_min[i]); } intersects == 1 diff --git a/quadtree/src/idx.rs b/quadtree/src/idx.rs index 754b1050..1702e0fd 100644 --- a/quadtree/src/idx.rs +++ b/quadtree/src/idx.rs @@ -58,9 +58,3 @@ impl OptionalIdx { Self(id) } } - -impl From for OptionalIdx { - fn from(value: usize) -> Self { - Self(value as Idx) - } -} diff --git a/quadtree/src/lib.rs b/quadtree/src/lib.rs index 7d8d219a..998bb720 100644 --- a/quadtree/src/lib.rs +++ b/quadtree/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(unused)] // https://lisyarus.github.io/blog/programming/2022/12/21/quadtrees.html // https://snorrwe.onrender.com/posts/morton-table/ @@ -15,7 +14,7 @@ mod aaab; mod idx; pub mod iter; mod nearest; -mod rebuild; +pub mod rebuild; #[derive(Debug)] pub struct Node { @@ -83,55 +82,38 @@ fn build_impl( let start = slice.start as usize; let points = &mut tree.points[slice.start as usize..slice.end as usize]; - // println!("TOP; HAVE POINTS {:?}", points); - if slice.len() == 1 { - tree.node_points_begin[result as usize] = start as Idx; + tree.node_points_begin[result as usize] = Idx::try_from(start).unwrap(); return OptionalIdx::some(result); } if bbox.area() < tree.min_area { - tree.node_points_begin[result as usize] = start as Idx; + tree.node_points_begin[result as usize] = Idx::try_from(start).unwrap(); return OptionalIdx::some(result); } // equal if points.iter().all(|p| *p == points[0]) { - tree.node_points_begin[result as usize] = start as Idx; + tree.node_points_begin[result as usize] = Idx::try_from(start).unwrap(); return OptionalIdx::some(result); } - tree.node_points_begin[result as usize] = start as Idx; + tree.node_points_begin[result as usize] = Idx::try_from(start).unwrap(); let center = bbox.mid(); let bottom = |p: &Vec2| p.y < center.y; let left = |p: &Vec2| p.x < center.x; - // len - // println!("points: {}", points.len()); - // println!("{:?}", points); - // todo: why need to &mut points[..]? let split_y = itertools::partition(&mut *points, bottom); - // println!(); - // println!("split_y: {} @ {}", split_y, center.y); - // println!("BOTTOM {:?}", &points[..split_y]); - // println!("TOP {:?}", &points[split_y..]); - debug_assert!(points[..split_y].iter().all(|p| p.y < center.y)); debug_assert!(points[split_y..].iter().all(|p| p.y >= center.y)); - let split_x_lower = itertools::partition(&mut points[..split_y], left) as Idx; - let split_x_upper = itertools::partition(&mut points[split_y..], left) as Idx; - - // println!(); - // println!("split_x_lower: {} @ {}", split_x_lower, center.x); - // println!("LEFT {:?}", &points[..split_x_lower as usize]); - // println!("RIGHT {:?}", &points[split_x_lower as usize..split_y]); - - let split_y = split_y as Idx; + let split_x_lower = Idx::try_from(itertools::partition(&mut points[..split_y], left)).unwrap(); + let split_x_upper = Idx::try_from(itertools::partition(&mut points[split_y..], left)).unwrap(); + let split_y = Idx::try_from(split_y).unwrap(); let result_some = OptionalIdx::some(result); @@ -140,9 +122,6 @@ fn build_impl( let child10_idx = (slice.start + split_y)..(slice.start + split_y + split_x_upper); let child11_idx = (slice.start + split_y + split_x_upper)..slice.end; - // println!("indices \n{:?}\n{:?}\n{:?}\n{:?}", child00_idx, child01_idx, child10_idx, - // child11_idx); - let child00 = build_impl(tree, Aabb::new(bbox.min, center), child00_idx, result_some); tree.get_node_mut(result).unwrap().children[0][0] = child00; @@ -206,7 +185,7 @@ impl Quadtree { self.node_points_begin.push(0); - result as Idx + Idx::try_from(result).unwrap() } #[must_use] @@ -256,8 +235,12 @@ impl Quadtree { } } + /// # Panics + /// If `points.len()` is greater than `u16::MAX` #[must_use] pub fn build_with_min_area(points: Vec, min_area: f64) -> Self { + debug_assert!(points.len() <= Idx::MAX as usize); + let aabb = Aabb::from_points(&points); let len = points.len(); @@ -271,10 +254,12 @@ impl Quadtree { node_points_begin: vec![], }; - result.root = build_impl(&mut result, aabb, 0..len as Idx, OptionalIdx::NONE); + let len = Idx::try_from(len).unwrap(); + + result.root = build_impl(&mut result, aabb, 0..len, OptionalIdx::NONE); // to eliminate edge case on right edge - result.node_points_begin.push(result.points.len() as Idx); + result.node_points_begin.push(len); result } @@ -380,7 +365,7 @@ impl Quadtree { #[allow(clippy::missing_panics_doc, clippy::indexing_slicing)] pub fn move_point(&mut self, node_idx: Idx, local_idx: usize, new_point: Vec2) { - let mut node = &self.nodes[node_idx as usize]; + let node = &self.nodes[node_idx as usize]; let range = self.points_range_for(node_idx).unwrap(); let points = &mut self.points[range]; diff --git a/quadtree/src/rebuild.rs b/quadtree/src/rebuild.rs index 75cc16c0..745eab9c 100644 --- a/quadtree/src/rebuild.rs +++ b/quadtree/src/rebuild.rs @@ -1,11 +1,12 @@ use std::{cmp::Reverse, iter::Peekable, ops::AddAssign}; +use itertools::Itertools; use num_traits::PrimInt; #[derive(Copy, Clone)] -struct MoveElement { - remove_from_idx: usize, - insert_to_idx: usize, +pub struct MoveElement { + pub remove_from_idx: usize, + pub insert_to_idx: usize, } struct OrderedEvents { @@ -96,24 +97,35 @@ where { } } +fn debug_assert_valid_changes(changes: &[MoveElement]) { + debug_assert!( + changes.iter().map(|x| x.remove_from_idx).all_unique(), + "removal indices must be unique" + ); + debug_assert!( + changes.iter().all(|x| x.remove_from_idx != x.insert_to_idx), + "removal and insertion indices must be different" + ); +} + #[allow(clippy::indexing_slicing)] #[allow(dead_code)] -fn rebuild_vec( - input: Vec, - changes: Vec, +pub fn apply_vec( + input: &[T], + changes: &[MoveElement], tracking: &mut [Idx], // 3 9 10 ) -> Vec where T: Copy, Idx: PrimInt + AddAssign, { - // todo: assert tracking sorted + debug_assert_valid_changes(changes); let len = input.len(); let mut result = Vec::with_capacity(len); - let ordered_events = OrderedEvents::from(changes); + let ordered_events = OrderedEvents::from(changes.to_vec()); - let mut tracking = tracking.iter_mut(); + let tracking = tracking.iter_mut(); let mut tracking = TrackingUpdate::new(tracking); let mut src_idx = 0; @@ -122,6 +134,8 @@ where for event in ordered_events { match event { Event::Removal(removal) => { + debug_assert!(removal < len, "attempt to move element from invalid index"); + result.extend_from_slice(&input[src_idx..removal]); src_idx = removal + 1; @@ -130,6 +144,9 @@ where offset -= 1; } Event::Insert { from, to } => { + debug_assert!(from < len, "attempt to move element from invalid index"); + debug_assert!(to < len, "attempt to move element to invalid index"); + result.extend_from_slice(&input[src_idx..=to]); let elem = input[from]; result.push(elem); @@ -159,7 +176,7 @@ mod tests { let expected = vec![1, 2, 3, 4, 5]; - assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); assert_eq!(tracking, vec![0]); } @@ -174,7 +191,7 @@ mod tests { let mut tracking = vec![0, 1, 2, 3, 4]; let expected = vec![1, 3, 4, 2, 5]; - assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); assert_eq!(tracking, vec![0, 1, 1, 2, 4]); } @@ -193,7 +210,7 @@ mod tests { ]; let mut tracking = vec![0, 1, 2, 3, 4]; let expected = vec![1, 5, 3, 4, 2]; - assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); assert_eq!(tracking, vec![0, 2, 2, 3, 5]); } @@ -212,7 +229,7 @@ mod tests { ]; let mut tracking = vec![0, 1, 2, 3, 4]; let expected = vec![1, 3, 4, 5, 2]; - assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); assert_eq!(tracking, vec![0, 1, 1, 2, 5]); } @@ -235,14 +252,13 @@ mod tests { ]; let mut tracking = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - assert_eq!( - rebuild_vec(input.clone(), changes.clone(), &mut tracking), - vec![0, 4, 5, 1, 6, 7, 8, 2, 9, 3] - ); + assert_eq!(apply_vec(&input, &changes.clone(), &mut tracking), vec![ + 0, 4, 5, 1, 6, 7, 8, 2, 9, 3 + ]); assert_eq!(tracking, vec![0, 1, 1, 1, 1, 2, 4, 5, 6, 8, 10]); let mut tracking = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - assert_eq!(rebuild_vec(input, changes, &mut tracking), vec![ + assert_eq!(apply_vec(&input, &changes, &mut tracking), vec![ 0, 4, 5, 1, 6, 7, 8, 2, 9, 3 ]); assert_eq!(tracking, vec![0, 1, 1, 1, 1, 2, 4, 5, 6, 8]); @@ -254,7 +270,120 @@ mod tests { let changes = vec![]; let mut tracking: Vec = vec![]; let expected = vec![]; - assert_eq!(rebuild_vec(input, changes, &mut tracking), expected); + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); assert_eq!(tracking, vec![]); } + + #[test] + #[should_panic(expected = "attempt to move element to invalid index")] + fn test_move_to_out_of_bounds_index() { + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![MoveElement { + remove_from_idx: 1, + insert_to_idx: 10, + }]; + let mut tracking = vec![0, 1, 2, 3, 4]; + + apply_vec(&input, &changes, &mut tracking); + } + + #[test] + #[should_panic(expected = "attempt to move element from invalid index")] + fn test_move_from_out_of_bounds_index() { + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![MoveElement { + remove_from_idx: 10, + insert_to_idx: 3, + }]; + let mut tracking = vec![0, 1, 2, 3, 4]; + + apply_vec(&input, &changes, &mut tracking); + } + + #[test] + fn test_empty_tracking() { + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![ + MoveElement { + remove_from_idx: 1, + insert_to_idx: 3, + }, + MoveElement { + remove_from_idx: 4, + insert_to_idx: 0, + }, + ]; + let mut tracking: Vec = vec![]; + let expected = vec![1, 5, 3, 4, 2]; + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); + assert_eq!(tracking, vec![]); + } + + #[test] + fn test_move_first_element_to_end() { + let input = vec![1, 2, 3, 4, 5]; + let changes = vec![MoveElement { + remove_from_idx: 0, + insert_to_idx: 4, + }]; + let mut tracking = vec![0, 1, 2, 3, 4]; + let expected = vec![2, 3, 4, 5, 1]; + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); + assert_eq!(tracking, vec![0, 0, 1, 2, 3]); + } + #[test] + fn test_swap_first_and_last_elements() { + // Initial setup with elements in a sequential order for clarity. + let input = vec![1, 2, 3, 4, 5]; + // Defining the changes to swap the first (index 0) and the last (index 4) elements. + let changes = vec![ + MoveElement { + remove_from_idx: 0, + insert_to_idx: 4, + }, + MoveElement { + remove_from_idx: 4, + insert_to_idx: 0, + }, + ]; + // Tracking vector to observe changes in indices due to swaps. + let mut tracking = vec![0, 1, 2, 3, 4]; + + // Expected result after swapping the first and last elements. + let expected = vec![5, 2, 3, 4, 1]; + // Applying the vector changes and comparing the result to the expected vector. + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); + // Expected tracking after the swaps, showing how indices should have adjusted. + assert_eq!(tracking, vec![0, 1, 2, 3, 4]); + } + + #[test] + fn test_multiple_ends() { + // Initial setup with elements in a sequential order for clarity. + let input = vec![1, 2, 3, 4, 5]; + // Defining the changes to swap the first (index 0) and the last (index 4) elements. + let changes = vec![ + MoveElement { + remove_from_idx: 0, + insert_to_idx: 4, + }, + MoveElement { + remove_from_idx: 4, + insert_to_idx: 0, + }, + MoveElement { + remove_from_idx: 3, + insert_to_idx: 0, + }, + ]; + // Tracking vector to observe changes in indices due to swaps. + let mut tracking = vec![0, 1, 2, 3, 4, 5]; + + // Expected result after swapping the first and last elements. + let expected = vec![4, 5, 2, 3, 1]; + // Applying the vector changes and comparing the result to the expected vector. + assert_eq!(apply_vec(&input, &changes, &mut tracking), expected); + // Expected tracking after the swaps, showing how indices should have adjusted. + assert_eq!(tracking, vec![0, 2, 3, 4, 4, 5]); + } } diff --git a/server/Cargo.toml b/server/Cargo.toml index 44a01ed4..32f491f9 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -62,7 +62,6 @@ warnings = "deny" [lints.clippy] # cargo cargo_common_metadata = "allow" -#multiple_crate_versions = "warn" negative_feature_names = "deny" redundant_feature_names = "deny" wildcard_dependencies = "deny" From 7cde14bc6976e4655d7077bc23352475bbe51735 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Fri, 29 Mar 2024 00:31:58 -0500 Subject: [PATCH 12/13] stash --- quadtree/benches/rebuild.rs | 29 ++++++++++++++++++++++++++++- quadtree/src/rebuild.rs | 4 ++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/quadtree/benches/rebuild.rs b/quadtree/benches/rebuild.rs index f7b3567d..3dff9537 100644 --- a/quadtree/benches/rebuild.rs +++ b/quadtree/benches/rebuild.rs @@ -10,7 +10,7 @@ fn main() { // Register a `fibonacci` function and benchmark it over multiple cases. -const LENS: &[usize] = &[8, 64, 1024, 4096, 16384]; +const LENS: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]; #[divan::bench( args = LENS, @@ -53,3 +53,30 @@ fn rebuild_every(bencher: Bencher, len: usize) { quadtree::rebuild::apply_vec(&v, &changes, &mut [0, 1, 2, 3, 4]); }); } + +// not very helpful and makes results more confusing +// #[divan::bench( +// args = LENS, +// )] +// fn rebuild_4096(bencher: Bencher, len: usize) { +// let v = (0..4096).collect::>(); +// +// let arr = 0..len; +// +// // shuffle the array +// let mut arr = arr.collect::>(); +// arr.shuffle(&mut rand::thread_rng()); +// +// let changes = arr +// .iter() +// .enumerate() +// .map(|(i, &x)| quadtree::rebuild::MoveElement { +// remove_from_idx: i, +// insert_to_idx: x, +// }) +// .collect::>(); +// +// bencher.counter(4096_usize).bench(|| { +// quadtree::rebuild::apply_vec(&v, &changes, &mut [0, 1, 2, 3, 4]); +// }); +// } diff --git a/quadtree/src/rebuild.rs b/quadtree/src/rebuild.rs index 745eab9c..f5482d37 100644 --- a/quadtree/src/rebuild.rs +++ b/quadtree/src/rebuild.rs @@ -53,7 +53,7 @@ impl Iterator for OrderedEvents { Some(Event::Insert { from, to }) } (Some(removal), Some(insertion)) => { - if removal.remove_from_idx < insertion.insert_to_idx { + if removal.remove_from_idx <= insertion.insert_to_idx { self.by_removal.pop(); Some(Event::Removal(removal.remove_from_idx)) } else { @@ -252,7 +252,7 @@ mod tests { ]; let mut tracking = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - assert_eq!(apply_vec(&input, &changes.clone(), &mut tracking), vec![ + assert_eq!(apply_vec(&input, &changes, &mut tracking), vec![ 0, 4, 5, 1, 6, 7, 8, 2, 9, 3 ]); assert_eq!(tracking, vec![0, 1, 1, 1, 1, 2, 4, 5, 6, 8, 10]); From 7d26321189238d929230de5b0e4efc8ec8fba491 Mon Sep 17 00:00:00 2001 From: Andrew Gazelka Date: Fri, 29 Mar 2024 00:57:07 -0500 Subject: [PATCH 13/13] stash --- quadtree/benches/rebuild.rs | 93 ++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/quadtree/benches/rebuild.rs b/quadtree/benches/rebuild.rs index 3dff9537..40f60916 100644 --- a/quadtree/benches/rebuild.rs +++ b/quadtree/benches/rebuild.rs @@ -1,4 +1,5 @@ use divan::{AllocProfiler, Bencher}; +use quadtree::rebuild::MoveElement; use rand::prelude::SliceRandom; #[global_allocator] @@ -10,7 +11,8 @@ fn main() { // Register a `fibonacci` function and benchmark it over multiple cases. -const LENS: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]; +// +const LENS: &[usize] = &[64, 512, 4096, 32768, 262_144]; #[divan::bench( args = LENS, @@ -54,29 +56,66 @@ fn rebuild_every(bencher: Bencher, len: usize) { }); } -// not very helpful and makes results more confusing -// #[divan::bench( -// args = LENS, -// )] -// fn rebuild_4096(bencher: Bencher, len: usize) { -// let v = (0..4096).collect::>(); -// -// let arr = 0..len; -// -// // shuffle the array -// let mut arr = arr.collect::>(); -// arr.shuffle(&mut rand::thread_rng()); -// -// let changes = arr -// .iter() -// .enumerate() -// .map(|(i, &x)| quadtree::rebuild::MoveElement { -// remove_from_idx: i, -// insert_to_idx: x, -// }) -// .collect::>(); -// -// bencher.counter(4096_usize).bench(|| { -// quadtree::rebuild::apply_vec(&v, &changes, &mut [0, 1, 2, 3, 4]); -// }); -// } +#[divan::bench( +args = LENS, +)] +fn rebuild_262144(bencher: Bencher, len: usize) { + const LEN: usize = 262_144; + let v = (0..LEN).collect::>(); + + let arr = 0..len; + + // shuffle the array + let mut arr = arr.collect::>(); + arr.shuffle(&mut rand::thread_rng()); + + let changes = arr + .iter() + .enumerate() + .map(|(i, &x)| quadtree::rebuild::MoveElement { + remove_from_idx: i, + insert_to_idx: x, + }) + .collect::>(); + + bencher.bench(|| { + quadtree::rebuild::apply_vec(&v, &changes, &mut [0, 1, 2, 3, 4]); + }); +} + +#[divan::bench( +args = [64, 512, 4096], +)] +fn rebuild_262144_naive(bencher: Bencher, len: usize) { + const LEN: usize = 262_144; + let v = (0..LEN).collect::>(); + + let arr = 0..len; + + // shuffle the array + let mut arr = arr.collect::>(); + arr.shuffle(&mut rand::thread_rng()); + + let changes = arr + .iter() + .enumerate() + .map(|(i, &x)| quadtree::rebuild::MoveElement { + remove_from_idx: i, + insert_to_idx: x, + }) + .collect::>(); + + bencher.bench(|| { + let mut v = v.clone(); + + for &MoveElement { + remove_from_idx, + insert_to_idx, + } in &changes + { + let elem = v[remove_from_idx]; + v.remove(remove_from_idx); + v.insert(insert_to_idx, elem); + } + }); +}