wip: Refactor space colonization, separate nodes vec from space colonization struct allows for simpler ownership management

This commit is contained in:
jeangab 2023-08-08 23:33:34 -04:00
parent cb9df9e79a
commit 66e1f813cf
4 changed files with 55 additions and 101 deletions

View File

@ -5,6 +5,7 @@ use wasm_bindgen::JsValue;
use web_sys::window; use web_sys::window;
use crate::space_colonization::Node; use crate::space_colonization::Node;
use crate::space_colonization::Point;
use crate::space_colonization::SpaceColonization; use crate::space_colonization::SpaceColonization;
#[component] #[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_width(u32::try_from(width).unwrap());
canvas.set_height(u32::try_from(height).unwrap()); canvas.set_height(u32::try_from(height).unwrap());
let mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().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 // TODO Resize on window resize
log!( log!(
"TODO resize on window resize canvas parent size = {} {}", "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()); context.line_to(child.position.x.into(), child.position.y.into());
}; };
let render_id = window().unwrap().performance().unwrap().now(); 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.stroke();
context.set_fill_style(&JsValue::from("magenta")); 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 { for _i in 1..150 {
sc.grow(); nodes = sc.grow(nodes);
let render_id = window().unwrap().performance().unwrap().now(); let render_id = window().unwrap().performance().unwrap().now();
context.begin_path(); context.begin_path();
sc.render_nodes(render_id, render_node_fn); sc.render_nodes(&nodes, render_id, render_node_fn);
context.stroke(); context.stroke();
} }
}); });

View File

@ -30,7 +30,8 @@ mod tests {
#[test] #[test]
fn new_node_moves_toward_single_attractor() { fn new_node_moves_toward_single_attractor() {
let growth_cell = GrowthCell::from_positions([(0, 0), (0, 10)].to_vec()); 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))); assert_eq!(point, Point::new((0, 5)));
} }
@ -48,6 +49,7 @@ mod tests {
let node = Node { let node = Node {
position: Point::new(positions[0]), position: Point::new(positions[0]),
children: Vec::new().into(), children: Vec::new().into(),
growing: true,
}; };
let mut attractors = Vec::new(); let mut attractors = Vec::new();
for p in positions.iter().skip(1) { for p in positions.iter().skip(1) {
@ -58,9 +60,5 @@ mod tests {
} }
Self { node, attractors } Self { node, attractors }
} }
fn as_refs(&self) -> (Node, Vec<&Attractor>) {
(self.node, self.attractors.iter().collect())
}
} }
} }

View File

