Compare commits

..

10 Commits

10 changed files with 687 additions and 296 deletions

327
Cargo.lock generated
View File

@ -175,6 +175,18 @@ dependencies = [
"libc",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstyle"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
[[package]]
name = "approx"
version = "0.5.1"
@ -1211,6 +1223,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.83"
@ -1248,6 +1266,33 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clang-sys"
version = "1.7.0"
@ -1259,6 +1304,31 @@ dependencies = [
"libloading 0.8.1",
]
[[package]]
name = "clap"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -1431,6 +1501,42 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.11"
@ -1440,12 +1546,37 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "d3d12"
version = "0.7.0"
@ -1481,6 +1612,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "encase"
version = "0.6.1"
@ -1528,6 +1665,16 @@ dependencies = [
"serde",
]
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "euclid"
version = "0.22.9"
@ -1888,12 +2035,25 @@ dependencies = [
"svg_fmt",
]
[[package]]
name = "half"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "haranae-rs"
version = "0.1.0"
dependencies = [
"bevy",
"criterion",
"ndarray",
"rand",
"rayon",
]
[[package]]
@ -1928,6 +2088,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "hermit-abi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
[[package]]
name = "hexasphere"
version = "9.1.0"
@ -2025,6 +2191,26 @@ dependencies = [
"mach2",
]
[[package]]
name = "is-terminal"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.10"
@ -2173,6 +2359,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.11"
@ -2216,6 +2408,16 @@ dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matrixmultiply"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "memchr"
version = "2.7.1"
@ -2306,6 +2508,20 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "ndarray"
version = "0.15.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
dependencies = [
"matrixmultiply",
"num-complex",
"num-integer",
"num-traits",
"rawpointer",
"rayon",
]
[[package]]
name = "ndk"
version = "0.7.0"
@ -2392,6 +2608,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-complex"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [
"num-traits",
]
[[package]]
name = "num-derive"
version = "0.3.3"
@ -2403,6 +2628,16 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
@ -2546,6 +2781,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "orbclient"
version = "0.3.47"
@ -2650,6 +2891,34 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
[[package]]
name = "plotters"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
[[package]]
name = "png"
version = "0.17.11"
@ -2760,6 +3029,32 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "rectangle-pack"
version = "0.4.2"
@ -2874,6 +3169,19 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.2",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "ruzstd"
version = "0.4.0"
@ -3123,6 +3431,16 @@ dependencies = [
"once_cell",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -3644,6 +3962,15 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"

View File

@ -7,7 +7,14 @@ edition = "2021"
[dependencies]
bevy = "0.12.1"
criterion = "0.5.1"
ndarray = { version = "0.15.6", features = ["rayon"] }
rand = "0.8.5"
rayon = "1.8.1"
[profile.dev.package."*"]
opt-level = 3
[[bench]]
name = "rule_generation"
harness = false

View File

@ -0,0 +1,11 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use haranae_rs::falling_sand::rules::FallingSandRules;
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("Rule Generation", |b| {
b.iter(|| black_box(FallingSandRules::default()))
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

104
src/falling_sand/chunk.rs Normal file
View File

@ -0,0 +1,104 @@
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<Element>,
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 + 2, height + 2), Element::None),
dirty_rect: DirtyRect::default(),
};
// Set Main area to air
for y in 0..height {
for x in 0..width {
initial.set_cell(x, y, Element::Air);
}
}
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;
}
let x = x + 1;
let y = y + 1;
self.cells[(x, y)] = element;
self.dirty_rect.add_point(x - 1, y - 1);
}
pub fn get_cell(&self, x: usize, y: usize) -> Option<Element> {
if x >= self.width || y >= self.height {
return None;
}
let x = x + 1;
let y = y + 1;
Some(self.cells[(x, y)])
}
pub fn update(&mut self, rules: &Res<FallingSandRules>) {
// 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
// Also we directly access the cells here rather than using the helper function
// because we know the values will never be out of bounds
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 + 1;
for block_x in 0..(self.width / 2) {
let x = block_x * 2 + block_offset.0 + 1;
// 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.cells[(x + o.0, y + o.1)];
}
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] {
let pos = (x + o.0, y + o.1);
self.cells[pos] = out_elements[i];
self.dirty_rect.add_point(pos.0 - 1, pos.1 - 1);
}
}
}
}
}
}

