From e2ccd0ceaddb47330134a1d5db8f7a79aea9d2a1 Mon Sep 17 00:00:00 2001 From: popertots Date: Sun, 11 May 2025 22:58:25 +0100 Subject: [PATCH] Log (wip), parallelisation --- Cargo.lock | 127 ++++++++++++++++----- Cargo.toml | 1 + assets/log.png | Bin 0 -> 283 bytes src/camera.rs | 14 ++- src/tile.rs | 4 +- src/tilemap.rs | 294 +++++++++++++++++++++++++++++++++++++------------ src/tiles.rs | 179 +++++++++++++++++++++--------- 7 files changed, 463 insertions(+), 156 deletions(-) create mode 100644 assets/log.png diff --git a/Cargo.lock b/Cargo.lock index cec7581..2129af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1532,9 +1532,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.21" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "jobserver", "libc", @@ -1850,11 +1850,11 @@ checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "ctrlc" -version = "3.4.6" +version = "3.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ - "nix", + "nix 0.30.1", "windows-sys 0.59.0", ] @@ -1936,6 +1936,7 @@ dependencies = [ "image", "noise", "rand 0.9.1", + "rayon", ] [[package]] @@ -2104,9 +2105,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "font-types" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf" +checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7" dependencies = [ "bytemuck", ] @@ -2220,9 +2221,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -2266,7 +2267,7 @@ dependencies = [ "libc", "libudev-sys", "log", - "nix", + "nix 0.29.0", "uuid", "vec_map", "wasm-bindgen", @@ -2664,7 +2665,7 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] @@ -2751,12 +2752,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] @@ -3020,6 +3021,18 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "noise" version = "0.9.0" @@ -3739,7 +3752,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -3851,9 +3864,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.25.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f" +checksum = "5ce8e2ca6b24313587a03ca61bb74c384e2a815bd90cf2866cfc9f5fb7a11fa0" dependencies = [ "bytemuck", "font-types", @@ -4124,9 +4137,9 @@ dependencies = [ [[package]] name = "skrifa" -version = "0.26.6" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d" +checksum = "bbe6666ab11018ab91ff7b03f1a3b9fdbecfb610848436fefa5ce50343d3d913" dependencies = [ "bytemuck", "read-fonts", @@ -4231,9 +4244,9 @@ checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] name = "swash" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fae9a562c7b46107d9c78cd78b75bbe1e991c16734c0aee8ff0ee711fb8b620a" +checksum = "5dce3f0af95643c855cdc449fbaa17d8c2cd08e0b00a49a6babcbe6e71667f3d" dependencies = [ "skrifa", "yazi", @@ -4600,7 +4613,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", @@ -5219,13 +5232,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -5244,6 +5273,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -5262,6 +5297,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -5280,12 +5321,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -5304,6 +5357,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -5322,6 +5381,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -5340,6 +5405,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -5358,6 +5429,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winit" version = "0.30.10" @@ -5485,9 +5562,9 @@ checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" [[package]] name = "zeno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0de2315dc13d00e5df3cd6b8d2124a6eaec6a2d4b6a1c5f37b7efad17fcc17" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" diff --git a/Cargo.toml b/Cargo.toml index 28f78d1..1316378 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ noise = "0.9.0" rand = "0.9.1" image = "0.25.6" bevy_platform = "0.16.0" +rayon = "1.10.0" # Enable max optimizations for dependencies, but not for our code: [profile.dev.package."*"] diff --git a/assets/log.png b/assets/log.png new file mode 100644 index 0000000000000000000000000000000000000000..f11a1b8a084d8e8e79a5ed5fcda46cb267dc4722 GIT binary patch literal 283 zcmV+$0p$LPP)Px#)k#D_R5*=|ld%zlFbqXMIS)Xa4v)Yc4J9o*WRz4MB|D@|Nt3&b09{%(a2n+M zk_|)^7{4dU78Q14?Aplh&-bS)?Q?rzxLxh1SQ070*tJnWKlF+Yn39Mk&cXt&%d7;l zzKN2=?ZI{9l|a_~&@0KsHp=k2%&C&P@mW;>;5fU5?XyU@imU<#&awkQQ6O;y9tuj? z?V(Dz?K)z>q|U)gxHSN{KfWm5f(1+P{7OTGG*M@R#IR%sXcD;b| hK-!bHVB_E4`3HeQob?wFD?002ovPDHLkV1l3Rb*%sZ literal 0 HcmV?d00001 diff --git a/src/camera.rs b/src/camera.rs index 041d31e..a739306 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -22,13 +22,17 @@ pub fn camera_z_movement( camera_moved.0 = false; if keyboard_input.just_pressed(KeyCode::ShiftLeft) { z_index.0 -= 1.0; - z_index.0 = z_index.0.clamp(-tilemap::Z_BELOW + 1.0, tilemap::Z_ABOVE); + z_index.0 = z_index + .0 + .clamp(-tilemap::Z_BELOW + 1.0, tilemap::Z_ABOVE - 1.); camera_moved.0 = true; println!("z_index = {}", z_index.0); } if keyboard_input.just_pressed(KeyCode::ShiftRight) { z_index.0 += 1.0; - z_index.0 = z_index.0.clamp(-tilemap::Z_BELOW + 1.0, tilemap::Z_ABOVE); + z_index.0 = z_index + .0 + .clamp(-tilemap::Z_BELOW + 1.0, tilemap::Z_ABOVE - 1.); camera_moved.0 = true; println!("z_index = {}", z_index.0); } @@ -77,9 +81,9 @@ pub fn spawn_panning_camera(mut commands: Commands) { } pub fn scroll_events(mut evr_scroll: EventReader, mut query: Query<&mut Projection>) { - const ZOOM_SENSITIVITY: f32 = 0.05; - const MIN_SCALE: f32 = 0.4; - const MAX_SCALE: f32 = 1.5; + const ZOOM_SENSITIVITY: f32 = 0.035; + const MIN_SCALE: f32 = 0.2; + const MAX_SCALE: f32 = 2.0; for ev in evr_scroll.read() { for mut projection_component in query.iter_mut() { diff --git a/src/tile.rs b/src/tile.rs index 7aeacd6..29de2af 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -49,8 +49,8 @@ impl Default for FixtureTile { #[derive(Resource, Default, Clone)] pub struct TileMap { - pub floor_tiles: HashMap, // id, opaque, walkable, astar_weight, visible_range - pub fixture_tiles: HashMap, + pub floor_tiles: HashMap, //id, opaque, walkable, astar_weight, visible_range + pub fixture_tiles: HashMap, // id, solid, visible_range } pub fn update_tile_visibility( diff --git a/src/tilemap.rs b/src/tilemap.rs index 391a9f3..38835f3 100644 --- a/src/tilemap.rs +++ b/src/tilemap.rs @@ -1,3 +1,4 @@ +use std::sync::Mutex; use std::time::Instant; use crate::constants::{ITILE_SIZE, TILE_SIZE}; @@ -12,7 +13,7 @@ use noise::{NoiseFn, Perlin}; pub const CHUNK_SIZE: i32 = 8; pub const Z_BELOW: f32 = 20.0; -pub const Z_ABOVE: f32 = 10.0; +pub const Z_ABOVE: f32 = 4.0; pub const Z_TOTAL: f32 = Z_ABOVE + Z_BELOW; // MAX 255 DO NOT EXCEED #[derive(Resource)] @@ -29,7 +30,29 @@ impl Default for ChunkMap { } #[derive(Event)] -pub struct LoadChunkEvent { +pub struct GenerateChunkEvent { + pub chunk_position: IVec2, +} +#[derive(Event)] +pub struct ChunkTerrainEvent { + pub chunk_position: IVec2, +} +#[derive(Event)] +// CAlculate the weather effecrs and rivers/lakes of this chunk +pub struct ChunkWeatheringAndPrecipitationEvent { + pub chunk_position: IVec2, +} +#[derive(Event)] +pub struct ChunkForrestryEvent { + pub chunk_position: IVec2, + pub floor_tiles: Vec<(Vec3, String)>, +} +#[derive(Event)] +pub struct ChunkFoliageEvent { + pub chunk_position: IVec2, +} +#[derive(Event)] +pub struct ChunkFaunaEvent { pub chunk_position: IVec2, } @@ -142,7 +165,7 @@ pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] { visible_range } -pub fn generate_surface_noise(x: i32, y: i32) -> f32 { +pub fn generate_surface_terrain(x: i32, y: i32) -> f32 { let noise = Perlin::new(0); let mut noise_value = 0.0; let mut amplitude = 1.0; @@ -157,28 +180,67 @@ pub fn generate_surface_noise(x: i32, y: i32) -> f32 { } fn generate_chunks_from_algo( - mut commands: Commands, - mut events: EventReader, + mut chunk_events: EventReader, + mut terrain_event_writer: EventWriter, + mut weathering_event_writer: EventWriter, + mut foliage_event_writer: EventWriter, + mut fauna_event_writer: EventWriter, +) { + // Fire each terrain pass. They will all fire sequentially. + for event in chunk_events.read() { + let chunk_pos = event.chunk_position; + terrain_event_writer.write(ChunkTerrainEvent { + chunk_position: chunk_pos, + }); + weathering_event_writer.write(ChunkWeatheringAndPrecipitationEvent { + chunk_position: chunk_pos, + }); + foliage_event_writer.write(ChunkFoliageEvent { + chunk_position: chunk_pos, + }); + fauna_event_writer.write(ChunkFaunaEvent { + chunk_position: chunk_pos, + }); + } +} + +fn generate_chunk_terrain( + commands: ParallelCommands<'_, '_>, // Use ParallelCommands for parallel spawning + mut events: EventReader, mut chunk_map: ResMut, mut tilemap: ResMut, + mut forrestry_event_writer: EventWriter, ) { let is_empty = events.is_empty(); let start = Instant::now(); let count = events.len(); let cave_noise = Perlin::new(0); - for event in events.read() { + + // Create mutexes for our shared resources + let chunk_map_updates = Mutex::new(HashMap::new()); + let tilemap_updates = Mutex::new(HashMap::new()); + let forrestry_events = Mutex::new(Vec::new()); + + events.par_read().for_each(|event| { let chunk_pos = event.chunk_position; - if chunk_map.loaded_chunks.contains_key(&chunk_pos) { - continue; + + // Check if chunk is already loaded - using a local check first to avoid locking + { + let chunk_map_guard = chunk_map.loaded_chunks.get(&chunk_pos); + if let Some(true) = chunk_map_guard { + return; + } } - chunk_map.loaded_chunks.insert(chunk_pos, true); + // Mark chunk as loaded in our thread-safe collection + chunk_map_updates.lock().unwrap().insert(chunk_pos, true); let start_x = chunk_pos.x * CHUNK_SIZE; let start_y = chunk_pos.y * CHUNK_SIZE; - let mut floor_positions = Vec::new(); + let mut surface_positions: Vec<(Vec3, String)> = Vec::new(); + let mut local_tilemap_updates = HashMap::new(); // Generate tiles for this chunk for local_y in 0..CHUNK_SIZE { @@ -189,7 +251,7 @@ fn generate_chunks_from_algo( let noise_position = Vec3::new( (world_x as f32 * TILE_SIZE).round(), (world_y as f32 * TILE_SIZE).round(), - (generate_surface_noise(world_x, world_y) * TILE_SIZE).round(), + (generate_surface_terrain(world_x, world_y) * TILE_SIZE).round(), ); // Spawn tiles and add them to tilemap @@ -202,91 +264,168 @@ fn generate_chunks_from_algo( let pos_ivec = position.as_ivec3(); if z < -5 { - let noise_value = cave_noise.get([ + let cave_value = cave_noise.get([ world_x as f64 * 0.05, world_y as f64 * 0.05, z as f64 * 0.05, ]); - if noise_value < -0.85 { - FloorTilePrefab::air(position).spawn(&mut commands); - tilemap - .floor_tiles - .insert(pos_ivec, (0, false, true, 0, [0; 8])); // Air tile - floor_positions.push((position, "air")); - } else if noise_value < 0.8 { - FloorTilePrefab::rock(position).spawn(&mut commands); - tilemap - .floor_tiles - .insert(pos_ivec, (2, true, false, 50, [0; 8])); // Rock tile - floor_positions.push((position, "rock")); + if cave_value < -0.75 { + commands.command_scope(|mut cmd| { + FloorTilePrefab::air(position).spawn(&mut cmd); + }); + local_tilemap_updates.insert(pos_ivec, (0, false, true, 0, [0; 8])); + // Air tile + } else if cave_value < 0.8 { + commands.command_scope(|mut cmd| { + FloorTilePrefab::rock(position).spawn(&mut cmd); + }); + local_tilemap_updates.insert(pos_ivec, (2, true, false, 50, [0; 8])); + // Rock tile } else { - FloorTilePrefab::dirt(position).spawn(&mut commands); - tilemap - .floor_tiles - .insert(pos_ivec, (1, true, true, 85, [0; 8])); // Dirt tile - floor_positions.push((position, "dirt")); + commands.command_scope(|mut cmd| { + FloorTilePrefab::dirt(position).spawn(&mut cmd); + }); + local_tilemap_updates.insert(pos_ivec, (1, true, true, 85, [0; 8])); + // Dirt tile } } else if noise_position.z > position.z { - if (generate_surface_noise(world_x, world_y) * TILE_SIZE).round() + if (generate_surface_terrain(world_x, world_y) * TILE_SIZE).round() <= position.z + TILE_SIZE { - FloorTilePrefab::grass(position).spawn(&mut commands); - tilemap - .floor_tiles - .insert(pos_ivec, (1, true, true, 100, [0; 8])); // Dirt tile (grass) - floor_positions.push((position, "dirt")); + commands.command_scope(|mut cmd| { + FloorTilePrefab::grass(position).spawn(&mut cmd); + }); + local_tilemap_updates.insert(pos_ivec, (1, true, true, 100, [0; 8])); // Dirt tile (grass) + surface_positions.push((position, ("grass").to_string())); } else { - FloorTilePrefab::dirt(position).spawn(&mut commands); - tilemap - .floor_tiles - .insert(pos_ivec, (1, true, true, 85, [0; 8])); // Dirt tile - floor_positions.push((position, "dirt")); + commands.command_scope(|mut cmd| { + FloorTilePrefab::dirt(position).spawn(&mut cmd); + }); + local_tilemap_updates.insert(pos_ivec, (1, true, true, 85, [0; 8])); + // Dirt tile } } else { - FloorTilePrefab::air(position).spawn(&mut commands); - tilemap - .floor_tiles - .insert(pos_ivec, (0, false, true, 0, [0; 8])); // Air tile - floor_positions.push((position, "air")); + commands.command_scope(|mut cmd| { + FloorTilePrefab::air(position).spawn(&mut cmd); + }); + local_tilemap_updates.insert(pos_ivec, (0, false, true, 0, [0; 8])); + // Air tile } } } } - // Generate fixtures and add them to tilemap + // Add our local updates to the global mutexes + { + let mut tilemap_guard = tilemap_updates.lock().unwrap(); + for (pos, data) in local_tilemap_updates { + tilemap_guard.insert(pos, data); + } + } + + // Store forrestry event for this chunk + forrestry_events.lock().unwrap().push(ChunkForrestryEvent { + chunk_position: chunk_pos, + floor_tiles: surface_positions, + }); + }); + + // Apply all collected updates to the actual resources + for (chunk_pos, value) in chunk_map_updates.into_inner().unwrap() { + chunk_map.loaded_chunks.insert(chunk_pos, value); + } + + for (pos, data) in tilemap_updates.into_inner().unwrap() { + tilemap.floor_tiles.insert(pos, data); + } + + // Send all forrestry events + for event in forrestry_events.into_inner().unwrap() { + forrestry_event_writer.write(event); + } + + if !is_empty { + println!("{} terrain chunks loaded in {:.2?}", count, start.elapsed()); + } +} + +fn generate_chunk_weathering_and_precipitation(// mut commands: Commands, + // mut events: EventReader, + // mut chunk_map: ResMut, + // mut tilemap: ResMut, +) { + // TODO: Generate weathering and precipitation + // Temperature and humidity + // Erosion + // Hadley lines etc + // Biomes +} + +fn generate_chunk_forrestry( + commands: ParallelCommands<'_, '_>, // Use ParallelCommands for parallel spawning + mut events: EventReader, + mut tilemap: ResMut, +) { + let start = Instant::now(); + let count = events.len(); + + let collected_tilemap_updates = Mutex::new(Vec::<(IVec3, (u32, bool, [u32; 8]))>::new()); + + events.par_read().for_each(|event| { + let floor_positions = &event.floor_tiles; + for (position, floor_type) in floor_positions.iter() { let above_pos = *position + Vec3::new(0.0, 0.0, TILE_SIZE); let above_ivec = above_pos.as_ivec3(); - match *floor_type { - "dirt" => { - FixtureTilePrefab::dirt_wall(above_pos).spawn(&mut commands); - tilemap.fixture_tiles.insert(above_ivec, (1, true, [0; 8])); - // Dirt wall - } - "rock" => { - FixtureTilePrefab::rock_wall(above_pos).spawn(&mut commands); - tilemap.fixture_tiles.insert(above_ivec, (2, true, [0; 8])); - // Rock wall - } - "bedrock" => { - FixtureTilePrefab::bedrock_wall(above_pos).spawn(&mut commands); - tilemap.fixture_tiles.insert(above_ivec, (3, true, [0; 8])); - // Bedrock wall + match floor_type.as_str() { + // TODO Fix rendering + "grass" => { + commands.command_scope(|mut commands| { + FixtureTilePrefab::log(above_pos).spawn(&mut commands); + }); + + collected_tilemap_updates + .lock() + .unwrap() + .push((above_ivec, (1, true, [0; 8]))); } _ => {} } } + }); + + let collected_updates = collected_tilemap_updates.into_inner().unwrap(); + for (ivec, data) in collected_updates { + tilemap.fixture_tiles.insert(ivec, data); } - if !is_empty { - println!("{} chunks loaded in {:.2?}", count, start.elapsed()); - } + println!( + "Forrestry update for {:?} chunks in {:.2?}", + count, + start.elapsed() + ); } -fn setup_initial_chunks(mut event_writer: EventWriter) { +fn generate_chunk_foliage( + mut commands: Commands, + mut events: EventReader, + mut chunk_map: ResMut, + mut tilemap: ResMut, +) { +} + +fn generate_chunk_fauna( + mut commands: Commands, + mut events: EventReader, + mut chunk_map: ResMut, + mut tilemap: ResMut, +) { +} + +fn setup_initial_chunks(mut event_writer: EventWriter) { for x in -16..=16 { for y in -9..=9 { - event_writer.write(LoadChunkEvent { + event_writer.write(GenerateChunkEvent { chunk_position: IVec2::new(x, y), }); } @@ -314,12 +453,29 @@ pub struct TilemapPlugin; impl Plugin for TilemapPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() .add_systems(Startup, (setup_chunk_system, setup_initial_chunks)) .add_systems( PostStartup, - (generate_chunks_from_algo, handle_tile_occlusion_updates).chain(), + ( + generate_chunks_from_algo, + ( + generate_chunk_terrain, + generate_chunk_weathering_and_precipitation, + generate_chunk_forrestry, + generate_chunk_foliage, + generate_chunk_fauna, + ) + .chain(), + handle_tile_occlusion_updates, + ) + .chain(), ) - .add_systems(FixedUpdate, (generate_chunks_from_algo).chain()); + .add_systems(FixedUpdate, generate_chunks_from_algo); } } diff --git a/src/tiles.rs b/src/tiles.rs index d1dad81..2b3b174 100644 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -5,6 +5,7 @@ use bevy::asset::RenderAssetUsages; use bevy::prelude::*; use bevy::render::render_resource; use bevy_platform::collections::hash_map::HashMap; +use rayon::prelude::*; #[derive(Resource)] pub struct Textures { @@ -29,6 +30,7 @@ const SKY_PATH: &str = "sky.png"; const DIRT_WALL_PATH: &str = "dirt_wall.png"; const ROCK_WALL_PATH: &str = "rock_wall.png"; const BEDROCK_WALL_PATH: &str = "bedrock_wall.png"; +const LOG_PATH: &str = "log.png"; pub fn initialize_textures(mut commands: Commands, asset_server: Res) { let mut textures: HashMap> = HashMap::new(); @@ -73,6 +75,8 @@ pub fn initialize_textures(mut commands: Commands, asset_server: Res, - mut commands: Commands, + commands: ParallelCommands<'_, '_>, mut cwss: ResMut, textures: Res, texture_ids: Res, @@ -130,18 +135,25 @@ pub fn build_quilted_terrain_sprites( let now = Instant::now(); cwss.state = TerrainSpriteState::InProgress; - for entity in query_sprites.iter() { - commands.entity(entity).despawn(); + // Despawn existing terrain sprites + let despawn_entities: Vec = query_sprites.iter().collect(); + for entity in despawn_entities { + commands.command_scope(|mut cmd| { + cmd.entity(entity).despawn(); + }); } + // Collect tiles by z-index first let mut tiles_by_z: HashMap> = HashMap::new(); + // Calculate bounds for all visible tiles - for z_index in 0..=tilemap::Z_TOTAL as usize { - for (floortile, transform) in query.iter() { + for (floortile, transform) in query.iter() { + let position = Vec2::new(transform.translation.x, transform.translation.y); + + for z_index in 0..=tilemap::Z_TOTAL as usize { let is_visible = (floortile.visible_range[z_index / 32] & (1 << (z_index % 32) as u32)) != 0; if is_visible { - let position = Vec2::new(transform.translation.x, transform.translation.y); tiles_by_z .entry(z_index) .or_default() @@ -150,12 +162,25 @@ pub fn build_quilted_terrain_sprites( } } - // For each z-index, create a new quilted texture, baked and composited. - for (z_index, tiles) in tiles_by_z.iter() { + // Thread-safe collections to store results + let dimensions_mutex = Mutex::new(HashMap::new()); + let texture_handles_mutex = Mutex::new(HashMap::new()); + + // Process each z-level in parallel + let z_indices: Vec = tiles_by_z.keys().cloned().collect(); + + z_indices.into_par_iter().for_each(|z_index| { + let tiles = if let Some(tiles) = tiles_by_z.get(&z_index) { + tiles + } else { + return; // Skip empty z-levels + }; + if tiles.is_empty() { - continue; + return; } + // Calculate bounds let min_x_aligned: f32 = ((tiles.iter().map(|(pos, _)| pos.x).reduce(f32::min).unwrap() - TILE_SIZE / 2.0) / TILE_SIZE) @@ -183,49 +208,56 @@ pub fn build_quilted_terrain_sprites( let width_px = width_tiles * TILE_PIXELS; let height_px = height_tiles * TILE_PIXELS; - quilt_cache - .dimensions - .insert(*z_index, (width_px, height_px)); + // Store dimensions in our thread-safe map + dimensions_mutex + .lock() + .unwrap() + .insert(z_index, (width_px, height_px)); let mut texture_data = vec![0u8; (width_px * height_px * 4) as usize]; + // Process tiles for this z-level + // We can't parallelize this inner loop without more complex locking on texture_data for (pos, floortile) in tiles { - let texture = textures - .handles - .get(texture_ids.refs.get(&floortile.id).unwrap()) - .unwrap(); + if let Some(texture_id) = texture_ids.refs.get(&floortile.id) { + if let Some(texture) = textures.handles.get(texture_id) { + let rel_x = pos.x - min_x_aligned; + let rel_y = pos.y - min_y_aligned; - let rel_x = pos.x - min_x_aligned; - let rel_y = pos.y - min_y_aligned; + let tile_x = (rel_x / TILE_SIZE).round() as u32; + let tile_y = (height_tiles as f32 - 1.0 - (rel_y / TILE_SIZE).round()) as u32; - let tile_x = (rel_x / TILE_SIZE).round() as u32; - let tile_y = (height_tiles as f32 - 1.0 - (rel_y / TILE_SIZE).round()) as u32; + let target_x = tile_x * TILE_PIXELS; + let target_y = tile_y * TILE_PIXELS; + let mut data: Vec<&[u8]> = vec![]; - let target_x = tile_x * TILE_PIXELS; - let target_y = tile_y * TILE_PIXELS; - let mut data: Vec<&[u8]> = vec![]; + let mut base_texture: &Image = &Default::default(); - let mut base_texture: &Image = &Default::default(); + // Use a thread-safe approach to access images + // In a full implementation, this would require a more sophisticated + // thread-safe access pattern to Assets + if let Some(_base_texture) = images.get(texture) { + if let Some(_data) = &_base_texture.data { + data.push(_data); + base_texture = _base_texture; + } + } - if let Some(_base_texture) = images.get(texture) { - if let Some(_data) = &_base_texture.data { - data.push(_data); - base_texture = _base_texture; + blit_texture_with_alpha( + data, // tile + &mut texture_data, // terrain + base_texture.size().x as u32, + base_texture.size().y as u32, + width_px, + height_px, + target_x, + target_y, + ); } } - - blit_texture_with_alpha( - data, // tile - &mut texture_data, // terrain - base_texture.size().x as u32, - base_texture.size().y as u32, - width_px, - height_px, - target_x, - target_y, - ); } + // Create the quilted texture let quilted_texture = Image::new_fill( render_resource::Extent3d { width: width_px, @@ -237,25 +269,48 @@ pub fn build_quilted_terrain_sprites( render_resource::TextureFormat::Rgba8UnormSrgb, RenderAssetUsages::RENDER_WORLD, ); - let texture_handle = images.add(quilted_texture); + // In a real implementation, we would need thread-safe access to images + // For now, we'll collect the textures and add them after parallel processing let center_x = min_x_aligned + (max_x_aligned - min_x_aligned) / 2.0; let center_y = min_y_aligned + (max_y_aligned - min_y_aligned) / 2.0; - commands.spawn(( - Sprite { - image: texture_handle, - ..Default::default() - }, - Transform::from_xyz( - center_x - TILE_SIZE / 2.0, - center_y - TILE_SIZE / 2.0, - -10.0 * TILE_SIZE, - ) - .with_scale(Vec3::splat(PIXEL_RATIO)), - Visibility::Hidden, - TerrainSprite { z_index: *z_index }, - )); + // Store texture and position data for later spawning + texture_handles_mutex + .lock() + .unwrap() + .insert(z_index, (quilted_texture, center_x, center_y)); + }); + + // Process collected textures and spawn entities + let collected_dimensions = dimensions_mutex.into_inner().unwrap(); + let collected_textures = texture_handles_mutex.into_inner().unwrap(); + + // Update quilt_cache dimensions + for (z_index, dimensions) in collected_dimensions { + quilt_cache.dimensions.insert(z_index, dimensions); + } + + // Add images and spawn entities with the collected data + for (z_index, (quilted_texture, center_x, center_y)) in collected_textures { + let texture_handle = images.add(quilted_texture); + + commands.command_scope(|mut cmd| { + cmd.spawn(( + Sprite { + image: texture_handle, + ..Default::default() + }, + Transform::from_xyz( + center_x - TILE_SIZE / 2.0, + center_y - TILE_SIZE / 2.0, + -10.0 * TILE_SIZE, + ) + .with_scale(Vec3::splat(PIXEL_RATIO)), + Visibility::Hidden, + TerrainSprite { z_index }, + )); + }); } quilt_cache.dirty_indices.clear(); @@ -488,7 +543,21 @@ impl FixtureTilePrefab { FixtureTilePrefab { transform: Transform::from_translation(position), tile: FixtureTile { - id: 2, + id: FIXTURE_ID_OFFSET + 2, + ..Default::default() + }, + visibility: Visibility::Hidden, + needs_occluded: NeedsOccluded { + has_been_occluded: false, + }, + } + } + + pub fn log(position: Vec3) -> Self { + FixtureTilePrefab { + transform: Transform::from_translation(position), + tile: FixtureTile { + id: FIXTURE_ID_OFFSET + 3, ..Default::default() }, visibility: Visibility::Hidden,