wip: refactor space colonization to use mutables instead of refCell

This commit is contained in:
jeangab 2023-08-08 14:07:58 -04:00
parent 0b892980ca
commit b7401bef1b
3 changed files with 82 additions and 69 deletions

View File

@ -18,7 +18,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let height = canvas_parent.client_height();
canvas.set_width(u32::try_from(width).unwrap());
canvas.set_height(u32::try_from(height).unwrap());
let sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap());
let mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap());
// TODO Resize on window resize
log!(
"TODO resize on window resize canvas parent size = {} {}",
@ -38,10 +38,6 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
context.set_fill_style(&JsValue::from("yellow"));
log!("About to render nodes");
let start_time = window().unwrap().performance().unwrap().now();
for n in sc.nodes_tree.borrow().iter() {
context.fill_rect(n.position.x.into(), n.position.y.into(), 5.0, 5.0);
}
context.begin_path();
let render_node_fn = |n: &Node, child: &Node| {
context.move_to(n.position.x.into(), n.position.y.into());
@ -52,14 +48,13 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
context.stroke();
context.set_fill_style(&JsValue::from("magenta"));
for a in sc.attractors.borrow().iter() {
for a in sc.attractors.iter() {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
}
let end_time = window().unwrap().performance().unwrap().now();
log!(
"Rendering {} nodes and {} attractors took {}",
sc.nodes_tree.borrow().len(),
sc.attractors.borrow().len(),
"Rendering nodes and {} attractors took {}",
sc.attractors.len(),
end_time - start_time
);

View File

@ -12,7 +12,7 @@ extern "C" {
fn performance() -> web_sys::Performance;
}
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Attractor {
pub position: Point,
pub dead: bool,
@ -38,6 +38,9 @@ impl Attractor {
/// - Probably worth marking nodes as dead when no attractor is in range
#[derive(Debug, PartialEq, Eq)]
pub struct Node {
/// When a Node is born it is growing
/// it stops growing when there is no attractor in range
pub growing: bool,
pub position: Point,
pub children: Vec<Node>,
}
@ -76,6 +79,7 @@ impl Node {
fn new(position: Point) -> Self {
Self {
position,
growing: true,
children: Vec::new().into(),
}
}

View File

@ -1,5 +1,4 @@
use super::math::calculate_new_node_position;
use super::{Attractor, Node, NodeRef, Point};
use super::{Attractor, Node, Point};
use log::info;
use rand::thread_rng;
use rand::Rng;
@ -40,9 +39,21 @@ pub struct SpaceColonization {
///
/// If density is 10, then there will be an average distance of 10 between attractors
density: i32,
new_nodes: Vec<(NodeRef, Node)>,
/// Flat list of all nodes in the tree
/// [node, child1, grand-child, child2, grand-child2]
/// 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>,
}
@ -50,10 +61,7 @@ pub struct SpaceColonization {
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let mut nodes_vec = Vec::new();
nodes_vec.push(Node {
position: Point { x: 100, y: 100 },
children: Vec::new(),
});
nodes_vec.push(Node::new(Point::new((100, 100))));
let attractors = Vec::new();
let mut sc = SpaceColonization {
@ -67,7 +75,6 @@ impl SpaceColonization {
density: 30,
nodes: nodes_vec,
attractors,
new_nodes: Vec::new(),
};
sc.place_attractors();
@ -93,7 +100,6 @@ impl SpaceColonization {
density: 3,
nodes,
attractors,
new_nodes: Vec::new(),
}
}
@ -147,78 +153,87 @@ impl SpaceColonization {
}
}
pub fn grow(&self) {
pub fn grow(&mut 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
// [x] Find a clean API that will be stable across refactoring
// [ ] Write the test against this api including performance
// [x] 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();
println!("new nodes for iteration {:?}", self.new_nodes);
let mut nodes_mut = self.nodes;
for new_pair in self.new_nodes.iter() {
new_pair.0.children.push(new_pair.1);
nodes_mut.push(new_pair.1);
}
self.new_nodes.clear();
}
pub fn grow_nodes(&self) {
pub fn grow_nodes(&mut self) {
// 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 attractors = self.attractors;
let mut growing_paths: HashMap<<NodeRef>, Vec<AttractorRef>> = HashMap::new();
for a in attractors.iter() {
if a.dead {
continue;
}
let mut closest_node: Option<Node> = None;
let mut closest_node_distance = f64::MAX;
for n in self.nodes.iter() {
let distance = n.position.distance(&a.position);
if distance <= self.attraction_distance as f64 {
// TODO make sure it is closest node amongs all nodes
if distance < closest_node_distance {
closest_node = Some(n);
closest_node_distance = distance;
if distance < self.kill_distance as f64 {
a.dead.replace(true);
}
}
}
}
if let Some(node) = closest_node {
if let Some(attractors) = growing_paths.get_mut(&node) {
attractors.push(a);
} else {
let mut attractors = Vec::new();
attractors.push(a);
growing_paths.insert(node, attractors);
}
}
}
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.replace(true);
a.dead = true;
}
}
}
*/
}
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<Node>, mut map: HashMap<&'a Attractor, (&'a Node, f64)>) {
for n in nodes.iter() {
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;
}
for a in attractors {
if let Some(closest) = map.get(a.0) {
if a.1 < closest.1 {
map.insert(a.0, (n, a.1));
}
} else {
map.insert(a.0, (n, a.1));
}
}
self.new_nodes
.push((growth_cell.0, Node::new(position)));
}
}
fn find_attractors_in_range(&self, n: &Node) -> Vec<(&Attractor, f64)> {
let mut attractors_in_range = Vec::new();
for a in self.attractors.iter() {
let distance = n.position.distance(&a.position);
if distance < self.attraction_distance as f64 {
attractors_in_range.push((a, distance));
}
}
attractors_in_range
}
}
#[cfg(test)]
mod test {
use std::cell::RefCell;
@ -259,7 +274,6 @@ mod test {
sc.grow();
assert_eq!(sc.new_nodes.len(), 0);
assert!(sc
.attractors
.iter()