wip(space colonization): Refactor to use a compile-time safe data structure that is also using contiguous memory

This commit is contained in:
Jean-Gabriel Gill-Couture 2023-07-30 22:27:02 -04:00
parent b893eb2a32
commit 6a0f480c8a
3 changed files with 79 additions and 84 deletions

View File

@ -1,9 +1,7 @@
use std::rc::Rc;
use super::{Attractor, Node, Point}; use super::{Attractor, Node, Point};
pub fn calculate_new_node_position( pub fn calculate_new_node_position(
growth_cell: &(Rc<Node>, Vec<&Attractor>), growth_cell: &(Node, Vec<&Attractor>),
segment_length: u16, segment_length: u16,
) -> Point { ) -> Point {
let node = &growth_cell.0; let node = &growth_cell.0;
@ -26,8 +24,6 @@ pub fn calculate_new_node_position(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::cell::Cell;
use super::*; use super::*;
const SEGMENT_LENGTH: u16 = 5; const SEGMENT_LENGTH: u16 = 5;
@ -42,29 +38,29 @@ mod tests {
fn new_node_ignores_dead_attractor() {} fn new_node_ignores_dead_attractor() {}
struct GrowthCell { struct GrowthCell {
node: Rc<Node>, node: Node,
attractors: Vec<Attractor>, attractors: Vec<Attractor>,
} }
impl GrowthCell { impl GrowthCell {
pub fn from_positions(positions: Vec<(i32, i32)>) -> Self { pub fn from_positions(positions: Vec<(i32, i32)>) -> Self {
assert!(positions.len() >= 2); assert!(positions.len() >= 2);
let node = Rc::new(Node { let node = Node {
position: Point::new(positions[0]), position: Point::new(positions[0]),
children: Vec::new().into(), children: Vec::new().into(),
}); };
let mut attractors = Vec::new(); let mut attractors = Vec::new();
for p in positions.iter().skip(1) { for p in positions.iter().skip(1) {
attractors.push(Attractor { attractors.push(Attractor {
position: Point::new(*p), position: Point::new(*p),
dead: Cell::new(false), dead: false,
}); });
} }
Self { node, attractors } Self { node, attractors }
} }
fn as_refs(&self) -> (Rc<Node>, Vec<&Attractor>) { fn as_refs(&self) -> (Node, Vec<&Attractor>) {
(self.node.clone(), self.attractors.iter().collect()) (self.node, self.attractors.iter().collect())
} }
} }
} }

View File

@ -1,8 +1,3 @@
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
mod point; mod point;
@ -20,14 +15,14 @@ extern "C" {
#[derive(Debug)] #[derive(Debug)]
pub struct Attractor { pub struct Attractor {
pub position: Point, pub position: Point,
pub dead: Cell<bool>, pub dead: bool,
} }
impl Attractor { impl Attractor {
pub fn new(position: (i32, i32)) -> Attractor { pub fn new(position: (i32, i32)) -> Attractor {
Attractor { Attractor {
position: Point::new(position), position: Point::new(position),
dead: Cell::new(false), dead: false,
} }
} }
} }
@ -35,7 +30,17 @@ impl Attractor {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Node { pub struct Node {
pub position: Point, pub position: Point,
pub children: RefCell<Vec<Rc<Node>>>, pub children: Vec<Node>,
}
#[derive(Debug)]
pub struct NodeRef {
path: Vec<u16>
}
#[derive(Debug)]
pub struct AttractorRef {
path: Vec<u16>
} }
impl std::hash::Hash for Node { impl std::hash::Hash for Node {
@ -49,7 +54,7 @@ impl Node {
where where
F: Copy + Fn(&Node, &Node), F: Copy + Fn(&Node, &Node),
{ {
let children = self.children.borrow(); let children = self.children;
for child in children.iter() { for child in children.iter() {
render_fn(self, &child); render_fn(self, &child);
} }

View File

@ -1,11 +1,9 @@
use super::math::calculate_new_node_position; use super::math::calculate_new_node_position;
use super::{Attractor, Node, Point}; use super::{Attractor, Node, NodeRef, Point};
use log::info; use log::info;
use rand::thread_rng; use rand::thread_rng;
use rand::Rng; use rand::Rng;
use std::cell::{Cell, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
pub struct SpaceColonization { pub struct SpaceColonization {
max_point: Point, max_point: Point,
@ -42,28 +40,21 @@ 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,
// TODO learning opportunity : avoid Rc and RefCell to have memory contiguous, increase new_nodes: Vec<(NodeRef, Node)>,
// performance and simplify code. Using a mutable struct instead of pointers to nodeswill
// improve performance. If required, use ids to point to nodes, those ids can be simply their
// indices in the array. If the node pointed to is deep, the index can be a vec or a tuple.
new_nodes: RefCell<Vec<(Rc<Node>, Rc<Node>)>>,
/// Tree like representation of all nodes
/// [node: [child1: [grand-child], child2: [grand-child2]]]
pub nodes_tree: RefCell<Vec<Rc<Node>>>,
/// Flat list of all nodes in the tree /// Flat list of all nodes in the tree
/// [node, child1, grand-child, child2, grand-child2] /// [node, child1, grand-child, child2, grand-child2]
nodes: RefCell<Vec<Rc<Node>>>, nodes: Vec<Node>,
pub attractors: Rc<RefCell<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(); let mut nodes_vec = Vec::new();
nodes_vec.push(Rc::new(Node { nodes_vec.push(Node {
position: Point { x: 100, y: 100 }, position: Point { x: 100, y: 100 },
children: RefCell::new(Vec::new()), children: Vec::new(),
})); });
let attractors = Rc::new(RefCell::new(Vec::new())); let attractors = Vec::new();
let mut sc = SpaceColonization { let mut sc = SpaceColonization {
max_point: Point { max_point: Point {
@ -74,10 +65,9 @@ impl SpaceColonization {
attraction_distance: 100, attraction_distance: 100,
segment_length: 5, segment_length: 5,
density: 30, density: 30,
nodes_tree: RefCell::new(nodes_vec.clone()), nodes: nodes_vec,
nodes: RefCell::new(nodes_vec),
attractors, attractors,
new_nodes: RefCell::new(Vec::new()), new_nodes: Vec::new(),
}; };
sc.place_attractors(); sc.place_attractors();
@ -89,7 +79,7 @@ impl SpaceColonization {
pub fn new_for_tests( pub fn new_for_tests(
width: i32, width: i32,
height: i32, height: i32,
nodes: Vec<Rc<Node>>, nodes: Vec<Node>,
attractors: Vec<Attractor>, attractors: Vec<Attractor>,
) -> SpaceColonization { ) -> SpaceColonization {
SpaceColonization { SpaceColonization {
@ -101,10 +91,9 @@ impl SpaceColonization {
attraction_distance: 12, attraction_distance: 12,
segment_length: 3, segment_length: 3,
density: 3, density: 3,
nodes_tree: RefCell::new(nodes.clone()), nodes,
nodes: RefCell::new(nodes), attractors,
attractors: Rc::new(RefCell::new(attractors)), new_nodes: Vec::new(),
new_nodes: RefCell::new(Vec::new()),
} }
} }
@ -112,8 +101,8 @@ impl SpaceColonization {
where where
F: Copy + Fn(&Node, &Node), F: Copy + Fn(&Node, &Node),
{ {
info!("Rendering {} nodes", self.nodes.borrow().len()); info!("Rendering {} nodes", self.nodes.len());
for n in self.nodes_tree.borrow().iter() { for n in self.nodes_tree.iter() {
n.render(render_id, render_fn); n.render(render_id, render_fn);
} }
} }
@ -123,9 +112,9 @@ impl SpaceColonization {
let mut y_pos = 0; let mut y_pos = 0;
while x_pos < self.max_point.x { while x_pos < self.max_point.x {
while y_pos < self.max_point.y { while y_pos < self.max_point.y {
self.attractors.borrow_mut().push(Attractor { self.attractors.push(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()), position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: Cell::new(false), dead: false,
}); });
y_pos += self.density; y_pos += self.density;
} }
@ -159,14 +148,23 @@ impl SpaceColonization {
} }
pub fn grow(&self) { pub fn grow(&self) {
// TODO
// Find a clean API that will be stable across refactoring
// Write the test against this api including performance
// Find a way to make a compile-time safe datastructure that will ensure that
// - I can store my attractors and their state (remove them or update them when dead)
// - I can store my nodes and their state
// - 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();
println!("new nodes for iteration {:?}", self.new_nodes.borrow()); println!("new nodes for iteration {:?}", self.new_nodes);
let mut nodes_mut = self.nodes.borrow_mut(); let mut nodes_mut = self.nodes;
for new_pair in self.new_nodes.borrow().iter() { for new_pair in self.new_nodes.iter() {
new_pair.0.children.borrow_mut().push(new_pair.1.clone()); new_pair.0.children.push(new_pair.1);
nodes_mut.push(new_pair.1.clone()); nodes_mut.push(new_pair.1);
} }
self.new_nodes.borrow_mut().clear(); self.new_nodes.clear();
} }
pub fn grow_nodes(&self) { pub fn grow_nodes(&self) {
@ -176,21 +174,21 @@ impl SpaceColonization {
// 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 attractors = self.attractors.borrow(); let attractors = self.attractors;
let mut growing_paths: HashMap<Rc<Node>, Vec<&Attractor>> = HashMap::new(); let mut growing_paths: HashMap<<NodeRef>, Vec<AttractorRef>> = HashMap::new();
for a in attractors.iter() { for a in attractors.iter() {
if a.dead.get() { if a.dead {
continue; continue;
} }
let mut closest_node: Option<Rc<Node>> = None; let mut closest_node: Option<Node> = None;
let mut closest_node_distance = f64::MAX; let mut closest_node_distance = f64::MAX;
for n in self.nodes.borrow().iter() { for n in self.nodes.iter() {
let distance = n.position.distance(&a.position); let distance = n.position.distance(&a.position);
if distance <= self.attraction_distance as f64 { if distance <= self.attraction_distance as f64 {
// TODO make sure it is closest node amongs all nodes // TODO make sure it is closest node amongs all nodes
if distance < closest_node_distance { if distance < closest_node_distance {
closest_node = Some(n.clone()); closest_node = Some(n);
closest_node_distance = distance; closest_node_distance = distance;
if distance < self.kill_distance as f64 { if distance < self.kill_distance as f64 {
a.dead.replace(true); a.dead.replace(true);
@ -216,8 +214,7 @@ impl SpaceColonization {
} }
} }
self.new_nodes self.new_nodes
.borrow_mut() .push((growth_cell.0, Node::new(position)));
.push((growth_cell.0.clone(), Rc::new(Node::new(position))));
} }
} }
} }
@ -229,71 +226,68 @@ mod test {
#[test] #[test]
fn grow_should_reach_single_attractor_and_die() { fn grow_should_reach_single_attractor_and_die() {
let mut nodes = Vec::new(); let mut nodes = Vec::new();
nodes.push(Rc::new(Node::new(Point::new((0, 0))))); nodes.push(Node::new(Point::new((0, 0))));
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 sc = SpaceColonization::new_for_tests(100, 100, nodes, attractors);
assert_eq!(sc.attractors.borrow().len(), 1); assert_eq!(sc.attractors.len(), 1);
assert!(sc assert!(sc
.attractors .attractors
.borrow()
.iter() .iter()
.find(|a| a.dead.get() == true) .find(|a| a.dead == true)
.is_none()); .is_none());
assert_eq!(sc.nodes_tree.borrow()[0].children.borrow().len(), 0); assert_eq!(sc.nodes_tree[0].children.len(), 0);
sc.grow(); sc.grow();
assert_eq!(sc.new_nodes.borrow().len(), 0); assert_eq!(sc.new_nodes.len(), 0);
assert_eq!(sc.nodes_tree.borrow()[0].children.borrow().len(), 1); assert_eq!(sc.nodes_tree.[0].children.len(), 1);
assert!(sc assert!(sc
.attractors .attractors
.borrow()
.iter() .iter()
.find(|a| a.dead.get() == true) .find(|a| a.dead == true)
.is_none()); .is_none());
assert_eq!( assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow()[0].position, sc.nodes_tree.[0].children.[0].position,
Point::new((3, 0)) Point::new((3, 0))
); );
assert_eq!( assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow().len(), sc.nodes_tree[0].children.len(),
1, 1,
); );
assert_eq!( assert_eq!(
sc.nodes_tree.borrow().len(), sc.nodes_tree.len(),
1, 1,
); );
println!("root node direct children iteration 1 {:?}", sc.nodes_tree.borrow()[0].children.borrow()); println!("root node direct children iteration 1 {:?}", sc.nodes_tree.[0].children);
sc.grow(); sc.grow();
assert_eq!( assert_eq!(
sc.nodes_tree.borrow().len(), sc.nodes_tree.len(),
1, 1,
); );
assert_eq!(sc assert_eq!(sc
.attractors .attractors
.borrow()
.iter() .iter()
.filter(|a| a.dead.get() == true) .filter(|a| a.dead == true)
.collect::<Vec<&Attractor>>().len(), 1); .collect::<Vec<&Attractor>>().len(), 1);
println!("root node direct children iteration 2 {:?}", sc.nodes_tree.borrow()[0].children.borrow()); println!("root node direct children iteration 2 {:?}", sc.nodes_tree.[0].children);
assert_eq!( assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow().len(), sc.nodes_tree[0].children.len(),
1, 1,
); );
assert_eq!( assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow()[0].position, sc.nodes_tree[0].children[0].position,
Point::new((3, 0)) Point::new((3, 0))
); );
assert_eq!( assert_eq!(
sc.nodes_tree.borrow()[0].children.borrow()[0].children.borrow()[0].position, sc.nodes_tree[0].children[0].children[0].position,
Point::new((6, 0)) Point::new((6, 0))
); );
assert_eq!(sc.nodes.borrow().len(), 3); assert_eq!(sc.nodes.len(), 3);
} }
} }