diff --git a/Cargo.lock b/Cargo.lock index 923c958..7d2025f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,6 +186,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + [[package]] name = "approx" version = "0.5.1" @@ -246,8 +252,11 @@ dependencies = [ "bevy-inspector-egui", "bevy_dylib", "bincode", + "egui", "fastrand 2.0.1", "itertools", + "puffin", + "puffin_egui", "rand", "ron", "serde", @@ -1729,6 +1738,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "dirs" version = "4.0.0" @@ -2390,6 +2408,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -2690,6 +2709,12 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lz4_flex" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15" + [[package]] name = "mach2" version = "0.4.1" @@ -2815,6 +2840,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "natord" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" + [[package]] name = "ndk" version = "0.7.0" @@ -3277,6 +3308,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "pp-rs" version = "0.2.1" @@ -3323,6 +3360,38 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df" +[[package]] +name = "puffin" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02330f795caafc2007510f742624c10aa813b8c3097c77ff344b1b86eb6be846" +dependencies = [ + "anyhow", + "bincode", + "byteorder", + "cfg-if", + "lz4_flex", + "once_cell", + "parking_lot", + "serde", +] + +[[package]] +name = "puffin_egui" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a845df86714d942260f881fa395592911e914b12d705a38ca7c8e6d32eaffd" +dependencies = [ + "egui", + "indexmap 1.9.3", + "natord", + "once_cell", + "puffin", + "time", + "vec1", + "web-time", +] + [[package]] name = "quote" version = "1.0.33" @@ -3814,6 +3883,35 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -4026,6 +4124,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "vec1" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bda7c41ca331fe9a1c278a9e7ee055f4be7f5eb1c2b72f079b4ff8b5fce9d5c" + [[package]] name = "vec_map" version = "0.8.2" @@ -4147,6 +4251,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webbrowser" version = "0.8.8" diff --git a/Cargo.toml b/Cargo.toml index f59f6af..8b2daf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,10 @@ serde-big-array = "0.5.1" ron = "0.8.1" bevy-async-task = "1.3.1" +egui = "0.24" +puffin = "0.18" +puffin_egui = "0.24" + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] bevy_dylib = "0.12.1" diff --git a/src/chunk_group.rs b/src/chunk_group.rs index a9887d6..48013b4 100644 --- a/src/chunk_group.rs +++ b/src/chunk_group.rs @@ -1,10 +1,13 @@ use crate::prelude::*; +use async_channel::Sender; +use itertools::Itertools; -pub type ChunkCorners<'a> = [Option<[&'a mut Atom; CHUNK_LEN / 4]>; 4]; -pub type ChunkSides<'a> = [Option<[&'a mut Atom; CHUNK_LEN / 2]>; 4]; +pub type ChunkCenter<'a> = Option<&'a mut [Atom; CHUNK_LEN]>; +pub type ChunkCorners<'a> = [Option<[&'a mut Atom; QUARTER_CHUNK_LEN]>; 4]; +pub type ChunkSides<'a> = [Option<[&'a mut Atom; HALF_CHUNK_LEN]>; 4]; pub struct ChunkGroup<'a> { - pub center: &'a mut [Atom; CHUNK_LEN], + pub center: ChunkCenter<'a>, pub corners: ChunkCorners<'a>, pub sides: ChunkSides<'a>, /// Position of the center chunk. @@ -12,15 +15,6 @@ pub struct ChunkGroup<'a> { } impl<'a> ChunkGroup<'a> { - pub fn new(center: &'a mut [Atom; CHUNK_LEN], center_pos: IVec2) -> Self { - Self { - center, - corners: [None, None, None, None], - sides: [None, None, None, None], - center_pos, - } - } - pub fn group_to_chunk(center_pos: IVec2, group_idx: i32) -> IVec2 { let x_diff = group_idx % 3 - 1; let y_diff = group_idx / 3 - 1; @@ -47,7 +41,7 @@ impl<'a> ChunkGroup<'a> { let mut pos = idx.0; match idx.1 { // Center - 4 => Some(&self.center[idx.0.d1()]), + 4 => Some(&self.center.as_ref().unwrap()[idx.0.d1()]), // Corners 0 | 2 | 6 | 8 => { // Offset position @@ -100,7 +94,7 @@ impl<'a> ChunkGroup<'a> { let mut pos = idx.0; match idx.1 { // Center - 4 => Some(&mut self.center[idx.0.d1()]), + 4 => Some(&mut self.center.as_mut().unwrap()[idx.0.d1()]), // Corners 0 | 2 | 6 | 8 => { // Offset position @@ -235,123 +229,163 @@ pub fn local_to_global(pos: (IVec2, i32)) -> IVec2 { IVec2::new(global_x, global_y) } -//Chunk References - -pub enum ChunkReference<'a> { - //Not chopped - Center(&'a mut [Atom; CHUNK_LEN]), - //Chopped in two - Side([Option<[&'a mut Atom; HALF_CHUNK_LEN]>; 2]), - //Chopped in four - Corner([Option<[&'a mut Atom; QUARTER_CHUNK_LEN]>; 4]), -} - -//This splits up our chunks for the update step, while also mutably borrowing them, making a `ChunkReference` -//Some chunks are not chopped others are chopped up/down, left/right, and also in four corners. -//We do this because each center chunk needs half of the adjacent chunks -//So it needs up/down/left/right halves, and also four corners -//TODO Decrease individual atoms iterations to get a &mut Atom; -pub fn get_mutable_references<'a>( +//This function gets the chunk groups and spawns a scope to update them +pub fn update_chunk_groups<'a>( chunks: &'a mut HashMap, - mutable_references: &mut HashMap>, (x_toff, y_toff): (i32, i32), - dirty_rects: &HashMap, + dirty_rects: &'a HashMap, manager_pos: IVec2, + senders: ( + &'a Sender, + &'a Sender, + ), + update: (u8, &'a Materials), + + scope: &Scope<'a, '_, ()>, ) { - chunks - .iter_mut() - .filter(|(chunk_pos, _)| { - let same_x = (chunk_pos.x + x_toff + manager_pos.x.abs() % 2) % 2 == 0; - let same_y = (chunk_pos.y + y_toff + manager_pos.y.abs() % 2) % 2 == 0; - let step_as_center = same_x && same_y; - if step_as_center && dirty_rects.contains_key(*chunk_pos) { - return true; - } else if !step_as_center { - let to_check = match (same_x, same_y) { - (false, false) => vec![ivec2(-1, -1), ivec2(-1, 1), ivec2(1, -1), ivec2(1, 1)], - (true, false) => vec![ivec2(0, -1), ivec2(0, 1)], - (false, true) => vec![ivec2(-1, 0), ivec2(1, 0)], - _ => unreachable!(), - }; - for vec in to_check { - if dirty_rects.contains_key(&(**chunk_pos + vec)) { - return true; - } - } - } + puffin::profile_function!(); - false - }) - .for_each(|(chunk_pos, chunk)| { - let same_x = (chunk_pos.x + x_toff + manager_pos.x.abs() % 2) % 2 == 0; - let same_y = (chunk_pos.y + y_toff + manager_pos.y.abs() % 2) % 2 == 0; + let (dirty_update_rect_send, dirty_render_rect_send) = senders; + let (dt, materials) = update; - match (same_x, same_y) { - (true, true) => { - mutable_references.insert(*chunk_pos, ChunkReference::Center(&mut chunk.atoms)); - } - (true, false) => { - let (up, down) = chunk.atoms.split_at_mut(CHUNK_LEN / 2); - - mutable_references.insert( - *chunk_pos, - ChunkReference::Side([ - Some(up.iter_mut().collect::>().try_into().unwrap()), - Some(down.iter_mut().collect::>().try_into().unwrap()), - ]), - ); - } - (false, true) => { - let (left, right) = split_left_right(&mut chunk.atoms); + let mut chunk_groups = vec![]; + let mut indices = HashMap::new(); - mutable_references - .insert(*chunk_pos, ChunkReference::Side([Some(left), Some(right)])); - } + for chunk_pos in dirty_rects.keys() { + let same_x = (chunk_pos.x + x_toff + manager_pos.x.abs() % 2) % 2 == 0; + let same_y = (chunk_pos.y + y_toff + manager_pos.y.abs() % 2) % 2 == 0; - (false, false) => { - let (up, down) = chunk.atoms.split_at_mut(CHUNK_LEN / 2); - - let (up_left, up_right) = updown_to_leftright(up); - let (down_left, down_right) = updown_to_leftright(down); - - mutable_references.insert( - *chunk_pos, - ChunkReference::Corner([ - Some(up_left), - Some(up_right), - Some(down_left), - Some(down_right), - ]), - ); + if !same_x || !same_y || !chunks.contains_key(chunk_pos) { + continue; + } + + let mut chunk_group = ChunkGroup { + center: None, + sides: [None, None, None, None], + corners: [None, None, None, None], + center_pos: *chunk_pos, + }; + indices.insert(chunk_pos, chunk_groups.len()); + + for (x_off, y_off) in (-1..=1).cartesian_product(-1..=1) { + let off = ivec2(x_off, y_off); + unsafe { + match (x_off, y_off) { + // CENTER + (0, 0) => {} + // UP and DOWN + (0, -1) | (0, 1) => { + let Some(chunk) = chunks.get_mut(&(*chunk_pos + off)) else { + continue; + }; + + let mut start_ptr = chunk.atoms.as_mut_ptr(); + if y_off == -1 { + start_ptr = start_ptr.add(HALF_CHUNK_LEN); + } + + let mut atoms = vec![]; + for i in 0..HALF_CHUNK_LEN { + atoms.push(start_ptr.add(i).as_mut().unwrap()); + } + + chunk_group.sides[if y_off == -1 { 0 } else { 3 }] = + Some(atoms.try_into().unwrap()); + } + //LEFT and RIGHT + (-1, 0) | (1, 0) => { + let Some(chunk) = chunks.get_mut(&(*chunk_pos + off)) else { + continue; + }; + + let start_ptr = chunk.atoms.as_mut_ptr(); + + let mut atoms = vec![]; + let mut add_off = match x_off { + -1 => HALF_CHUNK_LENGHT, + 1 => 0, + _ => unreachable!(), + }; + + for i in 0..HALF_CHUNK_LEN { + if i % HALF_CHUNK_LENGHT == 0 && i != 0 { + add_off += HALF_CHUNK_LENGHT; + } + + atoms.push(start_ptr.add(i + add_off).as_mut().unwrap()); + } + + chunk_group.sides[if x_off == -1 { 1 } else { 2 }] = + Some(atoms.try_into().unwrap()); + } + //CORNERS + (-1, -1) | (1, -1) | (-1, 1) | (1, 1) => { + let Some(chunk) = chunks.get_mut(&(*chunk_pos + off)) else { + continue; + }; + + let start_ptr = chunk.atoms.as_mut_ptr(); + + let mut atoms = vec![]; + let mut add_off = match (x_off, y_off) { + (1, 1) => 0, + (-1, 1) => HALF_CHUNK_LENGHT, + (1, -1) => HALF_CHUNK_LEN, + (-1, -1) => HALF_CHUNK_LEN + HALF_CHUNK_LENGHT, + + _ => unreachable!(), + }; + + for i in 0..QUARTER_CHUNK_LEN { + if i % HALF_CHUNK_LENGHT == 0 && i != 0 { + add_off += HALF_CHUNK_LENGHT; + } + + atoms.push(start_ptr.add(i + add_off).as_mut().unwrap()); + } + + let corner_idx = match (x_off, y_off) { + (1, 1) => 3, + (-1, 1) => 2, + (1, -1) => 1, + (-1, -1) => 0, + + _ => unreachable!(), + }; + + chunk_group.corners[corner_idx] = Some(atoms.try_into().unwrap()); + } + + _ => unreachable!(), } } - }); -} + } -pub fn split_left_right( - array: &mut [Atom], -) -> ([&mut Atom; CHUNK_LEN / 2], [&mut Atom; CHUNK_LEN / 2]) { - let (left, right): (Vec<_>, Vec<_>) = array - .chunks_mut(CHUNK_LENGHT) - .flat_map(|chunk| { - let (left, right) = chunk.split_at_mut(HALF_CHUNK_LENGHT); - left.iter_mut().zip(right.iter_mut()).collect::>() - }) - .unzip(); - - (left.try_into().unwrap(), right.try_into().unwrap()) -} + chunk_groups.push(chunk_group); + } -pub fn updown_to_leftright( - array: &mut [Atom], -) -> ([&mut Atom; CHUNK_LEN / 4], [&mut Atom; CHUNK_LEN / 4]) { - let (left, right): (Vec<_>, Vec<_>) = array - .chunks_mut(CHUNK_LENGHT) - .flat_map(|chunk| { - let (left, right) = chunk.split_at_mut(HALF_CHUNK_LENGHT); - left.iter_mut().zip(right.iter_mut()).collect::>() - }) - .unzip(); - - (left.try_into().unwrap(), right.try_into().unwrap()) + //TODO This can maybe be done with a par_iter_mut() + for (chunk_pos, chunk) in chunks.iter_mut() { + if let Some(i) = indices.get(chunk_pos) { + let chunk_group; + unsafe { + chunk_group = chunk_groups.as_mut_ptr().add(*i).as_mut().unwrap(); + } + chunk_group.center = Some(&mut chunk.atoms); + let rect = dirty_rects.get(chunk_pos).unwrap(); + + scope.spawn(async move { + update_chunks( + &mut UpdateChunksType { + group: chunk_group, + dirty_update_rect_send, + dirty_render_rect_send, + materials, + }, + dt, + rect, + ) + }); + } + } } diff --git a/src/chunk_manager.rs b/src/chunk_manager.rs index 88acad3..47d545e 100644 --- a/src/chunk_manager.rs +++ b/src/chunk_manager.rs @@ -220,6 +220,8 @@ pub fn chunk_manager_update( mut dirty_rects_resource: ResMut, materials: (Res>, Res), ) { + puffin::profile_function!(); + chunk_manager.dt = chunk_manager.dt.wrapping_add(1); let dt = chunk_manager.dt; @@ -233,15 +235,8 @@ pub fn chunk_manager_update( //Get materials let materials = &materials.0.get(materials.1 .0.clone()).unwrap(); - let first_x = chunk_manager.pos.x; - let first_y = chunk_manager.pos.y; - let last_x = chunk_manager.pos.x + LOAD_WIDTH; - let last_y = chunk_manager.pos.y + LOAD_HEIGHT; let manager_pos = ivec2(chunk_manager.pos.x, chunk_manager.pos.y); - let row_range = first_x..last_x; - let column_range = first_y..last_y; - let compute_pool = ComputeTaskPool::get(); // Create channel for sending dirty update rects @@ -294,96 +289,18 @@ pub fn chunk_manager_update( .into_iter() .cartesian_product(rand_range(0..2).into_iter()) { + puffin::profile_scope!("Update step scope."); + compute_pool.scope(|scope| { - //Get chopped chunks references - let mut mutable_references = HashMap::new(); - get_mutable_references( + update_chunk_groups( &mut chunk_manager.chunks, - &mut mutable_references, (x_toff, y_toff), dirty_rects, manager_pos, + (dirty_update_rect_send, dirty_render_rect_send), + (dt, materials), + scope, ); - - //Iterate through the center chunks - let y_iter = ((y_toff + first_y)..last_y).step_by(2); - let x_iter = ((x_toff + first_x)..last_x).step_by(2); - for (x, y) in x_iter.cartesian_product(y_iter) { - let center_pos = ivec2(x, y); - let Some(rect) = dirty_rects.get(¢er_pos) else { - continue; - }; - let ChunkReference::Center(center) = - mutable_references.remove(¢er_pos).unwrap() - else { - unreachable!() - }; - let mut chunk_group = ChunkGroup::new(center, center_pos); - - // Get the rest of the 3x3 chunks to add to the chunk group - for (x_off, y_off) in (-1..=1).cartesian_product(-1..=1) { - //If it's the center chunk, or out of bounds continue - if (x_off == 0 && y_off == 0) - || !column_range.contains(&(y + y_off)) - || !row_range.contains(&(x + x_off)) - { - continue; - } - - let (group_idx, reference_idx) = match (x_off, y_off) { - // Left Right - (-1, 0) => (1, 1), - (1, 0) => (2, 0), - // Up Down - (0, -1) => (0, 1), - (0, 1) => (3, 0), - // Corners - (-1, -1) => (0, 3), - (1, -1) => (1, 2), - (-1, 1) => (2, 1), - (1, 1) => (3, 0), - - _ => unreachable!(), - }; - - if x_off.abs() != y_off.abs() { - // Side - let side = if let Some(ChunkReference::Side(ref mut side)) = - mutable_references.get_mut(&(center_pos + ivec2(x_off, y_off))) - { - side[reference_idx].take() - } else { - unreachable!() - }; - - chunk_group.sides[group_idx] = side; - } else { - // Corner - let corner = if let Some(ChunkReference::Corner(ref mut corner)) = - mutable_references.get_mut(&(center_pos + ivec2(x_off, y_off))) - { - corner[reference_idx].take() - } else { - unreachable!() - }; - - chunk_group.corners[group_idx] = corner; - } - } - - scope.spawn(async move { - update_chunks( - &mut UpdateChunksType { - group: chunk_group, - dirty_update_rect_send, - dirty_render_rect_send, - materials, - }, - dt, - rect, - ) - }); - } }); } @@ -397,6 +314,8 @@ pub fn chunk_manager_update( } pub fn update_chunks(chunks: &mut UpdateChunksType, dt: u8, dirty_rect: &URect) { + puffin::profile_function!(); + let materials = chunks.materials; let x_iter = rand_range(dirty_rect.min.x as i32..dirty_rect.max.x as i32 + 1).into_iter(); diff --git a/src/main.rs b/src/main.rs index 76881ba..930cefb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,10 +14,12 @@ mod manager_api; mod materials; mod particles; mod player; +mod puffin_plugin; mod prelude { pub use crate::{ actors::*, animation::*, atom::*, camera::*, chunk::*, chunk_group::*, chunk_manager::*, consts::*, debug::*, geom_tools::*, manager_api::*, materials::*, particles::*, player::*, + puffin_plugin::*, }; pub use bevy::input::mouse::MouseScrollUnit; pub use bevy::input::mouse::MouseWheel; @@ -63,6 +65,10 @@ fn main() { app.add_plugins(DebugPlugin); } + if args.contains(&"-p".to_string()) || args.contains(&"--profiling".to_string()) { + app.add_plugins(PuffinPlugin); + } + app.run(); } diff --git a/src/manager_api.rs b/src/manager_api.rs index 70aee1b..6fa2710 100644 --- a/src/manager_api.rs +++ b/src/manager_api.rs @@ -8,7 +8,7 @@ use async_channel::Sender; use crate::prelude::*; pub struct UpdateChunksType<'a> { - pub group: ChunkGroup<'a>, + pub group: &'a mut ChunkGroup<'a>, pub dirty_update_rect_send: &'a Sender, pub dirty_render_rect_send: &'a Sender, pub materials: &'a Materials, diff --git a/src/puffin_plugin.rs b/src/puffin_plugin.rs new file mode 100644 index 0000000..f54781f --- /dev/null +++ b/src/puffin_plugin.rs @@ -0,0 +1,27 @@ +use crate::prelude::*; +use bevy::window::PrimaryWindow; +use bevy_inspector_egui::bevy_egui::EguiContext; + +fn setup() { + puffin::set_scopes_on(true); +} + +fn new_frame() { + puffin::GlobalProfiler::lock().new_frame(); +} + +fn egui(mut egui_ctx: Query<&mut EguiContext, With>) { + let Ok(mut ctx) = egui_ctx.get_single_mut() else { + return; + }; + puffin_egui::profiler_window(ctx.get_mut()); +} + +pub struct PuffinPlugin; +impl Plugin for PuffinPlugin { + fn build(&self, app: &mut App) { + app.add_systems(First, new_frame) + .add_systems(Update, egui) + .add_systems(Startup, setup); + } +}