space-colonization: progress but runtime crashes, need to fix architecture to a better rust style

This commit is contained in:
Jean-Gabriel Gill-Couture 2023-07-23 09:01:53 -04:00
parent 1b899a76cc
commit 6f2505736c
5 changed files with 372 additions and 120 deletions

View File

@ -38,33 +38,38 @@ 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.iter() {
context.fill_rect(n.position.x.into(), n.position.y.into(), 5.0, 5.0);
for n in sc.root_nodes.iter() {
context.fill_rect(n.read().unwrap().position.x.into(), n.read().unwrap().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());
context.line_to(child.position.x.into(), child.position.y.into());
};
let render_id = window().unwrap().performance().unwrap().now();
for n in sc.nodes.iter() {
n.render(render_id, |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.render_nodes(render_id, render_node_fn);
context.stroke();
context.set_fill_style(&JsValue::from("magenta"));
for a in sc.attractors.iter() {
context.fill_rect(a.position.x.into(), a.position.y.into(), 5.0, 5.0);
context.fill_rect(a.read().unwrap().position.x.into(), a.read().unwrap().position.y.into(), 5.0, 5.0);
}
let end_time = window().unwrap().performance().unwrap().now();
log!(
"Rendering {} nodes and {} attractors took {}",
sc.nodes.len(),
sc.root_nodes.len(),
sc.attractors.len(),
end_time - start_time
);
sc.grow();
for _i in 1..15 {
sc.grow();
let render_id = window().unwrap().performance().unwrap().now();
context.begin_path();
sc.render_nodes(render_id, render_node_fn);
context.stroke();
}
});
let class = format!("canvas {}", class);

View File

@ -10,7 +10,7 @@ pub fn HomePage(cx: Scope) -> impl IntoView {
<h1 class="text-6xl text-orange-400 font-extrabold pt-8">"NationTech"</h1>
<h2 class="text-3xl">"Unconventional team for extraordinary challenges"</h2>
<p class="text-lg">"We love working with small teams to tackle hard tasks. We are passionate about maximizing the value we bring to the company and project, while taking care of the people we work with."</p>
<p class="">"We are experienced in a wide array of technologies, projects and companies."</p>
<p class="text-lg">"We are experienced in a wide array of technologies, projects and companies."</p>
<p class="text-lg">"We are eternal learners that feel a visceral need to deeply understand a problem or subject once it is on their plate."</p>
<p class="text-lg">"We don't cheap out, we always give our all, we always try to balance all the factors to find the best possible solution to any situation."</p>
<p class="text-lg">"The status quo does not exist, we always improve, automate, look for exponential gains across the stack."</p>

View File

@ -1,9 +1,11 @@
use leptos::*;
use rand::thread_rng;
use rand::Rng;
use std::sync::RwLock;
use wasm_bindgen::prelude::*;
use web_sys::console;
use web_sys::window;
mod point;
pub use point::*;
mod space_colonization;
pub use space_colonization::*;
#[wasm_bindgen]
extern "C" {
@ -11,125 +13,27 @@ extern "C" {
fn performance() -> web_sys::Performance;
}
#[derive(Debug)]
pub struct Point {
pub x: u16,
pub y: u16,
}
pub struct Attractor {
pub position: Point,
pub dead: bool,
}
#[derive(Debug)]
pub struct Node {
pub position: Point,
pub children: Vec<Node>,
pub connected: bool,
pub children: Vec<RwLock<Node>>,
}
impl Node {
pub fn render<F>(&self, render_id: f64, render_fn: F) where F: Copy + Fn(&Node, &Node) {
for child in self.children.iter() {
render_fn(self, child);
render_fn(self, &child.read().unwrap());
}
for child in self.children.iter() {
child.render(render_id, render_fn);
child.read().unwrap().render(render_id, render_fn);
}
}
}
pub struct SpaceColonization {
max_point: Point,
kill_distance: u16,
attraction_distance: u16,
segment_length: u16,
density: u16,
pub nodes: Vec<Node>,
pub attractors: Vec<Attractor>,
}
impl SpaceColonization {
pub fn new(width: u16, height: u16) -> SpaceColonization {
let mut nodes = Vec::new();
nodes.push(Node {
position: Point { x: 100, y: 100 },
children: Vec::new(),
connected: false,
});
let attractors = Vec::new();
let mut sc = SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 10,
attraction_distance: 15,
segment_length: 5,
density: 30,
nodes,
attractors,
};
sc.place_attractors();
return sc;
}
fn place_attractors(&mut self) {
let start_time = window().unwrap().performance().unwrap().now();
console::log_1(&format!("Start placing attractors {}", start_time).into());
let mut x_pos = 0;
let mut y_pos = 0;
while x_pos < self.max_point.x {
while y_pos < self.max_point.y {
self.attractors.push(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()),
});
y_pos += self.density;
}
x_pos += self.density;
y_pos = 0;
}
let end_time = window().unwrap().performance().unwrap().now();
let elapsed = end_time - start_time;
console::log_1(&format!("Done placing attractors , took : {}", elapsed).into());
}
fn get_random_point(&self, x_pos: i32, y_pos: i32) -> Point {
let half_density: i32 = (self.density / 2).into();
let mut x_min = x_pos - half_density;
if x_min < 0 {
x_min = 0;
}
let mut y_min = y_pos - half_density;
if y_min < 0 {
y_min = 0;
}
Point {
x: thread_rng()
.gen_range(x_min..x_pos + half_density)
.try_into()
.unwrap(),
y: thread_rng()
.gen_range(y_min..y_pos + half_density)
.try_into()
.unwrap(),
}
}
pub fn grow(&self) {
todo!();
// iterate through the list of nodes that are not dead yet (still had an active attractor
// previous iteration)
// For each node :
// find attractors within attraction distance of node
// calculate distance to affecting attractors
// determine how many new nodes grow from here
// determine position of new nodes
// remove current node from leaves list
}
}

