From 66e1f813cf94e054f16df3bce7632142847e8033 Mon Sep 17 00:00:00 2001 From: jeangab Date: Tue, 8 Aug 2023 23:33:34 -0400 Subject: [PATCH] wip: Refactor space colonization, separate nodes vec from space colonization struct allows for simpler ownership management --- src/components/background.rs | 9 +- src/space_colonization/math.rs | 8 +- src/space_colonization/mod.rs | 7 +- src/space_colonization/space_colonization.rs | 132 ++++++------------- 4 files changed, 55 insertions(+), 101 deletions(-) diff --git a/src/components/background.rs b/src/components/background.rs index fb56834..35c171b 100644 --- a/src/components/background.rs +++ b/src/components/background.rs @@ -5,6 +5,7 @@ use wasm_bindgen::JsValue; use web_sys::window; use crate::space_colonization::Node; +use crate::space_colonization::Point; use crate::space_colonization::SpaceColonization; #[component] @@ -19,6 +20,8 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { canvas.set_width(u32::try_from(width).unwrap()); canvas.set_height(u32::try_from(height).unwrap()); let mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap()); + let mut nodes = Vec::new(); + nodes.push(Node::new(Point::new((100, 100)))); // TODO Resize on window resize log!( "TODO resize on window resize canvas parent size = {} {}", @@ -44,7 +47,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { context.line_to(child.position.x.into(), child.position.y.into()); }; let render_id = window().unwrap().performance().unwrap().now(); - sc.render_nodes(render_id, render_node_fn); + sc.render_nodes(&nodes, render_id, render_node_fn); context.stroke(); context.set_fill_style(&JsValue::from("magenta")); @@ -59,10 +62,10 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView { ); for _i in 1..150 { - sc.grow(); + nodes = sc.grow(nodes); let render_id = window().unwrap().performance().unwrap().now(); context.begin_path(); - sc.render_nodes(render_id, render_node_fn); + sc.render_nodes(&nodes, render_id, render_node_fn); context.stroke(); } }); diff --git a/src/space_colonization/math.rs b/src/space_colonization/math.rs index 756dfd3..15c266e 100644 --- a/src/space_colonization/math.rs +++ b/src/space_colonization/math.rs @@ -30,7 +30,8 @@ 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.as_refs(), SEGMENT_LENGTH); + + let point = calculate_new_node_position(&(growth_cell.node, growth_cell.attractors.iter().collect()), SEGMENT_LENGTH); assert_eq!(point, Point::new((0, 5))); } @@ -48,6 +49,7 @@ mod tests { let node = Node { position: Point::new(positions[0]), children: Vec::new().into(), + growing: true, }; let mut attractors = Vec::new(); for p in positions.iter().skip(1) { @@ -58,9 +60,5 @@ mod tests { } Self { node, attractors } } - - fn as_refs(&self) -> (Node, Vec<&Attractor>) { - (self.node, self.attractors.iter().collect()) - } } } diff --git a/src/space_colonization/mod.rs b/src/space_colonization/mod.rs index b84f929..a336ca2 100644 --- a/src/space_colonization/mod.rs +++ b/src/space_colonization/mod.rs @@ -66,17 +66,16 @@ impl Node { where F: Copy + Fn(&Node, &Node), { - let children = self.children; - for child in children.iter() { + for child in self.children.iter() { render_fn(self, &child); } - for child in children.iter() { + for child in self.children.iter() { child.render(render_id, render_fn); } } - fn new(position: Point) -> Self { + pub fn new(position: Point) -> Self { Self { position, growing: true, diff --git a/src/space_colonization/space_colonization.rs b/src/space_colonization/space_colonization.rs index 718850f..1b79742 100644 --- a/src/space_colonization/space_colonization.rs +++ b/src/space_colonization/space_colonization.rs @@ -39,29 +39,11 @@ pub struct SpaceColonization { /// /// If density is 10, then there will be an average distance of 10 between attractors density: i32, - /// Tree of all nodes in the space. There can be multiple root nodes. - /// ```yaml - /// [ - /// node: { - /// position: (x,y), - /// children : [ - /// child1 : { - /// position, children: [ grand-child1, grand-child2 ] - /// }, - /// child2 : {...} - /// ], - /// }, - /// node2: { ...another tree }, - /// ] - /// ``` - nodes: Vec, pub attractors: Vec, } impl SpaceColonization { pub fn new(width: i32, height: i32) -> SpaceColonization { - let mut nodes_vec = Vec::new(); - nodes_vec.push(Node::new(Point::new((100, 100)))); let attractors = Vec::new(); let mut sc = SpaceColonization { @@ -73,7 +55,6 @@ impl SpaceColonization { attraction_distance: 100, segment_length: 5, density: 30, - nodes: nodes_vec, attractors, }; @@ -86,7 +67,6 @@ impl SpaceColonization { pub fn new_for_tests( width: i32, height: i32, - nodes: Vec, attractors: Vec, ) -> SpaceColonization { SpaceColonization { @@ -98,17 +78,16 @@ impl SpaceColonization { attraction_distance: 12, segment_length: 3, density: 3, - nodes, attractors, } } - pub fn render_nodes(&self, render_id: f64, render_fn: F) + pub fn render_nodes(&self, nodes: &Vec, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node), { - info!("Rendering {} nodes", self.nodes.len()); - for n in self.nodes.iter() { + info!("Rendering {} nodes", nodes.len()); + for n in nodes.iter() { n.render(render_id, render_fn); } } @@ -153,7 +132,7 @@ impl SpaceColonization { } } - pub fn grow(&mut self) { + pub fn grow(&mut self, nodes: Vec) -> Vec { // TODO // [x] Find a clean API that will be stable across refactoring // [ ] Write the test against this api including performance @@ -163,62 +142,43 @@ 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(); + self.grow_nodes(nodes) } - pub fn grow_nodes(&mut self) { + pub fn grow_nodes(&mut self, nodes: Vec) -> 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 growing_paths: HashMap<*mut Node, Vec<&Attractor>> = HashMap::new(); - let mut influence_map = self.get_closest_node_for_attractors(); - /* - for growth_cell in growing_paths { - let position = calculate_new_node_position(&growth_cell, self.segment_length); - for a in growth_cell.1 { - if position.distance(&a.position) < self.kill_distance as f64 { - a.dead = true; - } - } - } - */ - } + let mut influence_map: HashMap<&Attractor, (*mut Node, f64)> = HashMap::new(); - fn get_closest_node_for_attractors(&self) -> HashMap<&Attractor, (&Node, f64)> { - let mut map = HashMap::new(); - self.build_attractor_map(&self.nodes, map); - - - - map - } - - // TODO use a struct instead of (&Node, f64) tuple - fn build_attractor_map<'a>(self, nodes: &'a Vec, mut map: HashMap<&'a Attractor, (&'a Node, f64)>) { - for n in nodes.iter() { + let nodes = nodes.into_iter().map(|mut n| { if !n.growing { - continue; - } - // TODO figure out that borrowing mess - let attractors: Vec<(&Attractor, f64)> = self.find_attractors_in_range(n); - if attractors.is_empty() { - n.growing = false; - continue; + return n; } - for a in attractors { - if let Some(closest) = map.get(a.0) { + let attractors_in_range = self.find_attractors_in_range(&n); + + if attractors_in_range.is_empty() { + n.growing = false; + return n; + } + + for a in attractors_in_range { + if let Some(closest) = influence_map.get(a.0) { if a.1 < closest.1 { - map.insert(a.0, (n, a.1)); + influence_map.insert(a.0, (&mut n, a.1)); } } else { - map.insert(a.0, (n, a.1)); + influence_map.insert(a.0, (&mut n, a.1)); } } - } + n + }).collect(); + + nodes } fn find_attractors_in_range(&self, n: &Node) -> Vec<(&Attractor, f64)> { @@ -233,16 +193,15 @@ impl SpaceColonization { } } - #[cfg(test)] mod test { use std::cell::RefCell; use super::*; - fn assert_nodes(sc: &SpaceColonization, expected_nodes: Vec<(Point, Point)>) { + fn assert_nodes(sc: &SpaceColonization, nodes: &Vec, expected_nodes: Vec<(Point, Point)>) { let rendered_nodes = RefCell::new(Vec::new()); - sc.render_nodes(0.0, |n1, n2| { + sc.render_nodes(&nodes, 0.0, |n1, n2| { rendered_nodes.borrow_mut().push((n1.position, n2.position)); }); @@ -252,7 +211,7 @@ mod test { } return line1.1.cmp(&line2.1); - }) + }); } #[test] @@ -262,34 +221,29 @@ mod test { let mut attractors = Vec::new(); attractors.push(Attractor::new((10, 0))); - let sc = SpaceColonization::new_for_tests(100, 100, nodes, attractors); + let mut sc = SpaceColonization::new_for_tests(100, 100, attractors); - assert_nodes(&sc, Vec::from([(Point::new((0,0)), Point::new((10,0)))])); + 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()); + assert!(sc.attractors.iter().find(|a| a.dead == true).is_none()); - sc.grow(); + nodes = sc.grow(nodes); - assert!(sc - .attractors - .iter() - .find(|a| a.dead == true) - .is_none()); + assert!(sc.attractors.iter().find(|a| a.dead == true).is_none()); // TODO assert point 3,0 - sc.grow(); + nodes = sc.grow(nodes); + + assert_eq!( + sc.attractors + .iter() + .filter(|a| a.dead == true) + .collect::>() + .len(), + 1 + ); - assert_eq!(sc - .attractors - .iter() - .filter(|a| a.dead == true) - .collect::>().len(), 1); - // TODO assert nodes 3,0 and 6,0 - assert_eq!(sc.nodes.len(), 3); + assert_eq!(nodes.len(), 3); } }