feat(space colonization): Only render new node when it is created, should improve performance instead of rerendering every node on every tick

This commit is contained in:
jeangab 2023-08-11 11:55:48 -04:00
parent 97cf5b21d9
commit c4cbef68d9
3 changed files with 90 additions and 51 deletions

View File

@ -25,19 +25,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let window_height = u32::try_from(height).unwrap();
canvas.set_width(window_width);
canvas.set_height(window_height);
let mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap());
let nodes = Rc::new(RefCell::new(Vec::new()));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width / 2) as i32,
(window_height / 2) as i32,
))));
// TODO Resize on window resize
log!(
"TODO resize on window resize canvas parent size = {} {}",
canvas_parent.client_width(),
canvas_parent.client_height()
);
log!("in canvas");
let context = canvas
.get_context("2d")
.ok()
@ -46,17 +34,54 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
.unchecked_into::<web_sys::CanvasRenderingContext2d>();
log!("context = {:#?}", context);
context.set_stroke_style(&JsValue::from("white"));
let context_to_render = context.clone();
// let render_node_fn : 'a Fn(&Node, &Node) = |n: &Node, child: &Node| {
let render_node_fn = move |n: &Node, child: &Node| {
context_to_render.move_to(n.position.x.into(), n.position.y.into());
context_to_render.line_to(child.position.x.into(), child.position.y.into());
};
let mut sc = SpaceColonization::new(width.try_into().unwrap(), height.try_into().unwrap(), render_node_fn);
let nodes = Rc::new(RefCell::new(Vec::new()));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width / 3) as i32,
(window_height / 3) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width / 2) as i32,
(window_height / 3) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width / 3) as i32,
(window_height / 2) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width - 200) as i32,
(window_height / 3) as i32,
))));
nodes.borrow_mut().push(Node::new(Point::new((
(window_width - 100) as i32,
(window_height - 100) as i32,
))));
// TODO Resize on window resize
log!(
"TODO resize on window resize canvas parent size = {} {}",
canvas_parent.client_width(),
canvas_parent.client_height()
);
context.set_stroke_style(&JsValue::from("white"));
context.set_fill_style(&JsValue::from("yellow"));
log!("About to render nodes");
let start_time = window().unwrap().performance().unwrap().now();
context.begin_path();
/*
let render_node_fn = |n: &Node, child: &Node| {
context.move_to(n.position.x.into(), n.position.y.into());
context.line_to(child.position.x.into(), child.position.y.into());
};
let render_id = window().unwrap().performance().unwrap().now();
sc.render_nodes(&nodes.borrow(), render_id, render_node_fn);
*/
// let render_id = window().unwrap().performance().unwrap().now();
// sc.render_all_nodes(&nodes.borrow(), render_id, render_node_fn);
context.stroke();
let end_time = window().unwrap().performance().unwrap().now();
@ -68,15 +93,12 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let context = context.clone();
let closure = Closure::<dyn FnMut(_)>::new(move |_: web_sys::MouseEvent| {
let start_time = window().unwrap().performance().unwrap().now();
let render_node_fn = |n: &Node, child: &Node| {
context.move_to(n.position.x.into(), n.position.y.into());
context.line_to(child.position.x.into(), child.position.y.into());
};
sc.grow(&mut nodes.borrow_mut());
let render_id = window().unwrap().performance().unwrap().now();
context.begin_path();
sc.render_nodes(&nodes.borrow(), render_id, render_node_fn);
// let render_id = window().unwrap().performance().unwrap().now();
// context.begin_path();
// sc.render_nodes(&nodes.borrow(), render_id, render_node_fn);
/*
context.set_fill_style(&JsValue::from("magenta"));
for a in sc.attractors.iter().filter(|a| a.dead) {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
@ -85,6 +107,7 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
for a in sc.attractors.iter().filter(|a| !a.dead) {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
}
*/
context.stroke();
let end_time = window().unwrap().performance().unwrap().now();
log!(
@ -96,7 +119,10 @@ pub fn Background(cx: Scope, class: &'static str) -> impl IntoView {
let window = window().unwrap();
window
.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
.set_interval_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
10,
)
.unwrap();
closure.forget();
});

View File

@ -62,9 +62,9 @@ impl std::hash::Hash for Node {
}
impl Node {
pub fn render<F>(&self, render_id: f64, render_fn: F)
pub fn render<G>(&self, render_id: f64, render_fn: G)
where
F: Copy + Fn(&Node, &Node),
G: Copy + Fn(&Node, &Node),
{
for child in self.children.iter() {
render_fn(self, &child);

View File

@ -6,7 +6,10 @@ use rand::thread_rng;
use rand::Rng;
use std::collections::HashMap;
pub struct SpaceColonization {
pub struct SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
max_point: Point,
/// When a node grows within kill_distance of an attractor, the attractor is killed
kill_distance: f64,
@ -42,10 +45,17 @@ pub struct SpaceColonization {
/// If density is 10, then there will be an average distance of 10 between attractors
density: i32,
pub attractors: Vec<Attractor>,
render_fn: F,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
impl<F> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
pub fn new(width: i32, height: i32, render_fn: F) -> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
let attractors = Vec::new();
let mut sc = SpaceColonization {
@ -58,6 +68,7 @@ impl SpaceColonization {
segment_length: 5,
density: 30,
attractors,
render_fn,
};
sc.place_attractors();
@ -66,7 +77,15 @@ impl SpaceColonization {
}
#[cfg(test)]
pub fn new_for_tests(width: i32, height: i32, attractors: Vec<Attractor>) -> SpaceColonization {
pub fn new_for_tests(
width: i32,
height: i32,
attractors: Vec<Attractor>,
render_fn: F,
) -> SpaceColonization<F>
where
F: Fn(&Node, &Node),
{
SpaceColonization {
max_point: Point {
x: width,
@ -77,12 +96,13 @@ impl SpaceColonization {
segment_length: 3,
density: 3,
attractors,
render_fn,
}
}
pub fn render_nodes<F>(&self, nodes: &Vec<Node>, render_id: f64, render_fn: F)
pub fn render_all_nodes<G>(&self, nodes: &Vec<Node>, render_id: f64, render_fn: G)
where
F: Copy + Fn(&Node, &Node),
G: Copy + Fn(&Node, &Node),
{
info!("Rendering {} nodes", nodes.len());
for n in nodes.iter() {
@ -129,16 +149,7 @@ impl SpaceColonization {
}
}
pub fn grow<'a>(&mut self, mut nodes: &'a mut Vec<Node>) {
// TODO
// [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
pub fn grow<'b>(&mut self, mut nodes: &'b mut Vec<Node>) {
info!("Growing nodes {:?}", nodes);
self.grow_nodes(&mut nodes)
}
@ -211,14 +222,15 @@ impl SpaceColonization {
(**a).dead = true;
}
});
(self.render_fn)(&**node, &new_node);
(**node).children.push(new_node);
}
}
});
}
fn build_attractor_to_closest_node<'a>(
&'a mut self,
fn build_attractor_to_closest_node<'b>(
&'b mut self,
mut attractor_to_closest_node: &mut HashMap<*mut Attractor, Attraction>,
n: &mut Node,
) {
@ -246,7 +258,6 @@ impl SpaceColonization {
attractor_to_closest_node.insert(a.0, Attraction::new(n, a.1));
}
}
}
fn find_attractors_in_range(&mut self, n: &Node) -> Vec<(*mut Attractor, f64)> {
@ -267,13 +278,15 @@ mod test {
use super::*;
fn assert_vertices(
sc: &SpaceColonization,
nodes: &Vec<Node>,
fn assert_vertices<'a, F>(
sc: &SpaceColonization<F>,
nodes: &'a Vec<Node>,
mut expected_nodes: Vec<(Point, Point)>,
) {
) where
F: Copy + Fn(&Node, &Node),
{
let rendered_nodes = RefCell::new(Vec::new());
sc.render_nodes(&nodes, 0.0, |n1, n2| {
sc.render_all_nodes(&nodes, 0.0, |n1: &Node, n2: &Node| {
rendered_nodes.borrow_mut().push((n1.position, n2.position));
});
let sort_points = |line1: &(Point, Point), line2: &(Point, Point)| {
@ -298,7 +311,7 @@ mod test {
let mut attractors = Vec::new();
attractors.push(Attractor::new(Point::new((10, 0))));
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors);
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors, |_, _|{});
assert_eq!(sc.attractors.len(), 1);
assert!(sc.attractors.iter().find(|a| a.dead == true).is_none());
@ -344,7 +357,7 @@ mod test {
dead: true,
});
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors);
let mut sc = SpaceColonization::new_for_tests(100, 100, attractors, |_, _|{});
assert_eq!(sc.attractors.len(), 1);
assert!(sc.attractors.iter().find(|a| a.dead == true).is_some());