View File

@ -0,0 +1,142 @@
#[derive(Debug)]
pub struct Point {
pub x: i32,
pub y: i32,
}
impl Point {
pub fn distance(&self, p: &Point) -> f64 {
if self.x == p.x {
return (p.y - self.y).into();
}
if self.y == p.y {
return (p.x - self.x).into();
}
let x_distance = p.x - self.x;
let y_distance = p.y - self.y;
return f64::from(x_distance.pow(2) + y_distance.pow(2)).sqrt();
}
}
#[cfg(test)]
mod tests {
use super::Point;
#[test]
fn distance_to_itself_is_zero() {
let p = Point {
x: 1,
y: 1,
};
assert_eq!(p.distance(&p), 0.0);
}
#[test]
fn distance_same_x_is_y() {
let p1 = Point {
x: 1,
y: 1,
};
let p2 = Point {
x: 1,
y: 5,
};
assert_eq!(p1.distance(&p2), 4.0);
}
#[test]
fn distance_same_y_is_x() {
let p1 = Point {
x: 1,
y: 1,
};
let p2 = Point {
x: 5,
y: 1,
};
assert_eq!(p1.distance(&p2), 4.0);
}
#[test]
fn distance_3_4_5() {
let p1 = Point {
x: 0,
y: 0,
};
let p2 = Point {
x: 3,
y: 4,
};
assert_eq!(p1.distance(&p2), 5.0);
}
#[test]
fn distance_quadrant3() {
let p1 = Point {
x: 3,
y: 4,
};
let p2 = Point {
x: 0,
y: 0,
};
assert_eq!(p1.distance(&p2), 5.0);
}
#[test]
fn distance_quadrant2() {
let p1 = Point {
x: 3,
y: 4,
};
let p2 = Point {
x: 0,
y: 100,
};
assert_eq!(p1.distance(&p2) as f32, 96.04687);
}
#[test]
fn distance_quadrant2_fast() {
let p1 = Point {
x: 3,
y: 4,
};
let p2 = Point {
x: 3,
y: 50,
};
assert_eq!(p1.distance(&p2), 46.0);
}
#[test]
fn distance_quadrant4() {
let p1 = Point {
x: 3,
y: 4,
};
let p2 = Point {
x: 50,
y: -50,
};
assert_eq!(p1.distance(&p2) as f32, 71.5891);
}
}

View File

