diff --git a/src/cell_event.rs b/src/cell_event.rs deleted file mode 100644 index b00d483..0000000 --- a/src/cell_event.rs +++ /dev/null @@ -1 +0,0 @@ -pub struct CellStatesChangedEvent; diff --git a/src/cells_multithreaded_v2.rs b/src/cells_multithreaded_v2.rs new file mode 100644 index 0000000..fd925b4 --- /dev/null +++ b/src/cells_multithreaded_v2.rs @@ -0,0 +1,313 @@ +use std::hash::Hash; +use std::ops::{Index, IndexMut}; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::time::Instant; + +use bevy::tasks::{ComputeTaskPool, ParallelSlice, TaskPool, ParallelSliceMut}; +use bevy::{ + input::Input, + math::{ivec3, vec3, IVec3}, + prelude::*, +}; +use rand::Rng; + +use crate::rotating_camera::UpdateEvent; +use crate::{ + cell_renderer::{InstanceData, InstanceMaterialData}, + rule::Rule, + utils::{self, keep_in_bounds}, +}; + +struct Cells { + /// Cache of [`Rule::bounding_size`] + bounding_size: i32, + + /// length of one side of the 3d array + side_length: usize, + + /// 3D arrary of (value, neighbours) + data: Vec<(u8, AtomicU8)>, +} + +impl Cells { + fn zeros(rule: &Rule) -> Self { + + let side_length: usize = (rule.bounding_size * 2 + 1).try_into().unwrap(); + + let vec = (0..side_length.pow(3)) + .map(|_| (0.into(), 0.into())) + .collect(); + + Self { + bounding_size: rule.bounding_size, + side_length, + data: vec, + } + } + + fn len(&self) -> usize { + self.side_length.pow(3) + } + + fn get(&self, pos: &IVec3) -> &(u8, AtomicU8) { + let index = self.to_index(pos); + &self[index] + } + + fn get_mut(&mut self, pos: &IVec3) -> &mut (u8, AtomicU8) { + let index = self.to_index(pos); + &mut self[index] + } + + fn to_index(&self, pos: &IVec3) -> usize { + let i: usize = (pos.x + self.bounding_size).try_into().unwrap(); + let j: usize = (pos.y + self.bounding_size).try_into().unwrap(); + let k: usize = (pos.z + self.bounding_size).try_into().unwrap(); + + i * self.side_length * self.side_length + j * self.side_length + k + } + + fn to_pos(&self, mut index: usize) -> IVec3 { + + let i = index / (self.side_length * self.side_length); + index %= self.side_length * self.side_length; + + let j = index / (self.side_length); + index %= self.side_length; + + let k = index; + + + ( + i as i32 - self.bounding_size, + j as i32 - self.bounding_size, + k as i32 - self.bounding_size, + ) + .into() + } + + fn spawn_noise( + &mut self, + // _task_pool: &TaskPool, // TODO: parallelize? + rule: &Rule + ) { + let timer = Instant::now(); + + // Different version of `utils::spawn_noise` + let mut rand = rand::thread_rng(); + let spawn_size = 6; + (0..12 * 12 * 12).for_each(|_i| { + let pos = ivec3( + rand.gen_range(-spawn_size..=spawn_size), + rand.gen_range(-spawn_size..=spawn_size), + rand.gen_range(-spawn_size..=spawn_size), + ); + let (value, _) = self.get_mut(&pos); + *value = rule.states; + }); + + info!( + "spawn_noise: {:.3} ms", + timer.elapsed().as_secs_f64() * 1000.0 + ); + } + + fn increment_neighbor(&self, pos: &IVec3) { + self.get(pos).1.fetch_add(1, Ordering::Relaxed); + } + + fn add_to_neighbours(&self, rule: &Rule, index: usize) { + let (ref value, ref _neighbours) = self[index]; + + if *value == rule.states { + let pos = self.to_pos(index); + for dir in rule.neighbour_method.get_neighbour_iter() { + let mut neighbour_pos = pos + *dir; + keep_in_bounds(rule.bounding_size, &mut neighbour_pos); + self.increment_neighbor(&neighbour_pos); + } + } + } + + fn calculate_neighbors(&self, task_pool: &TaskPool, rule: &Rule) { + + self.clear_neighbors(task_pool); + + let timer = Instant::now(); + + let indicies = (0..self.len()).collect::>(); + + indicies.par_chunk_map(task_pool, 1024, |indicies| { + for index in indicies { + self.add_to_neighbours(rule, *index); + } + }); + + info!( + "calculate_neighbors: {:.3} ms", + timer.elapsed().as_secs_f64() * 1000.0 + ); + } + + fn clear_neighbors(&self, task_pool: &TaskPool) { + let timer = Instant::now(); + + let slice = &self.data[..]; + + slice.par_chunk_map(task_pool, 2048, |states| { + for (_, ref neighbours) in states { + neighbours.store(0, Ordering::Relaxed); + } + }); + + info!( + "clear_neighbors: {:.3} ms", + timer.elapsed().as_secs_f64() * 1000.0 + ); + } + + fn update(rule: &Rule, value: &mut u8, neighbours: &u8) { + if *value != 0 { + // Decrement value if survival rule isn't passed + if !(*value == rule.states && rule.survival_rule.in_range(*neighbours)) { + *value -= 1; + } + } else { + // Check for birth + if rule.birth_rule.in_range(*neighbours) { + *value = rule.states; + } + } + } + + fn update_values(&mut self, task_pool: &TaskPool, rule: &Rule) { + + let timer = Instant::now(); + + let mut slice = &mut self.data[..]; + + slice.par_chunk_map_mut(task_pool, 1024, |states| { + for (ref mut value, ref mut neighbours) in states { + Self::update(rule, value, neighbours.get_mut()); + } + }); + + info!( + "update_values: {:.3} ms", + timer.elapsed().as_secs_f64() * 1000.0 + ); + } +} + +impl Index for Cells { + type Output = (u8, AtomicU8); + + fn index(&self, index: usize) -> &Self::Output { + &self.data[index] + } +} + +impl IndexMut for Cells { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.data[index] + } +} + +/// Spawn all the nessasary `Cell` entities and setup `Cells` +fn init_cells(rule: Res, mut commands: Commands) { + commands.insert_resource(Cells::zeros(&rule)) +} + +fn spawn_noise( + keyboard_input: Res>, + rule: Res, + mut cells: ResMut, + mut cell_event: EventWriter, +) { + if keyboard_input.just_pressed(KeyCode::P) { + + cells.spawn_noise(&rule); + + cell_event.send(UpdateEvent); + } +} + +fn tick_cells( + task_pool: Res, + keyboard_input: Res>, + rule: Res, + mut cells: ResMut, + mut cell_event: EventWriter, +) { + if !keyboard_input.pressed(KeyCode::E) { + return; + } + + // Calculate neighbor values in parallel + cells.calculate_neighbors(&task_pool, &rule); + + // Modifiy all cell values in parallel + cells.update_values(&task_pool, &rule); + + cell_event.send(UpdateEvent); +} + +fn prepare_cell_data( + rule: Res, + cells: Res, + mut query: Query<&mut InstanceMaterialData>, +) { + let timer = Instant::now(); + + // take the first + let mut instance_data = query.iter_mut().next().unwrap(); + instance_data.0.clear(); + + for index in 0..cells.len() { + let (value, ref neighbours) = cells[index]; + + if value != 0 { + let pos = cells.to_pos(index); + let neighbours = neighbours.load(Ordering::Relaxed); + + let dist_to_center = utils::dist_to_center(pos, &rule); + + instance_data.0.push(InstanceData { + position: vec3(pos.x as f32, pos.y as f32, pos.z as f32), + scale: 1.0, + color: rule + .color_method + .color(rule.states, value, neighbours, dist_to_center) + .as_rgba_f32(), + //color: Color::rgba(value as f32 / rule.states as f32, 0.0, 0.0, 1.0) + // .as_rgba_f32(), + }); + + } + } + + info!("prepare_cell_data: {:.3} ms", timer.elapsed().as_secs_f64() * 1000.0); +} + +/// The system stages to do in order +#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemLabel)] +pub enum Stages { + SpawnNoise, + TickCells, +} + +pub struct CellsMultithreadedV2Plugin; + +impl Plugin for CellsMultithreadedV2Plugin { + fn build(&self, app: &mut bevy::prelude::App) { + app + .add_startup_system(init_cells) + .add_system(spawn_noise.label(Stages::SpawnNoise)) + .add_system( + tick_cells + .label(Stages::TickCells) + .after(Stages::SpawnNoise), + ) + .add_system(prepare_cell_data.after(Stages::TickCells)); + } +} diff --git a/src/main.rs b/src/main.rs index 4f0439f..9b31086 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,19 @@ -use bevy::{prelude::*, render::view::NoFrustumCulling}; -use cell_event::CellStatesChangedEvent; -pub mod cell_event; mod cell_renderer; mod cells_multithreaded; +mod cells_multithreaded_v2; mod cells_single_threaded; mod neighbours; mod rotating_camera; mod rule; mod utils; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +use bevy::{prelude::*, render::view::NoFrustumCulling}; + use cell_renderer::*; -//use cells_multithreaded::*; +// use cells_multithreaded::*; +// use cells_multithreaded_v2::*; use cells_single_threaded::*; use neighbours::NeighbourMethod; use rotating_camera::{RotatingCamera, RotatingCameraPlugin}; @@ -34,7 +38,7 @@ impl CellState { fn main() { let rule = Rule { - bounding_size: 25, + bounding_size: 50, // builder survival_rule: Value::Singles(vec![2, 6, 9]), @@ -98,7 +102,7 @@ fn main() { //color_method: ColorMethod::StateLerp(Color::RED, Color::BLUE), //neighbour_method: NeighbourMethod::Moore, - // LARGE LINES + // LARGE LINES` //survival_rule: Value::Singles(vec![5]), //birth_rule: Value::Singles(vec![4, 6, 9, 10, 11, 16, 17, 18, 19, 20, 21, 22, 23, 24]), //states: 35, @@ -113,12 +117,12 @@ fn main() { .insert_resource(task_pool_settings) .add_plugins(DefaultPlugins) .insert_resource(ClearColor(Color::rgb(0.65f32, 0.9f32, 0.96f32))) - .add_event::() .add_plugin(RotatingCameraPlugin) .add_plugin(CellMaterialPlugin) .insert_resource(rule) // you can swap out the different implementations - //.add_plugin(CellsMultithreadedPlugin) + // .add_plugin(CellsMultithreadedV2Plugin) + // .add_plugin(CellsMultithreadedPlugin) .add_plugin(CellsSinglethreadedPlugin) .add_startup_system(setup) .run();