View File

@ -0,0 +1,67 @@
use super::rules::RuleBuilder;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Element {
None,
Air,
Sand,
Water,
ElementCount,
}
impl From<u32> for Element {
fn from(value: u32) -> Self {
match value {
x if x == Element::Air as u32 => Element::Air,
x if x == Element::Sand as u32 => Element::Sand,
x if x == Element::Water as u32 => Element::Water,
_ => Element::None,
}
}
}
pub fn update_sand(block: &mut RuleBuilder, me: Element) {
let swap_elements = [Element::Air, Element::Water];
let bottom = block.get(0, -1);
let bottom_left = block.get(-1, -1);
let bottom_right = block.get(1, -1);
if swap_elements.contains(&bottom) {
block.set(0, 0, bottom);
block.set(0, -1, me);
} else if swap_elements.contains(&bottom_left) {
block.set(0, 0, bottom_left);
block.set(-1, -1, me);
} else if swap_elements.contains(&bottom_right) {
block.set(0, 0, bottom_right);
block.set(1, -1, me);
}
}
pub fn update_water(block: &mut RuleBuilder, me: Element) {
let swap_elements = [Element::Air];
let bottom = block.get(0, -1);
let bottom_left = block.get(-1, -1);
let bottom_right = block.get(1, -1);
let left = block.get(-1, 0);
let right = block.get(1, 0);
if swap_elements.contains(&bottom) {
block.set(0, 0, bottom);
block.set(0, -1, me);
} else if swap_elements.contains(&bottom_left) {
block.set(0, 0, bottom_left);
block.set(-1, -1, me);
} else if swap_elements.contains(&bottom_right) {
block.set(0, 0, bottom_right);
block.set(1, -1, me);
} else if swap_elements.contains(&left) {
block.set(0, 0, left);
block.set(-1, 0, me);
} else if swap_elements.contains(&right) {
block.set(0, 0, right);
block.set(1, 0, me);
}
}

View File