@ -0,0 +1,201 @@
use std::sync::RwLock;
use super::{Attractor, Node, Point};
use rand::thread_rng;
use rand::Rng;
use web_sys::console;
use web_sys::window;
pub struct SpaceColonization {
max_point: Point,
/// When a node grows within kill_distance of an attractor, the attractor is killed
kill_distance: i32,
/// Maximum distance between an attractor and a node for the node to
/// be affected by the attractor.
///
/// Must be greater than sqrt((density)**2 + (density)**2)
attraction_distance: i32,
segment_length: i32,
/// Size of the cells on which attractors are placed.
///
/// If density is 10, then there will be an average distance of 10 between attractors
density: i32,
pub root_nodes: Vec<RwLock<Node>>,
pub attractors: Vec<RwLock<Attractor>>,
}
impl SpaceColonization {
pub fn new(width: i32, height: i32) -> SpaceColonization {
let mut root_nodes = Vec::new();
root_nodes.push(RwLock::new(Node {
position: Point { x: 100, y: 100 },
children: Vec::new(),
}));
let attractors = Vec::new();
let mut sc = SpaceColonization {
max_point: Point {
x: width,
y: height,
},
kill_distance: 10,
attraction_distance: 43,
segment_length: 5,
density: 30,
root_nodes,
attractors,
};
sc.place_attractors();
return sc;
}
pub fn render_nodes<F>(&self, render_id: f64, render_fn: F)
where
F: Copy + Fn(&Node, &Node),
{
for n in self.root_nodes.iter() {
n.read().unwrap().render(render_id, render_fn);
}
}
fn place_attractors(&mut self) {
let start_time = window().unwrap().performance().unwrap().now();
console::log_1(&format!("Start placing attractors {}", start_time).into());
let mut x_pos = 0;
let mut y_pos = 0;
while x_pos < self.max_point.x {
while y_pos < self.max_point.y {
self.attractors.push(RwLock::new(Attractor {
position: self.get_random_point(x_pos.into(), y_pos.into()),
dead: false,
}));
y_pos += self.density;
}
x_pos += self.density;
y_pos = 0;
}
let end_time = window().unwrap().performance().unwrap().now();
let elapsed = end_time - start_time;
console::log_1(&format!("Done placing attractors , took : {}", elapsed).into());
}
fn get_random_point(&self, x_pos: i32, y_pos: i32) -> Point {
let half_density: i32 = (self.density / 2).into();
let mut x_min = x_pos - half_density;
if x_min < 0 {
x_min = 0;
}
let mut y_min = y_pos - half_density;
if y_min < 0 {
y_min = 0;
}
Point {
x: thread_rng()
.gen_range(x_min..x_pos + half_density)
.try_into()
.unwrap(),
y: thread_rng()
.gen_range(y_min..y_pos + half_density)
.try_into()
.unwrap(),
}
}
pub fn grow(&self) {
for n in self.root_nodes.iter() {
self.grow_node(n);
}
// iterate through the list of nodes that are not dead yet (still had an active attractor
// previous iteration)
// For each node :
// find attractors within attraction distance of node
// calculate distance to affecting attractors
// determine how many new nodes grow from here
// determine position of new nodes
// remove current node from leaves list
}
fn grow_node(&self, n: &RwLock<Node>) {
n.read()
.unwrap()
.children
.iter()
.for_each(|n| self.grow_node(n));
let affecting_attractors = self.find_affecting_attractors(&n);
console::log_1(
&format!("Found {} affecting attractors", affecting_attractors.len()).into(),
);
let new_node = self.create_new_node(n, &affecting_attractors);
for a in affecting_attractors {
let mut a = a.write().unwrap();
if n.read().unwrap().position.distance(&a.position) < self.kill_distance as f64 {
a.dead = true;
}
}
console::log_1(&format!("New node {:?}", new_node).into());
n.write().unwrap().children.push(RwLock::new(new_node));
}
fn find_affecting_attractors(&self, n: &RwLock<Node>) -> Vec<&RwLock<Attractor>> {
let mut affecting = Vec::new();
for a in self.attractors.iter() {
let aread = a.read().unwrap();
// TODO remove attractors instead of marking them dead
// used to display them at the moment but could also be moved
// to a dead queue
if aread.dead {
continue;
}
let n_distance = n.read().unwrap().position.distance(&aread.position);
// todo for some reason I cannot verify closest node to attractor here
if n_distance < self.attraction_distance.into() && self.is_closest_node(&n.read().unwrap(), &aread) {
affecting.push(a)
}
}
affecting
}
fn create_new_node(
&self,
n: &RwLock<Node>,
affecting_attractors: &Vec<&RwLock<Attractor>>,
) -> Node {
let n = n.read().unwrap();
let mut attraction_sum_x = 0;
let mut attraction_sum_y = 0;
for a in affecting_attractors.iter() {
attraction_sum_x += n.position.x - a.read().unwrap().position.x;
attraction_sum_y += n.position.y - a.read().unwrap().position.y;
}
Node {
position: Point {
x: n.position.x + attraction_sum_x / affecting_attractors.len() as i32,
y: n.position.y + attraction_sum_y / affecting_attractors.len() as i32,
},
children: Vec::new(),
}
}
fn is_closest_node(&self, node: &Node, a: &Attractor) -> bool {
let node_distance = node.position.distance(&a.position);
for n in self.root_nodes.iter() {
let n_read = match n.read() {
Ok(val) => val,
Err(e) => todo!("Cannot read node {}", e),
};
if n_read.position.distance(&a.position) < node_distance {
return false;
}
// todo iterate the entire tree
todo!();
}
return true;
}
}