I'm writing a graphical simulation to analyze physics interactions at the subatomic level and doing so by creating my own structures (struct Simiulation, struct Particle,...). For initial testing and development purposes I'm using rand::thread_rng().gen_rang(). In order to use this simulation, I have to add use rand::Rng; at the top of the file. As a new Rust user (1 week), I'm unsure if this can be included automatically, without having to type it, in the event I share the simulation with a colleague for collaborative purposes.
I googled nested namespaces which presented C++ / JS options but not Rust. I checked StackOverflow as well, but didn't find anything. I haven't reached the Cargo section in the Book as of yet. My terminology man be incorrect, so let me know if it's called something else in Rust.
Partial Sample Code
use rand::Rng; // <-- Nest this?
// - global -
const NUM_PARTICLES: usize = 5;
const WINDOW_WIDTH: f64 = 500.0; // TODO: Determine Window Width
const WINDOW_HEIGHT: f64 = 500.0; // TODO: Determine Window Height
const PARTICLE_RADIUS: f64 = 2.0; // TODO: Generate max probability distributon
// cross-sectional area to be used (per particle)
fn main() {
let mut starting_image = generate_starting_image();
println!("{:#?}", starting_image);
}
// Each particle has a position with respect to
// the origin and is denoted by (x, y)
// where x is the projection of the position along
// the x-axis and y is the projection of the position along
// the y-axis.
//
// Each particle has a veloctiy denoted by (Vx, Vy)
// where Vx is the velocity component along the x-axis and
// Vy is the velocity component along the y-axis.
#[derive(Debug, Copy, Clone)]
struct Mass {
mass: f64,
}
impl Mass {
// generate random mass for the particle (in kgs)
fn random() -> Self {
Self {
mass: rand::thread_rng().gen_range(1.00..=6.00),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Position {
x: f64,
y: f64,
}
impl Position {
// generate random starting position (x, y) for a particle
fn random() -> Self {
Self {
x: rand::thread_rng().gen_range(1.0..=(WINDOW_WIDTH - 1.0)),
y: rand::thread_rng().gen_range(1.0..=(WINDOW_HEIGHT - 1.0)),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Velocity {
vx: f64,
vy: f64,
}
impl Velocity {
// generate random starting velocity (vx, vy) for a particle
fn random() -> Self {
Self {
vx: rand::thread_rng().gen_range(5.0..20.0),
vy: rand::thread_rng().gen_range(5.0..20.0),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Particle {
position: Position,
velocity: Velocity,
mass: Mass, // mass in kilograms
}
impl Particle {
fn random() -> Self {
Self {
position: Position::random(),
velocity: Velocity::random(),
mass: Mass::random(),
}
}
}
// An image is a snapshot of all the attribute values for each
// particle at some given time interval dt.
#[derive(Debug, Copy, Clone)]
struct Image {
particles: [Particle; NUM_PARTICLES],
}
// Calculate the distance between two points. Necessary to
// determine if a collision is taken place
fn distance (particle_1: &Particle, particle_2: &Particle) -> f64 {
let base_1: f64 = particle_1.position.x - particle_2.position.x;
let base_2: f64 = particle_1.position.y - particle_2.position.y;
let base: f64 = f64::powf(base_1, 2.0) + f64::powf(base_2, 2.0);
f64::powf(base, 0.5)
}
fn generate_starting_image() -> Image {
let mut starting_image = Image {
particles: [Particle::random(); NUM_PARTICLES]
};
let iter = starting_image.particles.iter();
let mut num_created_particles: usize = 0;
// Loop over the starting_image array to populate the
// array with starting particle values
loop {
// stop the loop when the number of particles has
// been generated
if num_created_particles == NUM_PARTICLES { break; }
// loop over the populated values and check to
// make sure the distance between the newly generated
// particle does not occupy the same position as
// a previously generated particle
// Particle to test
let mut current_particle = Particle::random();
// indices of checked particles
let mut checked_particles: usize = 0;
while checked_particles < num_created_particles {
if distance(¤t_particle, &starting_image.particles[checked_particles]) <= 4.0 {
current_particle = Particle::random();
checked_particles = 0;
}
// Newly generated particle is further than the minimum
// needed to be placed. increment to next particle to
// be checked.
checked_particles = checked_particles + 1;
}
// Newly generated particle has been checked for proximity
// against all previously generated particles. Insert
// into the particle array and increment to
// generate the next particle
println!("num_created_particles = {num_created_particles}");
starting_image.particles[num_created_particles] = current_particle;
num_created_particles = num_created_particles + 1;
checked_particles = 0;
}
// The starting values for each particle has been generated.
// Return array to begin the simulation
starting_image
}
Related
I'm working through the Rust WASM tutorial for Conway's game of life.
One of the simplest functions in the file is called Universe.render (it's the one for rendering a string representing game state). It's causing an error when I run wasm-pack build:
Fatal: error in validating input
Error: failed to execute `wasm-opt`: exited with exit code: 1
full command: "/home/vaer/.cache/.wasm-pack/wasm-opt-4d7a65327e9363b7/wasm-opt" "/home/vaer/src/learn-rust/wasm-game-of-life/pkg/wasm_game_of_life_bg.wasm" "-o" "/home/vaer/src/learn-rust/wasm-game-of-life/pkg/wasm_game_of_life_bg.wasm-opt.wasm" "-O"
To disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.
If I remove that function, the code builds without errors. If I replace it with the following function, the build fails with the same error:
pub fn wtf() -> String {
String::from("wtf")
}
It seems like any function that returns a String causes this error. Why?
Following is the entirety of my code:
mod utils;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
// Begin game of life impl
use std::fmt;
#[wasm_bindgen]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cell {
Dead = 0,
Alive = 1,
}
#[wasm_bindgen]
pub struct Universe {
width: u32,
height: u32,
cells: Vec<Cell>,
}
impl fmt::Display for Universe {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for line in self.cells.as_slice().chunks(self.width as usize) {
for &cell in line {
let symbol = if cell == Cell::Dead { '◻' } else { '◼' };
write!(f, "{}", symbol)?;
}
write!(f, "\n")?;
}
Ok(())
}
}
impl Universe {
fn get_index(&self, row: u32, column: u32) -> usize {
(row * self.width + column) as usize
}
fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
let mut count = 0;
for delta_row in [self.height - 1, 0, 1].iter().cloned() {
for delta_col in [self.width - 1, 0, 1].iter().cloned() {
if delta_row == 0 && delta_col == 0 {
continue;
}
let neighbor_row = (row + delta_row) % self.height;
let neighbor_col = (column + delta_col) % self.width;
let idx = self.get_index(neighbor_row, neighbor_col);
count += self.cells[idx] as u8;
}
}
count
}
}
/// Public methods, exported to JavaScript.
#[wasm_bindgen]
impl Universe {
pub fn tick(&mut self) {
let mut next = self.cells.clone();
for row in 0..self.height {
for col in 0..self.width {
let idx = self.get_index(row, col);
let cell = self.cells[idx];
let live_neighbors = self.live_neighbor_count(row, col);
let next_cell = match (cell, live_neighbors) {
// Rule 1: Any live cell with fewer than two live neighbours
// dies, as if caused by underpopulation.
(Cell::Alive, x) if x < 2 => Cell::Dead,
// Rule 2: Any live cell with two or three live neighbours
// lives on to the next generation.
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
// Rule 3: Any live cell with more than three live
// neighbours dies, as if by overpopulation.
(Cell::Alive, x) if x > 3 => Cell::Dead,
// Rule 4: Any dead cell with exactly three live neighbours
// becomes a live cell, as if by reproduction.
(Cell::Dead, 3) => Cell::Alive,
// All other cells remain in the same state.
(otherwise, _) => otherwise,
};
next[idx] = next_cell;
}
}
self.cells = next;
}
pub fn new() -> Universe {
let width = 64;
let height = 64;
let cells = (0..width * height)
.map(|i| {
if i % 2 == 0 || i % 7 == 0 {
Cell::Alive
} else {
Cell::Dead
}
})
.collect();
Universe {
width,
height,
cells,
}
}
pub fn render(&self) -> String {
self.to_string()
}
}
Simply removing the render function at the bottom of this file causes the build to succeed. Replacing the render function with any function returning a String causes the build to fail. Why?
It turns out that this is not expected behavior; instead it is a bug with wasm-pack.
The issue can be resolved for now by adding the following to the project's cargo.toml:
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-Oz", "--enable-mutable-globals"]
I want to generate DAGs with proptest. The algorithm that I pick would be this. I've written the plain algorithm below -- but I need help transforming this to a proptest strategy.
What would a strategy need to look like that did the same as the below code but without using a random number generator? (It goes without saying that random number generators are bad for property-based testing.)
Standard code without proptest strategy:
(https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2de4a757a96d123bf83b5157e0633d33)
use rand::Rng;
fn main() {
println!("{:?}", random_vec_of_vec());
}
fn random_vec_of_vec() -> Vec<Vec<u16>> {
const N: u16 = 30;
const K: usize = 3;
let mut rng = rand::thread_rng();
let length: u16 = rng.gen_range(0, N);
let mut outer = vec![];
for index in 1..length {
let mut inner = vec![0u16; rng.gen_range(0, K)];
for e in &mut inner {
*e = rng.gen_range(0, index);
}
// De-duplicate elements. Particularly a problem with `index < K`.
inner.sort();
inner.dedup();
outer.push(inner);
}
outer
}
Previous work
I tried using the vec function, but I would need to nest two vec functions. And, the inner vec function could only generate values up to the index in the outer vector.
use proptest::collection::vec;
// INDEX should be the value of the position of the inner vector
// in the outer vector. How could the be found?
let strategy = vec(vec(1..INDEX, 0..K), 0..N);
The index method is not helpful because the right size would still not be known.
One way to go about this, is to replace each rng.gen_range() call with a strategy. Nested strategies must then be connected with prop_flat_map.
In the below code, I replaced my pattern
let length = rng.gen_range(0, N); for i in 1..length { .. }, with a new function vec_from_length(length: usize), which returns a Strategy.
#[cfg(test)]
mod tests {
use super::*;
use proptest::collection::hash_set;
use proptest::prelude::*;
use std::collections::HashSet;
proptest! {
#[test]
fn meaningless_test(v in vec_of_vec()) {
let s = sum(&v); // sum of the sum of all vectors.
prop_assert!(s < 15);
}
}
fn vec_of_vec() -> impl Strategy<Value = Vec<Vec<u16>>> {
const N: u16 = 10;
let length = 0..N;
length.prop_flat_map(vec_from_length).prop_map(convert)
}
fn vec_from_length(length: u16) -> impl Strategy<Value = Vec<HashSet<u16>>> {
const K: usize = 5;
let mut result = vec![];
for index in 1..length {
// Using a hash_set instead of vec because the elements should be unique.
let inner = hash_set(0..index, 0..K);
result.push(inner);
}
result
}
/// Convert Vec<HashSet<T>> to Vec<Vec<T>>
fn convert(input: Vec<HashSet<u16>>) -> Vec<Vec<u16>> {
let mut output = vec![];
for inner in input {
output.push(inner.into_iter().collect())
}
output
}
}
One more thing: An impl Strategy<Value=Vec<T>> can be generated from either the vec function (a strategy of vector) or from a vector of strategies! In the above code, I do this through having result be pushed with hash_set(..) which is a Strategy. The type is thus something like Vec<Strategy<T>> not Strategy<Vec<T>> (pedantic: Strategy is not a type, maybe).
I have the function get_screen that's specified in a separate module from main.rs. It takes two 2D vectors (one that's 1920x1080 and called screen and another one that's even larger called world) and maps a portion of the world vector to the screen vector. This is the function signature when I first made it:
pub fn get_screen(
screen: &mut Vec<Vec<[u8; 4]>>,
world: &Vec<Vec<Chunk>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
chunk_width: usize,
chunk_height: usize,
)
I had serious issues with execution time, but I optimized it from 14ms down to 3ms by using #[inline].
I then moved the world vector to its own struct (alongside some other related variables like chunk width/height) and made the get_screen function into a method in the new world struct. This is what the function signature looked like after that change:
pub fn get_screen(
&self,
screen: &mut Vec<Vec<[u8; 4]>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
)
Then the execution time increases back to 14ms. I've tried enabling lto=true in Cargo.toml and switching to #[inline(always)] to enforce it, but it seems like the compiler refuses to optimize this function the way it used to.
I attempted to remove the get_screen method from the struct and run it as its own function like before and that seems to fix it, but only if I don't pass anything from the struct. If I attempt to pass even a usize from the world struct to the separate get_screen function, then the execution time increases from 3ms back to 14ms.
To show an example of what I mean, if I pass nothing directly from the world struct and instead pass it a cloned version of the 2D struct in world and the hardcoded chunk_width/chunk_height:
gen::get_screen(
&mut screen.buf,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
CHUNK_WIDTH,
CHUNK_HEIGHT,
);
It runs in 3.3ms. When I pass the usize fields chunk_width/chunk_height directly from the world struct:
gen::get_screen(
&mut screen.buf,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
world.chunk_width,
world.chunk_height,
);
it takes 14.55ms to run
What's up here? How can I get my get_screen function to compile inline while using my World struct? Preferably I'd like to be able to re-add it to my World struct as a method instead of keeping it separate.
Here is a minimal example:
use std::time::Instant;
const SCREEN_HEIGHT: usize = 1080; //528;
const SCREEN_WIDTH: usize = 1920; //960;
const CHUNK_WIDTH: usize = 256;
const CHUNK_HEIGHT: usize = 256;
const GEN_RANGE: isize = 25; //how far out to gen chunks
fn main() {
let batch_size = 1_000;
struct_test(batch_size);
separate_test(batch_size);
}
fn struct_test(batch_size: u32) {
let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
let camera_coords: (isize, isize) = (0, 0); //set camera location
let start = Instant::now();
for _ in 0..batch_size {
get_screen(
&mut screen,
&world.data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
world.chunk_width,
world.chunk_height,
); //gets visible pixels from world as 2d vec
}
println!(
"struct: {:?} {:?}",
start.elapsed(),
start.elapsed() / batch_size
);
}
fn separate_test(batch_size: u32) {
let world = World::new(CHUNK_WIDTH, CHUNK_HEIGHT, GEN_RANGE); //generate world
let cloned_world_data = world.data.clone();
let mut screen = vec![vec!([0; 4]; SCREEN_WIDTH); SCREEN_HEIGHT];
let camera_coords: (isize, isize) = (0, 0); //set camera location
let start = Instant::now();
for _ in 0..batch_size {
get_screen(
&mut screen,
&cloned_world_data,
camera_coords,
SCREEN_WIDTH,
SCREEN_HEIGHT,
CHUNK_WIDTH,
CHUNK_HEIGHT,
); //gets visible pixels from world as 2d vec
}
println!(
"separate: {:?} {:?}",
start.elapsed(),
start.elapsed() / batch_size
);
}
///gets all visible pixels on screen relative camera position in world
#[inline(always)] //INLINE STOPPED WORKING??
pub fn get_screen(
screen: &mut Vec<Vec<[u8; 4]>>,
world: &Vec<Vec<Chunk>>,
camera_coords: (isize, isize),
screen_width: usize,
screen_height: usize,
chunk_width: usize,
chunk_height: usize,
) {
let camera = get_local_coords(&world, camera_coords, chunk_width, chunk_height); //gets loaded coords of camera in loaded chunks
(camera.1 - screen_height as isize / 2..camera.1 + screen_height as isize / 2)
.enumerate()
.for_each(|(py, y)| {
//for screen pixel index and particle in range of camera loaded y
let (cy, ly) = get_local_pair(y, chunk_height); //calculate chunk y and inner y from loaded y
if let Some(c_row) = world.get(cy) {
//if chunk row at loaded chunk y exists
(camera.0 - screen_width as isize / 2..camera.0 + screen_width as isize / 2)
.enumerate()
.for_each(|(px, x)| {
//for screen pixel index and particle in range of camera loaded x
let (cx, lx) = get_local_pair(x, chunk_width); //get loaded chunk x and inner x from loaded x
if let Some(c) = c_row.get(cx) {
screen[py][px] = c.data[ly][lx];
}
//if chunk in row then copy color of target particle in chunk
else {
screen[py][px] = [0; 4]
} //if target chunk doesn't exist color black
})
} else {
screen[py].iter_mut().for_each(|px| *px = [0; 4])
} //if target chunk row doesn't exist color row black
});
}
///calculates local coordinates in world vec from your global position
///returns negative if above/left of rendered area
pub fn get_local_coords(
world: &Vec<Vec<Chunk>>,
coords: (isize, isize),
chunk_width: usize,
chunk_height: usize,
) -> (isize, isize) {
let (wx, wy) = world[0][0].chunk_coords; //gets coords of first chunk in rendered vec
let lx = coords.0 - (wx * chunk_width as isize); //calculates local x coord based off world coords of first chunk
let ly = (wy * chunk_height as isize) - coords.1; //calculates local y coord based off world coords of first chunk
(lx, ly)
}
pub fn get_local_pair(coord: isize, chunk: usize) -> (usize, usize) {
(coord as usize / chunk, coord as usize % chunk)
}
///contains chunk data
#[derive(Clone)]
pub struct Chunk {
//world chunk object
pub chunk_coords: (isize, isize), //chunk coordinates
pub data: Vec<Vec<[u8; 4]>>, //chunk Particle data
}
impl Chunk {
///generates chunk
fn new(chunk_coords: (isize, isize), chunk_width: usize, chunk_height: usize) -> Self {
let data = vec![vec!([0; 4]; chunk_width); chunk_height];
Self { chunk_coords, data }
}
}
pub struct World {
pub data: Vec<Vec<Chunk>>,
pub chunk_width: usize,
pub chunk_height: usize,
}
impl World {
pub fn new(chunk_width: usize, chunk_height: usize, gen_range: isize) -> Self {
let mut data = Vec::new(); //creates empty vec to hold world
for (yi, world_chunk_y) in (gen_range * -1..gen_range + 1).rev().enumerate() {
//for y index, y in gen range counting down
data.push(Vec::new()); //push new row
for world_chunk_x in gen_range * -1..gen_range + 1 {
//for chunk in gen range of row
data[yi].push(Chunk::new(
(world_chunk_x, world_chunk_y),
chunk_width,
chunk_height,
)); //gen new chunk and put it there
}
}
Self {
data,
chunk_width,
chunk_height,
}
}
}
Probably, when you use world.chunk_width and world.chunk_height as parameters the compiler does not consider these parameters as constants and then actually generates division and modulus operations.
On the other hand, when you provide constants for these parameters, they can be propagated in the algorithm (constant folding) and some expensive operations (division, modulus) are not performed (or transformed into bit-shifts/masks).
Copying/pasting your code in godbolt (compiler explorer), making separate_test() and struct_test() public, and compiling with -C opt-level=3 confirms this since div instructions are present in the generated code for struct_test() but not for separate_test().
I'm trying to implement the BVH algorithm in my Rust ray tracer, but I'm having trouble with lifetimes and ownership. I have a trait Hittable that a bunch of different things implement -- Sphere, Triangle, Mesh, etc. And so I have a Vec<Box<dyn Hittable>>, which I want to turn into a tree of this struct:
pub struct BvhNode {
bounding_box: BoundingBox,
left: Box<dyn Hittable>,
right: Box<dyn Hittable>,
}
And so I have this recursive algorithm that almost works, if not for lifetime issues. My function looks like
pub fn new(objects: Vec<Box<dyn Hittable>>, start: usize, end: usize, t0: f64, t1: f64) -> Self {
let r = util::rand();
let comp = if r < 1. / 3. {
util::box_x_compare
} else if r < 2. / 3. {
util::box_y_compare
} else {
util::box_z_compare
}; // which axis to compare along (random for now)
let num_obj = end - start;
let mut left: Box<dyn Hittable>;
let mut right: Box<dyn Hittable>;
if num_obj == 1 {
left = objects[start];
right = objects[start];
} else if num_obj == 2 {
if comp(&&objects[start], &&objects[start + 1]) != Ordering::Greater {
left = objects[start];
right = objects[start + 1];
} else {
left = objects[start + 1];
right = objects[start];
}
} else {
let mut slice: Vec<&Box<dyn Hittable>> = Vec::new();
for i in start..end { // make a copy to sort
slice.push(&objects[i]);
}
slice.sort_by(comp);
let mid = start + num_obj / 2;
let l = BvhNode::new(objects, start, mid, t0, t1);
let r = BvhNode::new(objects, mid, end, t0, t1);
left = Box::new(l.clone());
right = Box::new(r.clone());
}
let left_box = left.get_bounding_box(t0, t1);
let right_box = right.get_bounding_box(t0, t1);
if left_box.is_none() || right_box.is_none() {
println!("Error: No bounding box in Bvh Node");
panic!();
}
Self { left, right, bounding_box: BoundingBox::new(Point3::origin(), Point3::origin()) }
}
First, I ran into some issues with trying to "move out of a vec", which I can't do, so I tried to implement Clone on all of the types that implement Hittable. It works for almost all of them, but my Triangle struct
pub struct Triangle <'b> {
mat: &'b Box<dyn Material>,
bounding_box: Option<BoundingBox>,
p: Vec<Point3<f64>>,
n: Vec<Vector3<f64>>,
uv: Vec<Vector2<f64>>,
}
contains reference to the material of its associated mesh, which clone doesn't like. I could make a clone of the Material itself, but it could potentially be very large and I don't want to have thousands of copies of it for each triangle in a mesh.
I feel like there has to be a better way to design my system, like get rid of references in Triangle so it can easily be copied. Once this structure is created, I won't need the Vec anymore, so being able to move the objects to out of it inside of copying them would also work, but I don't see a way to do that either.
In case it helps, the full file is on GitHub here
I was able to solve this by changing the objects Vec to &mut Vec<Box<dyn Hittable>> and then using objects.remove() instead of indexing into it. I had to change the algorithm slightly to handle the new way, but it should work.
I'm trying to write a raytracer in Rust. I'm having difficulty getting the for loops to run in parallel. I'm not sure where the problem is, but I can't seem to get anything on the screen. Is this the correct approach or am I completely heading in the wrong direction?
I've tried running the for loops without multi-threading and it does correctly produce output. I've also added loggers to the consumer loop and I'm getting the correct values as well. It just doesn't seem to update the window.
#[derive(Clone, Copy)]
pub struct Pixel {
pub x: usize,
pub y: usize,
pub color: Vec3,
}
let mut buffer : Vec<u32> = vec![0; WIDTH * HEIGHT];
let (tx, rx) = mpsc::channel()
for x in 0..HEIGHT {
let tx_t = tx.clone();
thread::spawn(move || {
for y in 0..WIDTH {
let mut color = cast_ray(x, y); // returns vec3
let pixel = Pixel { x: x, y: y, color: color };
tx_t.send(pixel).unwrap();
}
});
}
for received in rx {
buffer[received.x * WIDTH + received.y] = received.color.x << 16 | received.color.y << 8 | received.color.z;
}
while window.is_open() && !window.is_key_down(Key::Escape) {
window.update_with_buffer(&buffer).unwrap();
}
I'm expecting a few spheres or color to appear on the screen, but it's just black.