From e67f97058b7e039cfb8b51a5b26c91180f81a944 Mon Sep 17 00:00:00 2001 From: jeangab Date: Wed, 9 Aug 2023 23:20:05 -0400 Subject: [PATCH] refactoring works, only left to add a test and feature of ignoring dead attractors and nodes --- src/space_colonization/math.rs | 26 ++- src/space_colonization/mod.rs | 14 +- src/space_colonization/point.rs | 2 +- src/space_colonization/space_colonization.rs | 167 ++++++++++++++----- 4 files changed, 154 insertions(+), 55 deletions(-) diff --git a/src/space_colonization/math.rs b/src/space_colonization/math.rs index 15c266e..c97ea01 100644 --- a/src/space_colonization/math.rs +++ b/src/space_colonization/math.rs @@ -1,17 +1,18 @@ use super::{Attractor, Node, Point}; pub fn calculate_new_node_position( - growth_cell: &(Node, Vec<&Attractor>), + node: &Node, + attractors: &Vec<*mut Attractor>, segment_length: u16, ) -> Point { - let node = &growth_cell.0; - let attractors = &growth_cell.1; let mut attraction_sum_x = 0; let mut attraction_sum_y = 0; for a in attractors.iter() { - attraction_sum_x += a.position.x - node.position.x; - attraction_sum_y += a.position.y - node.position.y; + unsafe { + attraction_sum_x += (**a).position.x - node.position.x; + attraction_sum_y += (**a).position.y - node.position.y; + } } let point = Point { @@ -29,9 +30,14 @@ mod tests { #[test] fn new_node_moves_toward_single_attractor() { - let growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec()); - - let point = calculate_new_node_position(&(growth_cell.node, growth_cell.attractors.iter().collect()), SEGMENT_LENGTH); + let mut growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec()); + let attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut(); + + let point = calculate_new_node_position( + &growth_cell.node, + &attractors_as_ptr_mut, + SEGMENT_LENGTH, + ); assert_eq!(point, Point::new((0, 5))); } @@ -60,5 +66,9 @@ mod tests { } Self { node, attractors } } + + fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> { + self.attractors.iter_mut().map(|a| { a as *mut Attractor }).collect() + } } } diff --git a/src/space_colonization/mod.rs b/src/space_colonization/mod.rs index a336ca2..dbce4f0 100644 --- a/src/space_colonization/mod.rs +++ b/src/space_colonization/mod.rs @@ -45,14 +45,24 @@ pub struct Node { pub children: Vec, } +pub struct Attraction { + node: *mut Node, + distance: f64, +} +impl Attraction { + fn new(node: &mut Node, distance: f64) -> Attraction { + Self { node, distance } + } +} + #[derive(Debug)] pub struct NodeRef { - path: Vec + path: Vec, } #[derive(Debug)] pub struct AttractorRef { - path: Vec + path: Vec, } impl std::hash::Hash for Node { diff --git a/src/space_colonization/point.rs b/src/space_colonization/point.rs index 94e39ea..43f72f9 100644 --- a/src/space_colonization/point.rs +++ b/src/space_colonization/point.rs @@ -191,7 +191,7 @@ mod tests { fn movement_does_not_overlap() { let root = Point::new((0,1)); let node = Point::new((0,0)); - assert_eq!(root.movement(node.clone(), 2), node); + assert_eq!(root.movement(node.clone(), 2), Point::new((0, -1))); } #[test] diff --git a/src/space_colonization/space_colonization.rs b/src/space_colonization/space_colonization.rs index 1b79742..0b9fcae 100644 --- a/src/space_colonization/space_colonization.rs +++ b/src/space_colonization/space_colonization.rs @@ -1,3 +1,5 @@ +use super::math::calculate_new_node_position; +use super::Attraction; use super::{Attractor, Node, Point}; use log::info; use rand::thread_rng; @@ -7,7 +9,7 @@ use std::collections::HashMap; pub struct SpaceColonization { max_point: Point, /// When a node grows within kill_distance of an attractor, the attractor is killed - kill_distance: i32, + kill_distance: f64, /// Maximum distance between an attractor and a node for the node to /// be affected by the attractor. /// @@ -51,7 +53,7 @@ impl SpaceColonization { x: width, y: height, }, - kill_distance: 5, + kill_distance: 5.0, attraction_distance: 100, segment_length: 5, density: 30, @@ -64,17 +66,13 @@ impl SpaceColonization { } #[cfg(test)] - pub fn new_for_tests( - width: i32, - height: i32, - attractors: Vec, - ) -> SpaceColonization { + pub fn new_for_tests(width: i32, height: i32, attractors: Vec) -> SpaceColonization { SpaceColonization { max_point: Point { x: width, y: height, }, - kill_distance: 5, + kill_distance: 5.0, attraction_distance: 12, segment_length: 3, density: 3, @@ -132,7 +130,7 @@ impl SpaceColonization { } } - pub fn grow(&mut self, nodes: Vec) -> Vec { + pub fn grow(&mut self, mut nodes: Vec) -> Vec { // TODO // [x] Find a clean API that will be stable across refactoring // [ ] Write the test against this api including performance @@ -142,51 +140,112 @@ impl SpaceColonization { // - I can efficiently render my nodes on a canvas // - I use as little memory as possible // - I can update my nodes - self.grow_nodes(nodes) + self.grow_nodes(&mut nodes); + nodes } - pub fn grow_nodes(&mut self, nodes: Vec) -> Vec{ + pub fn grow_nodes(&mut self, nodes: &mut Vec) { // iterate through attractors // find closest node within attraction range // build a map of nodes to affecting attractors // attractors within the attraction range that this node is the closest to // // calculate new node position - let mut influence_map: HashMap<&Attractor, (*mut Node, f64)> = HashMap::new(); - let nodes = nodes.into_iter().map(|mut n| { - if !n.growing { - return n; - } + // ------------ START OF BLOCK ---------- + // DO NOT MODIFY THE NODES VEC AFTER THIS + // We are taking raw pointers to Node to be dereferenced later, if the Vec of nodes is + // modified it will cause wrong behavior or segmentation faults and crash + let mut attractor_to_closest_node: HashMap<*mut Attractor, Attraction> = HashMap::new(); - let attractors_in_range = self.find_attractors_in_range(&n); + for n in nodes.iter_mut() { + self.build_attractor_to_closest_node(&mut attractor_to_closest_node, n); + } - if attractors_in_range.is_empty() { - n.growing = false; - return n; - } + let mut node_to_attractors: HashMap<*mut Node, Vec<*mut Attractor>> = HashMap::new(); - for a in attractors_in_range { - if let Some(closest) = influence_map.get(a.0) { - if a.1 < closest.1 { - influence_map.insert(a.0, (&mut n, a.1)); + attractor_to_closest_node + .drain() + .for_each(|(attractor, attraction)| { + let mut node_attractors = match node_to_attractors.remove(&attraction.node) { + Some(node_a) => node_a, + None => Vec::new(), + }; + node_attractors.push(attractor); + node_to_attractors.insert(attraction.node, node_attractors); + }); + + let mut dead_attractors: Vec<*mut Attractor> = Vec::new(); + node_to_attractors.iter().for_each(|(node, attractor)| { + // Unsafe is used here for two main reasons : + // + // PERFORMANCE : Using unsafe here allows to store multiple mutable references to a + // Node and save a few bytes of memory and cpu cycles to store a struct that holds + // a fake reference to the Node, such as the path in the tree, and then resolve it + // handling the Option<> every step of the way. + // + // Using a raw pointer we can Oh I actually just realised having a raw pointer deep + // in a tree of Vec that are getting pushed into might very well cause the pointer + // to become invalid when its parent gets pushed into and moved to another memory + // space + // + // Using raw fixed length arrays would solve that but its a fine line between too + // large memory usage and enough children nodes + + unsafe { + let new_node = Node::new(calculate_new_node_position( + &(**node), + attractor, + self.segment_length, + )); + attractor.iter().for_each(|a| { + if (**a).position.distance(&new_node.position) <= self.kill_distance { + dead_attractors.push(*a); + (**a).dead = true; } - } else { - influence_map.insert(a.0, (&mut n, a.1)); - } + }); + (**node).children.push(new_node); } - n - }).collect(); - - nodes + }); } - fn find_attractors_in_range(&self, n: &Node) -> Vec<(&Attractor, f64)> { + fn build_attractor_to_closest_node<'a>( + &'a mut self, + mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>, + n: &mut Node, + ) { + if !n.growing { + return; + } + + let attractors_in_range = self.find_attractors_in_range(&n); + + if attractors_in_range.is_empty() { + n.growing = false; + return; + } + + for a in attractors_in_range { + if let Some(closest) = attractor_to_closest_node.get(&a.0) { + if a.1 < closest.distance { + attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1)); + } + } else { + attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1)); + } + } + + for child in n.children.iter_mut() { + self.build_attractor_to_closest_node(&mut attractor_to_closest_node, child); + } + } + + fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> { let mut attractors_in_range = Vec::new(); - for a in self.attractors.iter() { + for a in self.attractors.iter_mut() { let distance = n.position.distance(&a.position); if distance < self.attraction_distance as f64 { - attractors_in_range.push((a, distance)); + attractors_in_range.push((a as *mut Attractor, distance)); } } attractors_in_range @@ -199,19 +258,28 @@ mod test { use super::*; - fn assert_nodes(sc: &SpaceColonization, nodes: &Vec, expected_nodes: Vec<(Point, Point)>) { + fn assert_vertices( + sc: &SpaceColonization, + nodes: &Vec, + mut expected_nodes: Vec<(Point, Point)>, + ) { let rendered_nodes = RefCell::new(Vec::new()); sc.render_nodes(&nodes, 0.0, |n1, n2| { rendered_nodes.borrow_mut().push((n1.position, n2.position)); }); - - rendered_nodes.borrow_mut().sort_by(|line1, line2| { + let sort_points = |line1: &(Point, Point), line2: &(Point, Point)| { if line1.0 != line2.0 { return line1.0.cmp(&line2.0); } return line1.1.cmp(&line2.1); - }); + }; + rendered_nodes.borrow_mut().sort_by(sort_points); + expected_nodes.sort_by(sort_points); + + let rendered_nodes = rendered_nodes.take(); + + assert_eq!(rendered_nodes, expected_nodes); } #[test] @@ -223,16 +291,30 @@ mod test { let mut sc = SpaceColonization::new_for_tests(100, 100, attractors); - assert_nodes(&sc, &nodes, Vec::from([(Point::new((0, 0)), Point::new((10, 0)))])); assert_eq!(sc.attractors.len(), 1); assert!(sc.attractors.iter().find(|a| a.dead == true).is_none()); + println!("before grow"); + dbg!(&nodes); nodes = sc.grow(nodes); + println!("after grow 1"); + dbg!(&nodes); assert!(sc.attractors.iter().find(|a| a.dead == true).is_none()); - // TODO assert point 3,0 nodes = sc.grow(nodes); + println!("after grow 2"); + dbg!(&nodes); + + // TODO assert nodes 3,0 and 6,0 + assert_vertices( + &sc, + &nodes, + Vec::from([ + (Point::new((0, 0)), Point::new((3, 0))), + (Point::new((3, 0)), Point::new((6, 0))), + ]), + ); assert_eq!( sc.attractors @@ -242,8 +324,5 @@ mod test { .len(), 1 ); - - // TODO assert nodes 3,0 and 6,0 - assert_eq!(nodes.len(), 3); } }