From fb56f03a744002df4e2d08bca9611c7ab2887978 Mon Sep 17 00:00:00 2001 From: Jarrod Doyle Date: Mon, 11 Mar 2024 12:57:02 +0000 Subject: [PATCH] Move chunk to separate module and minor refactor --- src/falling_sand/chunk.rs | 97 +++++++++++++++++++++++++++++++++++++ src/falling_sand/mod.rs | 63 +----------------------- src/falling_sand/rules.rs | 37 ++++++-------- src/falling_sand/systems.rs | 46 +----------------- 4 files changed, 116 insertions(+), 127 deletions(-) create mode 100644 src/falling_sand/chunk.rs diff --git a/src/falling_sand/chunk.rs b/src/falling_sand/chunk.rs new file mode 100644 index 0000000..689b648 --- /dev/null +++ b/src/falling_sand/chunk.rs @@ -0,0 +1,97 @@ +use bevy::ecs::{component::Component, system::Res}; +use ndarray::Array2; +use rand::Rng; + +use crate::util::DirtyRect; + +use super::{element::Element, rules::FallingSandRules}; + +#[derive(Component)] +pub struct Chunk { + step: usize, + pub width: usize, + pub height: usize, + cells: Array2, + pub dirty_rect: DirtyRect, +} + +impl Chunk { + pub fn new(width: usize, height: usize) -> Self { + let mut initial = Self { + step: 0, + width, + height, + cells: Array2::from_elem((width, height), Element::Air), + dirty_rect: DirtyRect::default(), + }; + + let max_y = height / rand::thread_rng().gen_range(2..10); + for y in 0..=max_y { + for x in 0..width { + initial.set_cell(x, y, Element::Water); + } + } + initial + } + + pub fn set_cell(&mut self, x: usize, y: usize, element: Element) { + if x >= self.width || y >= self.height { + return; + } + + self.cells[(x, y)] = element; + self.dirty_rect.add_point(x, y); + } + + pub fn swap_cells(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) { + if x0 >= self.width || y0 >= self.height || x1 >= self.width || y1 >= self.height { + return; + } + + self.cells.swap((x0, y0), (x1, y1)); + self.dirty_rect.add_point(x0, y0); + self.dirty_rect.add_point(x1, y1); + } + + pub fn get_cell(&self, x: usize, y: usize) -> Option { + if x >= self.width || y >= self.height { + return None; + } + + Some(self.cells[(x, y)]) + } + + pub fn update(&mut self, rules: &Res) { + // We operate on 2x2 blocks of cells. Each update we offset the blocks using a + // modified Margolus neighbourhood. + let offsets = [(0, 1), (1, 1), (0, 0), (1, 0)]; + let block_offset = [(0, 0), (1, 1), (0, 1), (1, 0)][self.step]; + self.step = (self.step + 1) % 4; + + // Faster to reuse rather than keep remaking it within the loops + let mut in_elements: [Element; 4] = [Element::None; 4]; + for block_y in 0..(self.height / 2) { + let y = block_y * 2 + block_offset.1; + for block_x in 0..(self.width / 2) { + let x = block_x * 2 + block_offset.0; + + // Get all the cells in our block and convert them to a rule state for lookup + // Because our offset can cause cell look-ups to go ourside of the grid we have + // a default `Element::None` + for i in 0..offsets.len() { + let o = offsets[i]; + in_elements[i] = self.get_cell(x + o.0, y + o.1).unwrap_or(Element::None); + } + let out_elements = rules.get_result(&in_elements); + + // We only need to actually update things if the state changed + for i in 0..offsets.len() { + let o = offsets[i]; + if in_elements[i] != out_elements[i] { + self.set_cell(x + o.0, y + o.1, out_elements[i]); + } + } + } + } + } +} diff --git a/src/falling_sand/mod.rs b/src/falling_sand/mod.rs index 1cd2369..0d8317e 100644 --- a/src/falling_sand/mod.rs +++ b/src/falling_sand/mod.rs @@ -1,3 +1,4 @@ +mod chunk; mod element; pub mod rules; mod systems; @@ -15,12 +16,8 @@ use bevy::{ sprite::SpriteBundle, transform::components::Transform, }; -use ndarray::Array2; -use rand::Rng; -use crate::util::DirtyRect; - -use self::{element::Element, rules::FallingSandRules}; +use self::{chunk::Chunk, element::Element, rules::FallingSandRules}; pub struct FallingSandPlugin; @@ -70,59 +67,3 @@ fn setup(mut commands: Commands, mut images: ResMut>) { commands.spawn(Chunk::new(256, 256)).insert(sprite_bundle1); commands.spawn(Chunk::new(256, 256)).insert(sprite_bundle2); } - -#[derive(Component)] -pub struct Chunk { - step: usize, - width: usize, - height: usize, - cells: Array2, - dirty_rect: DirtyRect, -} - -impl Chunk { - pub fn new(width: usize, height: usize) -> Self { - let mut initial = Self { - step: 0, - width, - height, - cells: Array2::from_elem((width, height), Element::Air), - dirty_rect: DirtyRect::default(), - }; - - let max_y = height / rand::thread_rng().gen_range(2..10); - for y in 0..=max_y { - for x in 0..width { - initial.set_cell(x, y, Element::Water); - } - } - initial - } - - pub fn set_cell(&mut self, x: usize, y: usize, element: Element) { - if x >= self.width || y >= self.height { - return; - } - - self.cells[(x, y)] = element; - self.dirty_rect.add_point(x, y); - } - - pub fn swap_cells(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) { - if x0 >= self.width || y0 >= self.height || x1 >= self.width || y1 >= self.height { - return; - } - - self.cells.swap((x0, y0), (x1, y1)); - self.dirty_rect.add_point(x0, y0); - self.dirty_rect.add_point(x1, y1); - } - - pub fn get_cell(&self, x: usize, y: usize) -> Option { - if x >= self.width || y >= self.height { - return None; - } - - Some(self.cells[(x, y)]) - } -} diff --git a/src/falling_sand/rules.rs b/src/falling_sand/rules.rs index 19e838f..24dee2f 100644 --- a/src/falling_sand/rules.rs +++ b/src/falling_sand/rules.rs @@ -12,9 +12,9 @@ pub struct RuleBuilder { } impl RuleBuilder { - pub fn build(input: (Element, Element, Element, Element)) -> u32 { + pub fn build(input: &[Element; 4]) -> u32 { let mut block = RuleBuilder { - elements: [input.0, input.1, input.2, input.3], + elements: *input, processed: [false; 4], position: 0, }; @@ -33,12 +33,7 @@ impl RuleBuilder { } } - to_rule_state(( - block.elements[0], - block.elements[1], - block.elements[2], - block.elements[3], - )) + to_rule_state(&block.elements) } pub fn get(&self, x: i8, y: i8) -> Element { @@ -85,9 +80,9 @@ impl Default for FallingSandRules { for b in 0..elements.len() { for c in 0..elements.len() { for d in 0..elements.len() { - let input = (elements[a], elements[b], elements[c], elements[d]); - let in_rule = to_rule_state(input); - let out_rule = RuleBuilder::build(input); + let input = [elements[a], elements[b], elements[c], elements[d]]; + let in_rule = to_rule_state(&input); + let out_rule = RuleBuilder::build(&input); if in_rule != out_rule { rules.insert(in_rule, out_rule); } @@ -101,11 +96,8 @@ impl Default for FallingSandRules { } impl FallingSandRules { - pub fn get_result( - &self, - input: (Element, Element, Element, Element), - ) -> (Element, Element, Element, Element) { - let input_rule = to_rule_state(input); + pub fn get_result(&self, input: &[Element; 4]) -> [Element; 4] { + let input_rule = to_rule_state(&input); let output_rule = match self.rules.get(&input_rule) { Some(&result) => result, None => input_rule, @@ -114,15 +106,18 @@ impl FallingSandRules { } } -fn to_rule_state(input: (Element, Element, Element, Element)) -> u32 { - ((input.0 as u32) << 24) + ((input.1 as u32) << 16) + ((input.2 as u32) << 8) + input.3 as u32 +fn to_rule_state(input: &[Element; 4]) -> u32 { + ((input[0] as u32) << 24) + + ((input[1] as u32) << 16) + + ((input[2] as u32) << 8) + + input[3] as u32 } -fn from_rule_state(input: u32) -> (Element, Element, Element, Element) { - ( +fn from_rule_state(input: u32) -> [Element; 4] { + [ Element::from((input >> 24) & 0xFF), Element::from((input >> 16) & 0xFF), Element::from((input >> 8) & 0xFF), Element::from(input & 0xFF), - ) + ] } diff --git a/src/falling_sand/systems.rs b/src/falling_sand/systems.rs index 9e3f419..6b1c0ea 100644 --- a/src/falling_sand/systems.rs +++ b/src/falling_sand/systems.rs @@ -18,51 +18,7 @@ pub fn place_sand(mut query: Query<&mut Chunk>) { pub fn simulate_chunk(rules: Res, mut query: Query<&mut Chunk>) { for mut chunk in &mut query { - // Determine which Margolus neighbourhood offset we're using this update - let offset = if chunk.step == 0 { - (0, 0) - } else if chunk.step == 1 { - (1, 1) - } else if chunk.step == 2 { - (0, 1) - } else { - (1, 0) - }; - chunk.step = (chunk.step + 1) % 4; - - // We're operating on 2x2 blocks of cells - for block_y in 0..(chunk.height / 2) { - let y = block_y * 2 + offset.1; - for block_x in 0..(chunk.width / 2) { - let x = block_x * 2 + offset.0; - - // Get all the cells in our block and convert them to a rule state for lookup - // Because our offset can cause cell look-ups to go ourside of the grid we have - // a default `Element::None` - // Cells blocks are in the order top-left, top-right, bottom-left, bottom-right - let in_elements = ( - chunk.get_cell(x, y + 1).unwrap_or(Element::None), - chunk.get_cell(x + 1, y + 1).unwrap_or(Element::None), - chunk.get_cell(x, y).unwrap_or(Element::None), - chunk.get_cell(x + 1, y).unwrap_or(Element::None), - ); - let out_elements = rules.get_result(in_elements); - - // We only need to actually update things if the state changed - if in_elements.0 != out_elements.0 { - chunk.set_cell(x, y + 1, out_elements.0); - } - if in_elements.1 != out_elements.1 { - chunk.set_cell(x + 1, y + 1, out_elements.1); - } - if in_elements.2 != out_elements.2 { - chunk.set_cell(x, y, out_elements.2); - } - if in_elements.3 != out_elements.3 { - chunk.set_cell(x + 1, y, out_elements.3); - } - } - } + chunk.update(&rules); } }