WebGPU Voxel Game

Async chunk loading

+24
Cargo.lock
··· 254 254 "rusqlite", 255 255 "thiserror 2.0.17", 256 256 "tobj", 257 + "tokio", 257 258 "wasm-bindgen", 258 259 "wasm-bindgen-futures", 259 260 "web-sys", ··· 2591 2592 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2592 2593 2593 2594 [[package]] 2595 + name = "signal-hook-registry" 2596 + version = "1.4.7" 2597 + source = "registry+https://github.com/rust-lang/crates.io-index" 2598 + checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" 2599 + dependencies = [ 2600 + "libc", 2601 + ] 2602 + 2603 + [[package]] 2594 2604 name = "simd-adler32" 2595 2605 version = "0.3.8" 2596 2606 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2880 2890 "bytes", 2881 2891 "libc", 2882 2892 "mio", 2893 + "parking_lot", 2883 2894 "pin-project-lite", 2895 + "signal-hook-registry", 2884 2896 "socket2", 2897 + "tokio-macros", 2885 2898 "windows-sys 0.61.2", 2899 + ] 2900 + 2901 + [[package]] 2902 + name = "tokio-macros" 2903 + version = "2.6.0" 2904 + source = "registry+https://github.com/rust-lang/crates.io-index" 2905 + checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 2906 + dependencies = [ 2907 + "proc-macro2", 2908 + "quote", 2909 + "syn", 2886 2910 ] 2887 2911 2888 2912 [[package]]
+1
Cargo.toml
··· 25 25 bincode = "2.0.0-RC.3" 26 26 instant = "0.1" 27 27 base64 = "0.22.1" 28 + tokio = { version = "1", features = ["full"]} 28 29 29 30 [build-dependencies] 30 31 fs_extra = "1.3"
+3 -37
src/app.rs
··· 1 1 use crate::world::chunk::Chunk; 2 + use crate::world::map::WorldMap; 2 3 use crate::{ 3 - concurrency::GameThread, 4 4 gfx::{Gfx, GfxBuilder, MaybeGfx}, 5 5 gui::EguiRenderer, 6 6 world::World, ··· 32 32 window: Option<Arc<Window>>, 33 33 egui: Option<EguiRenderer>, 34 34 world: Arc<Mutex<World>>, 35 - world_update_thread: GameThread<(), (i32, i32, i32)>, 36 35 last_render_time: instant::Instant, 37 36 conn: ConnectionOnlyOnNative, 38 37 } ··· 40 39 impl Application { 41 40 pub fn new(event_loop: &EventLoop<Gfx>, title: &str, conn: Connection) -> Self { 42 41 let world = Arc::new(Mutex::new(World { 43 - map: Default::default(), 42 + map: WorldMap::new(), 44 43 dirty: false, 45 44 })); 46 45 Self { ··· 49 48 window: None, 50 49 egui: None, 51 50 world: world.clone(), 52 - world_update_thread: { 53 - let (tx, rx) = channel(); 54 - 55 - let thread = spawn(move || { 56 - let w = world; 57 - //let mut conn = rusqlite::Connection::open("./save.sqlite").unwrap(); 58 - 59 - loop { 60 - println!("Looping!"); 61 - 62 - // TODO: Why is this in a separate thread? Analyse. 63 - // This has "temporarily" been disabled in gfx/camera.rs. 64 - let Ok((x, y, z)) = rx.recv() else { 65 - break; 66 - }; 67 - 68 - let Ok(ref mut w) = w.lock() else { 69 - log::error!("Poisoned mutex?"); 70 - break; 71 - }; 72 - 73 - w.map.focus = ivec3(x, y, z); 74 - w.dirty = true; 75 - } 76 - }); 77 - 78 - GameThread::new(thread, tx) 79 - }, 80 51 last_render_time: instant::Instant::now(), 81 52 conn, 82 53 } ··· 256 227 ) { 257 228 Ok(_) => { 258 229 // TODO CITE https://github.com/kaphula/winit-egui-wgpu-template/blob/master/src/app.rs#L3 259 - gfx.update( 260 - self.world.clone(), 261 - &mut self.conn, 262 - &mut self.world_update_thread, 263 - dt, 264 - ); 230 + gfx.update(self.world.clone(), &mut self.conn, dt); 265 231 } 266 232 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { 267 233 gfx.resize(window.inner_size());
-17
src/concurrency.rs
··· 1 - use std::sync::mpsc::Sender; 2 - use std::thread::JoinHandle; 3 - 4 - pub struct GameThread<T, U> { 5 - pub join_handle: JoinHandle<T>, 6 - pub tx: Sender<U>, 7 - } 8 - 9 - impl<T, U> GameThread<T, U> { 10 - pub fn new(join_handle: JoinHandle<T>, tx: Sender<U>) -> Self { 11 - GameThread { join_handle, tx } 12 - } 13 - 14 - // pub fn fulfil(&mut self, join_handle: JoinHandle<T>) { 15 - // self.join_handle = Some(join_handle); 16 - // } 17 - }
+39 -84
src/gfx.rs
··· 4 4 mod resources; 5 5 mod texture; 6 6 7 - use crate::concurrency::GameThread; 8 - use crate::gfx::camera::CameraUniform; 7 + use crate::gfx::camera::{Camera, CameraUniform}; 9 8 use crate::gfx::model::DrawLight; 10 - use crate::world::chunk::{sl3get, sl3get_opt, CHUNK_SIZE}; 9 + use crate::ivec3_product; 10 + use crate::world::chunk::{sl3get, sl3get_opt, Chunk, CHUNK_SIZE}; 11 11 use crate::{ 12 12 app::WASM_WIN_SIZE, 13 13 gfx::model::Vertex, ··· 344 344 345 345 // MAP LOAD 346 346 347 - let mut map = crate::world::map::WorldMap::default(); 347 + let mut map = WorldMap::new(); 348 348 349 - let instances = Self::remake_instance_buf(&mut map); 349 + let instances = Self::remake_instance_buf(&camera_state.object, &mut map); 350 350 351 351 let instance_data = instances.iter().map(Instance::to_raw).collect::<Vec<_>>(); 352 352 let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { ··· 655 655 ); 656 656 } 657 657 658 - fn remake_instance_buf(map: &mut WorldMap) -> Vec<Instance> { 658 + fn remake_instance_buf(camera: &Camera, _map: &mut WorldMap) -> Vec<Instance> { 659 659 let mut instances = vec![]; 660 660 661 - const SPACE_BETWEEN: f32 = 2.0; 661 + // WGPU Crashes with an empty instance buffer, add a small item out of camera vfar 662 + if instances.is_empty() { 663 + instances.push(Instance { 664 + position: Vec3::splat(9999.), 665 + rotation: Quat::from_axis_angle(Vec3::Y, 0.0), 666 + }) 667 + } 668 + instances 669 + } 662 670 663 - let mut conn = rusqlite::Connection::open("./save.sqlite").unwrap(); 671 + pub fn update_instance_buf(&mut self, map: &mut WorldMap) { 672 + let mut instances = self.object.instances.clone(); 673 + let camera = &self.camera.object; 664 674 665 - for (coords, chunk) in map.chunks_around_focus(&mut conn, 5).iter() { 666 - let _3diter = itertools::iproduct!(0..CHUNK_SIZE.0, 0..CHUNK_SIZE.1, 0..CHUNK_SIZE.2); 675 + const RENDER_RADIUS: i32 = 1; 667 676 668 - let mut i = _3diter 669 - .filter_map(|(x, y, z)| { 670 - if let BlockKind::Air = sl3get(&chunk.blocks, x, y, z) { 671 - return None; 672 - } 677 + //println!("{:?}", camera.chunk_pos()); 673 678 674 - // lookup if node is surrounded by nodes 675 - let mut do_not_occlude = false; 676 - let mut sum = vec![]; 679 + let lower = (camera.chunk_pos() - IVec3::splat(RENDER_RADIUS)).with_y(-1); 680 + let higher = (camera.chunk_pos() + IVec3::splat(RENDER_RADIUS)).with_y(1); 677 681 678 - for (x_, y_, z_) in [ 679 - (-1_i32, 0_i32, 0_i32), 680 - (1, 0, 0), 681 - (0, -1, 0), 682 - (0, 1, 0), 683 - (0, 0, -1), 684 - (0, 0, 1), 685 - ] { 686 - if x == 0 687 - || y == 0 688 - || z == 0 689 - || x == CHUNK_SIZE.0 - 1 690 - || y == CHUNK_SIZE.2 - 1 691 - || z == CHUNK_SIZE.1 - 1 692 - { 693 - do_not_occlude = true; 694 - break; 695 - } 682 + // poke! 683 + ivec3_product(lower, higher).for_each(|idx| map.request_chunk_load(idx)); 696 684 697 - let (x, y, z) = (x as i32 + x_, y as i32 + y_, z as i32 + z_); 698 - if x < 0 || y < 0 || z < 0 699 - // || x == CHUNK_SIZE.0 as i32 - 1 700 - // || y == CHUNK_SIZE.2 as i32 - 1 701 - // || z == CHUNK_SIZE.1 as i32 - 1 702 - { 703 - continue; 704 - } 685 + while let Ok(idx) = map.response_rx.try_recv() { 686 + println!("GFX: got {idx}"); 687 + let chunk = map.get_chunk(idx).expect("it said it was ready!"); 705 688 706 - if let Some(block) = 707 - sl3get_opt(&chunk.blocks, x as usize, y as usize, z as usize) 708 - { 709 - sum.push(block) 710 - } 711 - } 689 + let mut ids = chunk.indices.clone(); 690 + instances.append(&mut ids); 691 + } 692 + // while let Some(idx) = map.try_chunk() { 693 + // } 712 694 713 - if !do_not_occlude && sum.iter().all(|b| *b == BlockKind::Brick) { 714 - return None; 715 - } 695 + // for (coords, chunk) in map.chunks_around_focus(&mut conn, 5).iter() { 716 696 717 - let chunk_offset = coords.as_vec3() * (SPACE_BETWEEN * CHUNK_SIZE.0 as f32); 718 - 719 - let mapping = |n| SPACE_BETWEEN * (n as f32 - CHUNK_SIZE.0 as f32 / 2.0); 720 - let position = vec3( 721 - mapping(x) + chunk_offset.x, 722 - mapping(y) + chunk_offset.y, 723 - mapping(z) + chunk_offset.z, 724 - ); 725 - 726 - let rotation = Quat::from_axis_angle(Vec3::Y, 0.0); 727 - 728 - Some(Instance { position, rotation }) 729 - }) 730 - .collect::<Vec<_>>(); 731 - 732 - instances.append(&mut i); 733 - } 697 + // } 734 698 735 699 // WGPU Crashes with an empty instance buffer, add a small item out of camera vfar 736 700 if instances.is_empty() { ··· 739 703 rotation: Quat::from_axis_angle(Vec3::Y, 0.0), 740 704 }) 741 705 } 742 - instances 743 - } 744 - 745 - pub fn update_instance_buf(&mut self, map: &mut WorldMap) { 746 - let instances = Self::remake_instance_buf(map); 747 706 748 707 let instance_data = instances.iter().map(Instance::to_raw).collect::<Vec<_>>(); 749 708 let instance_buffer = self ··· 912 871 &mut self, 913 872 world: Arc<Mutex<World>>, 914 873 conn: &mut ConnectionOnlyOnNative, 915 - thread: &mut GameThread<(), (i32, i32, i32)>, 916 874 dt: instant::Duration, 917 875 ) { 918 876 // Camera update 919 - self.camera.controller.update_camera( 920 - &mut self.camera.object, 921 - dt, 922 - world.clone(), 923 - conn, 924 - thread, 925 - ); 877 + self.camera 878 + .controller 879 + .update_camera(&mut self.camera.object, dt, world.clone(), conn); 926 880 self.camera 927 881 .uniform 928 882 .update_view_proj(&self.camera.object, &self.camera.projection); ··· 949 903 ); 950 904 951 905 let mut world = world.lock().unwrap(); 906 + self.update_instance_buf(&mut world.map); 907 + 952 908 // Object update 953 909 if world.dirty { 954 - self.update_instance_buf(&mut world.map); 955 910 world.dirty = false; 956 911 } 957 912 }
+32 -26
src/gfx/camera.rs
··· 1 - use crate::concurrency::GameThread; 2 1 use crate::world::{chunk::Chunk, map::RENDER_GRID_SIZE, World}; 3 2 use crate::ConnectionOnlyOnNative; 4 3 use glam::{ivec3, vec3, IVec3, Mat4, Vec2, Vec3, Vec4}; ··· 74 73 Vec3::Y, 75 74 ) 76 75 } 76 + pub fn chunk_pos(&self) -> IVec3 { 77 + const BLOCK_UNIT_SIZE: i32 = 32; 78 + IVec3::from(( 79 + self.position.x as i32 / BLOCK_UNIT_SIZE, 80 + self.position.y as i32 / BLOCK_UNIT_SIZE, 81 + self.position.z as i32 / BLOCK_UNIT_SIZE, 82 + )) 83 + } 77 84 } 78 85 79 86 pub struct Projection { ··· 165 172 duration: Duration, 166 173 world: Arc<Mutex<World>>, 167 174 conn: &mut ConnectionOnlyOnNative, 168 - world_thread: &mut GameThread<(), (i32, i32, i32)>, 169 175 ) { 170 176 let dt = duration.as_secs_f32(); 171 177 let movement = self.movement.vec3(); ··· 188 194 camera.yaw += self.rotation.x * self.sensitivity * dt; 189 195 camera.pitch += -self.rotation.y * self.sensitivity * dt; 190 196 191 - if self.load_chunks { 192 - const BLOCK_UNIT_SIZE: i32 = 32; 193 - let chunk_relative = IVec3::from(( 194 - camera.position.x as i32 / BLOCK_UNIT_SIZE, 195 - camera.position.y as i32 / BLOCK_UNIT_SIZE, 196 - camera.position.z as i32 / BLOCK_UNIT_SIZE, 197 - )) + IVec3::splat(-(RENDER_GRID_SIZE as i32 / 2)); 198 - let mut world = world.lock().unwrap(); 199 - if chunk_relative != world.map.focus { 200 - // This is a stupid way to do threading. 201 - // world_thread.tx.send((x, y, z)).unwrap(); 197 + // if self.load_chunks { 198 + // const BLOCK_UNIT_SIZE: i32 = 32; 199 + // let chunk_relative = IVec3::from(( 200 + // camera.position.x as i32 / BLOCK_UNIT_SIZE, 201 + // camera.position.y as i32 / BLOCK_UNIT_SIZE, 202 + // camera.position.z as i32 / BLOCK_UNIT_SIZE, 203 + // )) + IVec3::splat(-(RENDER_GRID_SIZE as i32 / 2)); 204 + // let mut world = world.lock().unwrap(); 205 + // if chunk_relative != world.map.focus { 206 + // // This is a stupid way to do threading. 207 + // // world_thread.tx.send((x, y, z)).unwrap(); 202 208 203 - world.map.focus = chunk_relative; 204 - world.dirty = true; 205 - } 209 + // world.map.focus = chunk_relative; 210 + // world.dirty = true; 211 + // } 206 212 207 - // if chunk_relative != world.map.chunks.offset().into() { 208 - // world.map.chunks.reposition( 209 - // (IVec3::from(chunk_relative)).into(), 210 - // |_old, new, chunk| { 211 - // *chunk = Chunk::load(ivec3(new.0, new.1, new.2), conn).unwrap(); 212 - // }, 213 - // ); 214 - // *remake = true; 215 - // } 216 - } 213 + // // if chunk_relative != world.map.chunks.offset().into() { 214 + // // world.map.chunks.reposition( 215 + // // (IVec3::from(chunk_relative)).into(), 216 + // // |_old, new, chunk| { 217 + // // *chunk = Chunk::load(ivec3(new.0, new.1, new.2), conn).unwrap(); 218 + // // }, 219 + // // ); 220 + // // *remake = true; 221 + // // } 222 + // } 217 223 218 224 self.rotation = Vec2::ZERO; 219 225
+27 -32
src/gui.rs
··· 158 158 ) { 159 159 let mut scale_factor = self.scale_factor; 160 160 let (mut chunk_x, mut chunk_y, mut chunk_z) = self.chunk_influence; 161 - let mut map_focus = world.map.focus; 162 161 let mut camera_load = gfx.camera.controller.load_chunks; 163 162 164 163 let dt = dt.as_secs_f32(); ··· 258 257 ui.heading("World toys..."); 259 258 260 259 ui.checkbox(&mut camera_load, "Camera position loads chunks"); 261 - ui.label("Move chunk window... "); 262 - ui.horizontal(|ui| { 263 - ui.add_enabled( 264 - !camera_load, 265 - egui::DragValue::new(&mut map_focus.x) 266 - .speed(0.1) 267 - .update_while_editing(false), 268 - ); 269 - ui.label("x "); 260 + // ui.label("Move chunk window... "); 261 + // ui.horizontal(|ui| { 262 + // ui.add_enabled( 263 + // !camera_load, 264 + // egui::DragValue::new(&mut map_focus.x) 265 + // .speed(0.1) 266 + // .update_while_editing(false), 267 + // ); 268 + // ui.label("x "); 270 269 271 - ui.add_enabled( 272 - !camera_load, 273 - egui::DragValue::new(&mut map_focus.y) 274 - .speed(0.1) 275 - .update_while_editing(false), 276 - ); 277 - ui.label("y "); 270 + // ui.add_enabled( 271 + // !camera_load, 272 + // egui::DragValue::new(&mut map_focus.y) 273 + // .speed(0.1) 274 + // .update_while_editing(false), 275 + // ); 276 + // ui.label("y "); 278 277 279 - ui.add_enabled( 280 - !camera_load, 281 - egui::DragValue::new(&mut map_focus.z) 282 - .speed(0.1) 283 - .update_while_editing(false), 284 - ); 285 - ui.label("z."); 286 - }); 278 + // ui.add_enabled( 279 + // !camera_load, 280 + // egui::DragValue::new(&mut map_focus.z) 281 + // .speed(0.1) 282 + // .update_while_editing(false), 283 + // ); 284 + // ui.label("z."); 285 + // }); 287 286 288 287 ui.separator(); 289 288 ui.horizontal(|ui| { ··· 314 313 }; 315 314 316 315 if let Some(chunk) = c { 317 - world.map.chunks.insert(pos, chunk); 318 - world.dirty = true; 316 + // world.map.chunks.insert(pos, chunk); 317 + // world.dirty = true; 318 + todo!("asdf") 319 319 } 320 320 } 321 321 }); ··· 333 333 self.chunk_influence = (chunk_x, chunk_y, chunk_z); 334 334 335 335 gfx.camera.controller.load_chunks = camera_load; 336 - 337 - // If map focus has moved in tick, then: 338 - if !camera_load && map_focus != world.map.focus { 339 - world.dirty = true; 340 - } 341 336 } 342 337 }
+16 -2
src/lib.rs
··· 1 1 #![allow(rust_analyzer::inactive_code)] 2 2 3 3 mod app; 4 - mod concurrency; 5 4 mod gfx; 6 5 mod gui; 7 6 mod world; 8 7 9 - use glam::{Mat3, Quat, Vec3}; 8 + use glam::{ivec3, IVec3, Mat3, Quat, Vec3}; 10 9 #[cfg(target_arch = "wasm32")] 11 10 use wasm_bindgen::prelude::*; 12 11 use wasm_bindgen::UnwrapThrowExt; 13 12 use winit::event_loop::EventLoop; 14 13 use world::chunk::preload_chunk_cache; 15 14 15 + #[derive(Copy, Clone, Debug)] 16 16 struct Instance { 17 17 position: Vec3, 18 18 rotation: Quat, ··· 125 125 let mut app = app::Application::new(&event_loop, "BL0CK", conn); 126 126 event_loop.run_app(&mut app).unwrap(); 127 127 } 128 + 129 + fn ivec3_product(lower: IVec3, higher: IVec3) -> impl Iterator<Item = IVec3> { 130 + let IVec3 { 131 + x: ax, 132 + y: ay, 133 + z: az, 134 + } = lower; 135 + let IVec3 { 136 + x: bx, 137 + y: by, 138 + z: bz, 139 + } = higher; 140 + itertools::iproduct!(ax..=bx, ay..=by, az..=bz).map(|(i, j, k)| ivec3(i, j, k)) 141 + }
+3 -2
src/main.rs
··· 1 1 use bl0ck::run; 2 2 3 - fn main() { 4 - pollster::block_on(run()) 3 + #[tokio::main] 4 + async fn main() { 5 + run().await 5 6 }
+2 -1
src/world.rs
··· 13 13 &self, 14 14 conn: &mut ConnectionOnlyOnNative, 15 15 ) -> Result<(), Box<dyn std::error::Error>> { 16 - self.map.save(conn)?; 16 + // self.map.save(conn)?; 17 + todo!("save"); 17 18 Ok(()) 18 19 } 19 20 }
+178 -109
src/world/chunk.rs
··· 1 1 use super::map::BlockKind; 2 - use crate::ConnectionOnlyOnNative; 2 + use crate::{ConnectionOnlyOnNative, Instance}; 3 3 use bincode::{Decode, Encode}; 4 - use glam::{ivec3, IVec3}; 4 + use glam::{ivec3, vec3, IVec3, Quat, Vec3}; 5 5 use itertools::Itertools; 6 6 use std::sync::Mutex; 7 7 use std::{ ··· 38 38 Random, 39 39 } 40 40 41 - #[derive(Copy, Clone, Encode, Decode)] 41 + #[derive(Clone)] 42 42 pub struct Chunk { 43 43 pub blocks: Slice3, 44 + pub indices: Vec<Instance>, 44 45 } 45 46 46 47 impl Chunk { 48 + fn indices(blocks: Slice3, idx: IVec3) -> Vec<Instance> { 49 + let _3diter = itertools::iproduct!(0..CHUNK_SIZE.0, 0..CHUNK_SIZE.1, 0..CHUNK_SIZE.2); 50 + 51 + _3diter 52 + .filter_map(|(x, y, z)| { 53 + if let BlockKind::Air = sl3get(&blocks, x, y, z) { 54 + return None; 55 + } 56 + 57 + // lookup if node is surrounded by nodes 58 + let mut do_not_occlude = false; 59 + let mut sum = vec![]; 60 + 61 + for (x_, y_, z_) in [ 62 + (-1_i32, 0_i32, 0_i32), 63 + (1, 0, 0), 64 + (0, -1, 0), 65 + (0, 1, 0), 66 + (0, 0, -1), 67 + (0, 0, 1), 68 + ] { 69 + if x == 0 70 + || y == 0 71 + || z == 0 72 + || x == CHUNK_SIZE.0 - 1 73 + || y == CHUNK_SIZE.2 - 1 74 + || z == CHUNK_SIZE.1 - 1 75 + { 76 + do_not_occlude = true; 77 + break; 78 + } 79 + 80 + let (x, y, z) = (x as i32 + x_, y as i32 + y_, z as i32 + z_); 81 + if x < 0 || y < 0 || z < 0 82 + // || x == CHUNK_SIZE.0 as i32 - 1 83 + // || y == CHUNK_SIZE.2 as i32 - 1 84 + // || z == CHUNK_SIZE.1 as i32 - 1 85 + { 86 + continue; 87 + } 88 + 89 + if let Some(block) = sl3get_opt(&blocks, x as usize, y as usize, z as usize) { 90 + sum.push(block) 91 + } 92 + } 93 + 94 + if !do_not_occlude && sum.iter().all(|b| *b == BlockKind::Brick) { 95 + return None; 96 + } 97 + const SPACE_BETWEEN: f32 = 2.0; 98 + 99 + let chunk_offset = idx.as_vec3() * (SPACE_BETWEEN * CHUNK_SIZE.0 as f32); 100 + 101 + let mapping = |n| SPACE_BETWEEN * (n as f32 - CHUNK_SIZE.0 as f32 / 2.0); 102 + let position = vec3( 103 + mapping(x) + chunk_offset.x, 104 + mapping(y) + chunk_offset.y, 105 + mapping(z) + chunk_offset.z, 106 + ); 107 + 108 + let rotation = Quat::from_axis_angle(Vec3::Y, 0.0); 109 + 110 + Some(Instance { position, rotation }) 111 + }) 112 + .collect::<Vec<_>>() 113 + } 114 + 47 115 fn generate_normal(world_pos: IVec3) -> Chunk { 48 116 let blocks = itertools::iproduct!(0..CHUNK_SIZE.0, 0..CHUNK_SIZE.1, 0..CHUNK_SIZE.2) 49 117 .map(|(x, z, y)| { ··· 66 134 .collect_array() 67 135 .unwrap(); 68 136 69 - Chunk { blocks } 137 + Chunk { 138 + blocks, 139 + indices: Chunk::indices(blocks, world_pos), 140 + } 70 141 } 71 142 72 143 fn generate_callback(method: ChunkScramble) -> fn(IVec3) -> Chunk { 73 144 use ChunkScramble as C; 74 145 match method { 75 146 C::Normal => Chunk::generate_normal, 76 - C::Inverse => |p| Chunk { 77 - blocks: Chunk::generate_normal(p) 147 + C::Inverse => |p| { 148 + let blocks = Chunk::generate_normal(p) 78 149 .blocks 79 150 .iter() 80 151 .map(|b| match b { ··· 82 153 BlockKind::Brick => BlockKind::Air, 83 154 }) 84 155 .collect_array() 85 - .unwrap(), 156 + .unwrap(); 157 + Chunk { 158 + blocks, 159 + indices: Chunk::indices(blocks, p), 160 + } 86 161 }, 87 - C::Random => |_| Chunk { 162 + C::Random => |p| { 88 163 #[cfg(not(target_arch = "wasm32"))] 89 - blocks: { 164 + let blocks = { 90 165 use rand::Rng; 91 166 rand::rng().random() 92 - }, 93 - #[cfg(target_arch = "wasm32")] 94 - blocks: { panic!("i hate the web") }, 167 + }; 168 + 169 + Chunk { 170 + blocks, 171 + indices: Chunk::indices(blocks, p), 172 + } 95 173 }, 96 174 } 97 175 } ··· 99 177 Chunk::generate_callback(method)(map_pos) 100 178 } 101 179 102 - pub fn save( 103 - &self, 104 - map_pos: IVec3, 105 - conn: &mut ConnectionOnlyOnNative, 106 - ) -> Result<(), Box<dyn std::error::Error>> { 107 - let config = bincode::config::standard(); 180 + // pub fn save( 181 + // &self, 182 + // map_pos: IVec3, 183 + // conn: &mut ConnectionOnlyOnNative, 184 + // ) -> Result<(), Box<dyn std::error::Error>> { 185 + // let config = bincode::config::standard(); 108 186 109 - #[cfg(not(target_arch = "wasm32"))] 110 - { 111 - let encoded = bincode::encode_to_vec(self, config)?; 187 + // #[cfg(not(target_arch = "wasm32"))] 188 + // { 189 + // let encoded = bincode::encode_to_vec(self, config)?; 112 190 113 - let mut stmt = conn.prepare_cached( 114 - r#" 115 - INSERT INTO chunks (x,y,z,data) 116 - VALUES (?,?,?,?) 117 - "#, 118 - )?; 119 - stmt.insert((map_pos.x, map_pos.y, map_pos.z, encoded))?; 120 - } 191 + // let mut stmt = conn.prepare_cached( 192 + // r#" 193 + // INSERT INTO chunks (x,y,z,data) 194 + // VALUES (?,?,?,?) 195 + // "#, 196 + // )?; 197 + // stmt.insert((map_pos.x, map_pos.y, map_pos.z, encoded))?; 198 + // } 121 199 122 - // We are going to use LocalStorage for web. I don't like it either. 123 - #[cfg(target_arch = "wasm32")] 124 - { 125 - use base64::prelude::{Engine, BASE64_STANDARD}; 126 - let encoded = bincode::encode_to_vec(self, config)?; 127 - let encoded = BASE64_STANDARD.encode(encoded); 200 + // // We are going to use LocalStorage for web. I don't like it either. 201 + // #[cfg(target_arch = "wasm32")] 202 + // { 203 + // use base64::prelude::{Engine, BASE64_STANDARD}; 204 + // let encoded = bincode::encode_to_vec(self, config)?; 205 + // let encoded = BASE64_STANDARD.encode(encoded); 128 206 129 - let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 130 - store.set(&file_name, &encoded); 131 - } 132 - Ok(()) 133 - } 134 - fn load_from_file( 135 - map_pos: IVec3, 136 - conn: &mut ConnectionOnlyOnNative, 137 - ) -> Result<Option<Chunk>, Box<dyn std::error::Error>> { 138 - let config = bincode::config::standard(); 139 - let file_hash = calculate_hash(&map_pos); 140 - let file_name = format!("chunk_{}.bl0ck", file_hash); 207 + // let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 208 + // store.set(&file_name, &encoded); 209 + // } 210 + // Ok(()) 211 + // } 212 + // fn load_from_file( 213 + // map_pos: IVec3, 214 + // conn: &mut ConnectionOnlyOnNative, 215 + // ) -> Result<Option<Chunk>, Box<dyn std::error::Error>> { 216 + // let config = bincode::config::standard(); 217 + // let file_hash = calculate_hash(&map_pos); 218 + // let file_name = format!("chunk_{}.bl0ck", file_hash); 141 219 142 - #[cfg(not(target_arch = "wasm32"))] 143 - { 144 - // let file_path = Path::new("./save/chunk/").join(Path::new(&file_name)); 145 - // if file_path.exists() { 146 - // log::warn!("Load Chunk!"); 147 - // let mut file = File::open(file_path).unwrap(); 220 + // #[cfg(not(target_arch = "wasm32"))] 221 + // { 222 + // // let file_path = Path::new("./save/chunk/").join(Path::new(&file_name)); 223 + // // if file_path.exists() { 224 + // // log::warn!("Load Chunk!"); 225 + // // let mut file = File::open(file_path).unwrap(); 148 226 149 - let mut stmt = conn.prepare_cached( 150 - r#" 151 - SELECT (data) from chunks 152 - WHERE (x,y,z) == (?,?,?) 153 - "#, 154 - )?; 155 - let i: Vec<u8> = 156 - match stmt.query_row((map_pos.x, map_pos.y, map_pos.z), |f| f.get("data")) { 157 - Ok(x) => x, 158 - Err(rusqlite::Error::QueryReturnedNoRows) => { 159 - return Ok(None); 160 - } 161 - Err(e) => { 162 - return Err(e.into()); 163 - } 164 - }; 227 + // let mut stmt = conn.prepare_cached( 228 + // r#" 229 + // SELECT (data) from chunks 230 + // WHERE (x,y,z) == (?,?,?) 231 + // "#, 232 + // )?; 233 + // let i: Vec<u8> = 234 + // match stmt.query_row((map_pos.x, map_pos.y, map_pos.z), |f| f.get("data")) { 235 + // Ok(x) => x, 236 + // Err(rusqlite::Error::QueryReturnedNoRows) => { 237 + // return Ok(None); 238 + // } 239 + // Err(e) => { 240 + // return Err(e.into()); 241 + // } 242 + // }; 165 243 166 - let (decoded, _) = bincode::decode_from_slice(i.as_slice(), config)?; 244 + // let (decoded, _) = bincode::decode_from_slice(i.as_slice(), config)?; 167 245 168 - Ok(Some(decoded)) 169 - // } else { 170 - // log::warn!("Chunk not loaded!"); 171 - // Ok(None) 172 - // } 173 - } 174 - #[cfg(target_arch = "wasm32")] 175 - { 176 - use base64::prelude::{Engine, BASE64_STANDARD}; 177 - let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 178 - if let Ok(Some(s)) = store.get(&file_name) { 179 - let s = BASE64_STANDARD.decode(s)?; 180 - let (decoded, _) = bincode::decode_from_slice(&s[..], config)?; 246 + // Ok(Some(decoded)) 247 + // // } else { 248 + // // log::warn!("Chunk not loaded!"); 249 + // // Ok(None) 250 + // // } 251 + // } 252 + // #[cfg(target_arch = "wasm32")] 253 + // { 254 + // use base64::prelude::{Engine, BASE64_STANDARD}; 255 + // let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 256 + // if let Ok(Some(s)) = store.get(&file_name) { 257 + // let s = BASE64_STANDARD.decode(s)?; 258 + // let (decoded, _) = bincode::decode_from_slice(&s[..], config)?; 181 259 182 - Ok(Some(decoded)) 183 - } else { 184 - Ok(None) 185 - } 186 - } 187 - } 260 + // Ok(Some(decoded)) 261 + // } else { 262 + // Ok(None) 263 + // } 264 + // } 265 + // } 188 266 189 267 pub fn load( 190 268 map_pos: IVec3, ··· 195 273 #[cfg(target_arch = "wasm32")] 196 274 let cached = false; 197 275 198 - if cached { 199 - // log::warn!("Cache hit!"); 200 - #[cfg(not(target_arch = "wasm32"))] 201 - return Ok(CHUNK_FILE_CACHE.lock().unwrap()[&map_pos]); 202 - #[cfg(target_arch = "wasm32")] 203 - return unreachable!(); 204 - } else { 205 - // log::warn!("Cache miss!"); 206 - let chunk = match Chunk::load_from_file(map_pos, conn)? { 207 - Some(chunk) => chunk, 208 - None => { 209 - let new_chunk = Chunk::generate(map_pos, ChunkScramble::Normal); 210 - new_chunk.save(map_pos, conn)?; 211 - new_chunk 212 - } 213 - }; 214 - #[cfg(not(target_arch = "wasm32"))] 215 - CHUNK_FILE_CACHE.lock().unwrap().insert(map_pos, chunk); 216 - Ok(chunk) 217 - } 276 + let chunk = Chunk::generate(map_pos, ChunkScramble::Normal); 277 + Ok(chunk) 278 + 279 + // if cached { 280 + // // log::warn!("Cache hit!"); 281 + // #[cfg(not(target_arch = "wasm32"))] 282 + // //return Ok(CHUNK_FILE_CACHE.lock().unwrap()[&map_pos]); 283 + // #[cfg(target_arch = "wasm32")] 284 + // return unreachable!(); 285 + // } else { 286 + // } 218 287 } 219 288 } 220 289
+81 -34
src/world/map.rs
··· 1 - use std::collections::HashMap; 1 + use std::{ 2 + collections::HashMap, 3 + sync::{mpsc::Receiver, Arc, RwLock}, 4 + time::Duration, 5 + }; 2 6 3 7 use crate::ConnectionOnlyOnNative; 4 8 ··· 11 15 Rng, 12 16 }; 13 17 use rusqlite::Connection; 18 + use tokio::{ 19 + sync::mpsc::{error::TryRecvError, unbounded_channel, UnboundedReceiver, UnboundedSender}, 20 + time::sleep, 21 + }; 14 22 15 23 #[derive(Copy, Clone, Default, Encode, Decode, PartialEq)] 16 24 #[repr(u32)] ··· 33 41 } 34 42 } 35 43 36 - #[derive(Default)] 44 + enum LoadNotify { 45 + Loaded(IVec3), 46 + } 47 + 37 48 pub struct WorldMap { 38 - pub chunks: HashMap<IVec3, Chunk>, 39 - pub focus: IVec3, 49 + chunk_cache: Arc<RwLock<HashMap<IVec3, Chunk>>>, 50 + request_tx: UnboundedSender<IVec3>, 51 + pub response_rx: Receiver<IVec3>, 40 52 } 41 53 42 54 impl WorldMap { 43 - pub fn chunks_around_focus( 44 - &mut self, 45 - conn: &mut Connection, 46 - radius: i32, 47 - ) -> Vec<(IVec3, Chunk)> { 48 - let IVec3 { x, y, z } = self.focus - radius; 49 - let IVec3 { 50 - x: xp, 51 - y: yp, 52 - z: zp, 53 - } = self.focus + radius; 55 + pub fn new() -> Self { 56 + let (request_tx, mut request_rx) = unbounded_channel::<IVec3>(); 57 + let (response_tx, response_rx) = std::sync::mpsc::channel::<IVec3>(); 58 + let chunk_cache = Arc::new(RwLock::new(HashMap::<IVec3, Chunk>::new())); 59 + 60 + let cache_handle = chunk_cache.clone(); 61 + tokio::spawn(async move { 62 + let mut conn = rusqlite::Connection::open("./save.sqlite").unwrap(); 63 + 64 + while let Some(index) = request_rx.recv().await { 65 + sleep(Duration::from_millis(100)).await; 66 + //println!("idx {index}!"); 67 + 68 + // load chunk i guess. 69 + 70 + // Already cached, don't bother. 71 + if cache_handle.read().expect("poisoned").contains_key(&index) { 72 + println!("MAP: cache hit"); 73 + continue; 74 + } 75 + println!("MAP: got {index}"); 76 + 77 + let chunk = Chunk::load(index, &mut conn).expect("oh no."); 78 + 79 + cache_handle.write().expect("poisoned").insert(index, chunk); 80 + response_tx.send(index).expect("channel killed"); 81 + } 82 + }); 83 + 84 + Self { 85 + chunk_cache, 86 + request_tx, 87 + response_rx, 88 + } 89 + } 54 90 55 - itertools::iproduct!(x..=xp, y..=yp, z..=zp) 56 - .map(|(i, j, k)| { 57 - let index = ivec3(i, j, k); 58 - (index, self.fetch_chunk(conn, index)) 59 - }) 60 - .collect() 91 + pub fn get_chunk(&mut self, index: IVec3) -> Option<Chunk> { 92 + self.request_chunk_load(index); 93 + let cache = self.chunk_cache.read().expect("poisoned"); 94 + cache.get(&index).cloned() 61 95 } 62 96 63 - fn fetch_chunk(&mut self, conn: &mut Connection, index: IVec3) -> Chunk { 64 - let cnk = self.chunks.entry(index); 65 - let cnk = cnk.or_insert_with(|| Chunk::load(index, conn).unwrap()); // This sucks. Asyncing this would be much nicer. 97 + // pub fn try_chunk(&mut self) -> Option<IVec3> { 98 + // match self.response_rx.try_recv() { 99 + // Ok(idx) => Some(idx), 100 + // Err(TryRecvError::Empty) => { 101 + // println!("empy"); 102 + // None 103 + // } 104 + // Err(_) => panic!("aah!"), 105 + // } 106 + // } 66 107 67 - cnk.clone() // This sucks. 108 + fn poll_chunk(&mut self, index: IVec3) -> bool { 109 + let cache = self.chunk_cache.read().expect("poisoned"); 110 + cache.contains_key(&index) 68 111 } 69 112 70 - pub fn save( 71 - &self, 72 - conn: &mut ConnectionOnlyOnNative, 73 - ) -> Result<(), Box<dyn std::error::Error>> { 74 - for (pos, chunk) in self.chunks.clone() { 75 - // Eww a clone 76 - chunk.save(pos, conn)?; 77 - } 78 - Ok(()) 113 + pub fn request_chunk_load(&self, index: IVec3) { 114 + let _ = self.request_tx.send(index); 79 115 } 116 + 117 + // pub fn save( 118 + // &self, 119 + // conn: &mut ConnectionOnlyOnNative, 120 + // ) -> Result<(), Box<dyn std::error::Error>> { 121 + // for (pos, chunk) in self.chunks.clone() { 122 + // // Eww a clone 123 + // chunk.save(pos, conn)?; 124 + // } 125 + // Ok(()) 126 + // } 80 127 } 81 128 82 129 pub(crate) const RENDER_GRID_SIZE: usize = 9;