@ -1,4 +1,7 @@
mod rules;
mod chunk;
mod element;
pub mod rules;
mod systems;
use bevy::{
app::{Plugin, Startup},
@ -13,11 +16,8 @@ use bevy::{
sprite::SpriteBundle,
transform::components::Transform,
};
use rand::Rng;
use crate::util::DirtyRect;
use self::rules::{to_rule_state, FallingSandRules};
use self::{chunk::Chunk, element::Element, rules::FallingSandRules};
pub struct FallingSandPlugin;
@ -27,9 +27,9 @@ impl Plugin for FallingSandPlugin {
app.add_systems(
Update,
(
place_sand_system,
simulate_chunk_system,
update_chunk_texture_system,
systems::place_sand,
systems::simulate_chunk,
systems::update_chunk_texture,
)
.chain(),
);
@ -49,190 +49,21 @@ fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
TextureFormat::Rgba8UnormSrgb,
);
let image_handle = images.add(image);
commands.spawn(Chunk::new(256, 256)).insert(SpriteBundle {
let sprite_bundle1 = SpriteBundle {
sprite: Sprite {
flip_y: true,
..default()
},
texture: image_handle,
texture: images.add(image.clone()),
transform: Transform::from_translation(Vec3::new(256., 0., 0.))
.with_scale(Vec3::new(2., 2., 0.)),
..default()
});
}
#[derive(Component)]
pub struct Chunk {
step: usize,
width: usize,
height: usize,
cells: Vec<Element>,
dirty_rect: DirtyRect,
}
impl Chunk {
pub fn new(width: usize, height: usize) -> Self {
let mut initial = Self {
step: 0,
width,
height,
cells: vec![Element::Air; width * height],
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 * self.width] = 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;
}
let i0 = x0 + y0 * self.width;
let i1 = x1 + y1 * self.width;
self.cells.swap(i0, i1);
self.dirty_rect.add_point(x0, y0);
self.dirty_rect.add_point(x1, y1);
}
pub fn get_cell(&self, x: usize, y: usize) -> Option<Element> {
if x >= self.width || y >= self.height {
return None;
}
Some(self.cells[x + y * self.width])
}
}
pub fn place_sand_system(mut query: Query<&mut Chunk>) {
for mut chunk in &mut query {
let frac = chunk.width / 2;
let x = (chunk.width - frac) / 2 + rand::thread_rng().gen_range(0..frac);
let y = chunk.height - 1;
chunk.set_cell(x, y, Element::Sand);
}
}
pub fn simulate_chunk_system(rules: Res<FallingSandRules>, 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 are obtained in the order top-left, top-right, bottom-left, bottom-right
let start_state = to_rule_state((
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 end_state = rules.get_result(start_state);
// We only need to actually update things if the state changed
// Same ordering as above.
if start_state != end_state {
if (start_state & 0xFF000000) != (end_state & 0xFF000000) {
chunk.set_cell(x, y + 1, Element::from((end_state >> 24) & 0xFF));
}
if (start_state & 0x00FF0000) != (end_state & 0x00FF0000) {
chunk.set_cell(x + 1, y + 1, Element::from((end_state >> 16) & 0xFF));
}
if (start_state & 0x0000FF00) != (end_state & 0x0000FF00) {
chunk.set_cell(x, y, Element::from((end_state >> 8) & 0xFF));
}
if (start_state & 0x000000FF) != (end_state & 0x000000FF) {
chunk.set_cell(x + 1, y, Element::from(end_state & 0xFF));
}
}
}
}
}
}
pub fn update_chunk_texture_system(
mut images: ResMut<Assets<Image>>,
mut query: Query<(&mut Chunk, &Handle<Image>)>,
) {
for (mut chunk, image_handle) in &mut query {
if !chunk.dirty_rect.is_dirty() {
return;
}
if let Some(image) = images.get_mut(image_handle) {
for y in chunk.dirty_rect.range_y() {
for x in chunk.dirty_rect.range_x() {
let mut colour = (0, 0, 0);
if let Some(element) = chunk.cells.get(x + y * chunk.width) {
match element {
Element::Air => colour = (25, 24, 26),
Element::Sand => colour = (255, 216, 102),
Element::Water => colour = (120, 220, 232),
_ => {}
}
}
let index = (x + y * chunk.width) * 4;
image.data[index] = colour.0;
image.data[index + 1] = colour.1;
image.data[index + 2] = colour.2;
image.data[index + 3] = 255;
}
}
}
chunk.dirty_rect.reset();
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Element {
None,
Air,
Sand,
Water,
}
impl From<u32> for Element {
fn from(value: u32) -> Self {
match value {
x if x == Element::Air as u32 => Element::Air,
x if x == Element::Sand as u32 => Element::Sand,
x if x == Element::Water as u32 => Element::Water,
_ => Element::None,
}
}
};
let mut sprite_bundle2 = sprite_bundle1.clone();
sprite_bundle2.texture = images.add(image.clone());
sprite_bundle2.transform =
Transform::from_translation(Vec3::new(-256., 0., 0.)).with_scale(Vec3::new(2., 2., 0.));
commands.spawn(Chunk::new(256, 256)).insert(sprite_bundle1);
commands.spawn(Chunk::new(256, 256)).insert(sprite_bundle2);
}

View File

@ -1,35 +1,60 @@
use bevy::{ecs::system::Resource, utils::HashMap};
use super::Element;
use super::{
element::{update_sand, update_water},
Element,
};
struct RuleConfig {
element_groups: Vec<Vec<Element>>,
rules: Vec<([usize; 4], [usize; 4])>,
pub struct RuleBuilder {
elements: [Element; 4],
processed: [bool; 4],
position: i8,
}
impl RuleConfig {
fn compile(&self) -> HashMap<u32, u32> {
let mut rules = HashMap::new();
for i in 0..self.rules.len() {
let rule = self.rules[i];
impl RuleBuilder {
pub fn build(input: &[Element; 4]) -> u32 {
let mut block = RuleBuilder {
elements: *input,
processed: [false; 4],
position: 0,
};
let i0 = &self.element_groups[rule.0[0]];
let i1 = &self.element_groups[rule.0[1]];
let i2 = &self.element_groups[rule.0[2]];
let i3 = &self.element_groups[rule.0[3]];
for i in 0..4 {
if block.processed[i] {
continue;
}
let o0 = &self.element_groups[rule.1[0]];
let o1 = &self.element_groups[rule.1[1]];
let o2 = &self.element_groups[rule.1[2]];
let o3 = &self.element_groups[rule.1[3]];
// !HACK: Assume element groups are length 1 for now!
let input = to_rule_state((i0[0], i1[0], i2[0], i3[0]));
let output = to_rule_state((o0[0], o1[0], o2[0], o3[0]));
rules.insert(input, output);
block.position = i as i8;
let element = block.elements[i];
match element {
Element::Sand => update_sand(&mut block, element),
Element::Water => update_water(&mut block, element),
_ => {}
}
}
rules
to_rule_state(&block.elements)
}
pub fn get(&self, x: i8, y: i8) -> Element {
let x = x + (self.position % 2);
let y = (self.position / 2) - y;
if !(0..2).contains(&x) || !(0..2).contains(&y) {
return Element::None;
}
let idx = (x + y * 2) as usize;
self.elements[idx]
}
pub fn set(&mut self, x: i8, y: i8, element: Element) {
let x = x + (self.position % 2);
let y = (self.position / 2) - y;
if (0..2).contains(&x) && (0..2).contains(&y) {
let idx = (x + y * 2) as usize;
self.elements[idx] = element;
self.processed[idx] = true;
}
}
}
@ -40,98 +65,59 @@ pub struct FallingSandRules {
impl Default for FallingSandRules {
fn default() -> Self {
// Pre-computed rules
// I should really start building a tool for this instead of manually calculating it huh
let rule_config = RuleConfig {
element_groups: vec![
vec![Element::Air],
vec![Element::Sand],
vec![Element::Water],
],
rules: vec![
// Air/Sand
([0, 1, 0, 0], [0, 0, 0, 1]),
([0, 1, 0, 1], [0, 0, 1, 1]),
([0, 1, 1, 0], [0, 0, 1, 1]),
([1, 0, 0, 0], [0, 0, 1, 0]),
([1, 0, 0, 1], [0, 0, 1, 1]),
([1, 0, 1, 0], [0, 0, 1, 1]),
([1, 1, 0, 0], [0, 0, 1, 1]),
([1, 1, 0, 1], [0, 1, 1, 1]),
([1, 1, 1, 0], [1, 0, 1, 1]),
// Air/Water
([0, 2, 0, 0], [0, 0, 0, 2]),
([0, 2, 0, 2], [0, 0, 2, 2]),
([0, 2, 2, 0], [0, 0, 2, 2]),
([2, 0, 0, 0], [0, 0, 2, 0]),
([2, 0, 0, 2], [0, 0, 2, 2]),
([2, 0, 2, 0], [0, 0, 2, 2]),
([2, 2, 0, 0], [0, 0, 2, 2]),
([2, 2, 0, 2], [0, 2, 2, 2]),
([2, 2, 2, 0], [2, 0, 2, 2]),
([0, 2, 2, 2], [2, 0, 2, 2]),
([2, 0, 2, 2], [0, 2, 2, 2]),
// Air/Sand/Water
([0, 1, 0, 2], [0, 0, 2, 1]),
([0, 1, 1, 2], [0, 2, 1, 1]),
([0, 1, 2, 0], [0, 0, 2, 1]),
([0, 1, 2, 1], [2, 0, 1, 1]),
([0, 1, 2, 2], [0, 2, 2, 1]),
([0, 2, 0, 1], [0, 0, 2, 1]),
([0, 2, 1, 0], [0, 0, 1, 2]),
([0, 2, 1, 1], [2, 0, 1, 1]),
([0, 2, 1, 2], [2, 0, 1, 2]),
([0, 2, 2, 1], [2, 0, 2, 1]),
([1, 0, 0, 2], [0, 0, 1, 2]),
([1, 0, 1, 2], [0, 2, 1, 1]),
([1, 0, 2, 0], [0, 0, 1, 2]),
([1, 0, 2, 1], [2, 0, 1, 1]),
([1, 0, 2, 2], [2, 0, 1, 2]),
([1, 1, 0, 2], [0, 2, 1, 1]),
([1, 1, 1, 2], [1, 2, 1, 1]),
([1, 1, 2, 0], [2, 0, 1, 1]),
([1, 1, 2, 1], [2, 1, 1, 1]),
([1, 1, 2, 2], [2, 2, 1, 1]),
([1, 2, 0, 0], [0, 0, 1, 2]),
([1, 2, 0, 1], [0, 2, 1, 1]),
([1, 2, 0, 2], [0, 2, 1, 2]),
([1, 2, 1, 0], [0, 2, 1, 1]),
([1, 2, 1, 2], [2, 2, 1, 1]),
([1, 2, 2, 0], [0, 2, 1, 2]),
([1, 2, 2, 1], [2, 2, 1, 1]),
([1, 2, 2, 2], [2, 2, 1, 2]),
([2, 0, 0, 1], [0, 0, 2, 1]),
([2, 0, 1, 0], [0, 0, 1, 2]),
([2, 0, 1, 1], [0, 2, 1, 1]),
([2, 0, 1, 2], [0, 2, 1, 2]),
([2, 0, 2, 1], [0, 2, 2, 1]),
([2, 1, 0, 0], [0, 0, 2, 1]),
([2, 1, 0, 1], [2, 0, 1, 1]),
([2, 1, 0, 2], [2, 0, 2, 1]),
([2, 1, 1, 0], [2, 0, 1, 1]),
([2, 1, 1, 2], [2, 2, 1, 1]),
([2, 1, 2, 0], [2, 0, 2, 1]),
([2, 1, 2, 1], [2, 2, 1, 1]),
([2, 1, 2, 2], [2, 2, 2, 1]),
([2, 2, 0, 1], [0, 2, 2, 1]),
([2, 2, 1, 0], [2, 0, 1, 2]),
],
};
let rules = rule_config.compile();
// Build a list of elements
// We do it this way so it automatically handles adding new elements
let mut elements = vec![];
for i in 0..(Element::ElementCount as u32) {
elements.push(Element::from(i));
}
// Attempt to compute a rule for every possible element block permutation
// Only bother keeping the rule if the state actually changes
// TODO: See if there's a better way to build the permutations than nesting loops
let mut rules = HashMap::new();
for a in 0..elements.len() {
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);
if in_rule != out_rule {
rules.insert(in_rule, out_rule);
}
}
}
}
}
Self { rules }
}
}
impl FallingSandRules {
pub fn get_result(&self, input_rule: u32) -> u32 {
match self.rules.get(&input_rule) {
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,
}
};
from_rule_state(output_rule)
}
}
pub 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; 4] {
[
Element::from((input >> 24) & 0xFF),
Element::from((input >> 16) & 0xFF),
Element::from((input >> 8) & 0xFF),
Element::from(input & 0xFF),
]
}