@ -66,17 +66,16 @@ impl Node {
where where
F: Copy + Fn(&Node, &Node), F: Copy + Fn(&Node, &Node),
{ {
let children = self.children; for child in self.children.iter() {
for child in children.iter() {
render_fn(self, &child); render_fn(self, &child);
} }
for child in children.iter() { for child in self.children.iter() {
child.render(render_id, render_fn); child.render(render_id, render_fn);
} }
} }
fn new(position: Point) -> Self { pub fn new(position: Point) -> Self {
Self { Self {
position, position,
growing: true, growing: true,

View File

@ -39,29 +39,11 @@ pub struct SpaceColonization {
/// ///
/// If density is 10, then there will be an average distance of 10 between attractors /// If density is 10, then there will be an average distance of 10 between attractors
density: i32, 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<Node>,
pub attractors: Vec<Attractor>, pub attractors: Vec<Attractor>,
} }
impl SpaceColonization { impl SpaceColonization {
pub fn new(width: i32, height: i32) -> 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 attractors = Vec::new();
let mut sc = SpaceColonization { let mut sc = SpaceColonization {
@ -73,7 +55,6 @@ impl SpaceColonization {
attraction_distance: 100, attraction_distance: 100,
segment_length: 5, segment_length: 5,
density: 30, density: 30,
nodes: nodes_vec,
attractors, attractors,
}; };
@ -86,7 +67,6 @@ impl SpaceColonization {
pub fn new_for_tests( pub fn new_for_tests(
width: i32, width: i32,
height: i32, height: i32,
nodes: Vec<Node>,
attractors: Vec<Attractor>, attractors: Vec<Attractor>,
) -> SpaceColonization { ) -> SpaceColonization {
SpaceColonization { SpaceColonization {
@ -98,17 +78,16 @@ impl SpaceColonization {
attraction_distance: 12, attraction_distance: 12,
segment_length: 3, segment_length: 3,
density: 3, density: 3,
nodes,
attractors, attractors,
} }
} }
pub fn render_nodes<F>(&self, render_id: f64, render_fn: F) pub fn render_nodes<F>(&self, nodes: &Vec<Node>, render_id: f64, render_fn: F)
where where
F: Copy + Fn(&Node, &Node), F: Copy + Fn(&Node, &Node),
{ {
info!("Rendering {} nodes", self.nodes.len()); info!("Rendering {} nodes", nodes.len());
for n in self.nodes.iter() { for n in nodes.iter() {
n.render(render_id, render_fn); n.render(render_id, render_fn);
} }
} }
@ -153,7 +132,7 @@ impl SpaceColonization {
} }
} }
pub fn grow(&mut self) { pub fn grow(&mut self, nodes: Vec<Node>) -> Vec<Node> {
// TODO // TODO
// [x] Find a clean API that will be stable across refactoring // [x] Find a clean API that will be stable across refactoring
// [ ] Write the test against this api including performance // [ ] Write the test against this api including performance
@ -163,62 +142,43 @@ impl SpaceColonization {
// - I can efficiently render my nodes on a canvas // - I can efficiently render my nodes on a canvas
// - I use as little memory as possible // - I use as little memory as possible
// - I can update my nodes // - 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<Node>) -> Vec<Node>{
// iterate through attractors // iterate through attractors
// find closest node within attraction range // find closest node within attraction range
// build a map of nodes to affecting attractors // build a map of nodes to affecting attractors
// attractors within the attraction range that this node is the closest to // attractors within the attraction range that this node is the closest to
// //
// calculate new node position // calculate new node position
let mut growing_paths: HashMap<*mut Node, Vec<&Attractor>> = HashMap::new(); let mut influence_map: HashMap<&Attractor, (*mut Node, f64)> = 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;
}
}
}
*/
}
fn get_closest_node_for_attractors(&self) -> HashMap<&Attractor, (&Node, f64)> { let nodes = nodes.into_iter().map(|mut n| {
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<Node>, mut map: HashMap<&'a Attractor, (&'a Node, f64)>) {
for n in nodes.iter() {
if !n.growing { if !n.growing {
continue; return n;
}
// 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;
} }
for a in attractors { let attractors_in_range = self.find_attractors_in_range(&n);
if let Some(closest) = map.get(a.0) {
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 { if a.1 < closest.1 {
map.insert(a.0, (n, a.1)); influence_map.insert(a.0, (&mut n, a.1));
} }
} else { } 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)> { fn find_attractors_in_range(&self, n: &Node) -> Vec<(&Attractor, f64)> {
@ -233,16 +193,15 @@ impl SpaceColonization {
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::cell::RefCell; use std::cell::RefCell;
use super::*; use super::*;
fn assert_nodes(sc: &SpaceColonization, expected_nodes: Vec<(Point, Point)>) { fn assert_nodes(sc: &SpaceColonization, nodes: &Vec<Node>, expected_nodes: Vec<(Point, Point)>) {
let rendered_nodes = RefCell::new(Vec::new()); 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)); rendered_nodes.borrow_mut().push((n1.position, n2.position));
}); });
@ -252,7 +211,7 @@ mod test {
} }
return line1.1.cmp(&line2.1); return line1.1.cmp(&line2.1);
}) });
} }
#[test] #[test]
@ -262,34 +221,29 @@ mod test {
let mut attractors = Vec::new(); let mut attractors = Vec::new();
attractors.push(Attractor::new((10, 0))); 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_eq!(sc.attractors.len(), 1);
assert!(sc assert!(sc.attractors.iter().find(|a| a.dead == true).is_none());
.attractors
.iter()
.find(|a| a.dead == true)
.is_none());
sc.grow(); nodes = sc.grow(nodes);
assert!(sc assert!(sc.attractors.iter().find(|a| a.dead == true).is_none());
.attractors
.iter()
.find(|a| a.dead == true)
.is_none());
// TODO assert point 3,0 // TODO assert point 3,0
sc.grow(); nodes = sc.grow(nodes);
assert_eq!(
sc.attractors
.iter()
.filter(|a| a.dead == true)
.collect::<Vec<&Attractor>>()
.len(),
1
);
assert_eq!(sc
.attractors
.iter()
.filter(|a| a.dead == true)
.collect::<Vec<&Attractor>>().len(), 1);
// TODO assert nodes 3,0 and 6,0 // TODO assert nodes 3,0 and 6,0
assert_eq!(sc.nodes.len(), 3); assert_eq!(nodes.len(), 3);
} }
} }