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:
parent
97cf5b21d9
commit
c4cbef68d9
@ -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();
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user