View File

@ -0,0 +1,58 @@
use bevy::{
asset::{Assets, Handle},
ecs::system::{Query, Res, ResMut},
render::texture::Image,
};
use rand::Rng;
use super::{element::Element, rules::FallingSandRules, Chunk};
pub fn place_sand(mut query: Query<&mut Chunk>) {
for mut chunk in &mut query {
let frac = chunk.width / 2;
let x = (chunk.width - frac) / 2 + rand::thread_rng().gen_range(0..frac);
let y = chunk.height - 1;
chunk.set_cell(x, y, Element::Sand);
}
}
pub fn simulate_chunk(rules: Res<FallingSandRules>, mut query: Query<&mut Chunk>) {
for mut chunk in &mut query {
chunk.update(&rules);
}
}
pub fn update_chunk_texture(
mut images: ResMut<Assets<Image>>,
mut query: Query<(&mut Chunk, &Handle<Image>)>,
) {
for (mut chunk, image_handle) in &mut query {
if !chunk.dirty_rect.is_dirty() {
return;
}
if let Some(image) = images.get_mut(image_handle) {
for y in chunk.dirty_rect.range_y() {
for x in chunk.dirty_rect.range_x() {
let mut colour = (0, 0, 0);
if let Some(element) = chunk.get_cell(x, y) {
match element {
Element::Air => colour = (25, 24, 26),
Element::Sand => colour = (255, 216, 102),
Element::Water => colour = (120, 220, 232),
_ => {}
}
}
let index = (x + y * chunk.width) * 4;
image.data[index] = colour.0;
image.data[index + 1] = colour.1;
image.data[index + 2] = colour.2;
image.data[index + 3] = 255;
}
}
}
chunk.dirty_rect.reset();
}
}

2
src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod falling_sand;
pub mod util;

View File

@ -1,11 +1,9 @@
mod falling_sand;
mod util;
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
window::PresentMode,
};
use haranae_rs::falling_sand;
fn main() {
let window_plugin = WindowPlugin {