WebGPU Voxel Game

Multithread the chunk loader

+314 -98
+48 -1
Cargo.lock
··· 1 1 # This file is automatically @generated by Cargo. 2 2 # It is not intended for manual editing. 3 - version = 3 3 + version = 4 4 4 5 5 [[package]] 6 6 name = "ab_glyph" ··· 275 275 "rand", 276 276 "reqwest", 277 277 "rollgrid", 278 + "rusqlite", 278 279 "thiserror 2.0.11", 279 280 "tobj", 280 281 "wasm-bindgen", ··· 708 709 ] 709 710 710 711 [[package]] 712 + name = "fallible-iterator" 713 + version = "0.3.0" 714 + source = "registry+https://github.com/rust-lang/crates.io-index" 715 + checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 716 + 717 + [[package]] 718 + name = "fallible-streaming-iterator" 719 + version = "0.1.9" 720 + source = "registry+https://github.com/rust-lang/crates.io-index" 721 + checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 722 + 723 + [[package]] 711 724 name = "fastrand" 712 725 version = "2.3.0" 713 726 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1047 1060 checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 1048 1061 dependencies = [ 1049 1062 "foldhash", 1063 + ] 1064 + 1065 + [[package]] 1066 + name = "hashlink" 1067 + version = "0.10.0" 1068 + source = "registry+https://github.com/rust-lang/crates.io-index" 1069 + checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 1070 + dependencies = [ 1071 + "hashbrown", 1050 1072 ] 1051 1073 1052 1074 [[package]] ··· 1473 1495 ] 1474 1496 1475 1497 [[package]] 1498 + name = "libsqlite3-sys" 1499 + version = "0.32.0" 1500 + source = "registry+https://github.com/rust-lang/crates.io-index" 1501 + checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" 1502 + dependencies = [ 1503 + "cc", 1504 + "pkg-config", 1505 + "vcpkg", 1506 + ] 1507 + 1508 + [[package]] 1476 1509 name = "linux-raw-sys" 1477 1510 version = "0.4.15" 1478 1511 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2316 2349 checksum = "be0228715d4e911eadcd3df048512222f53fc3df6d4fb5e7e989b7b3a89019d5" 2317 2350 dependencies = [ 2318 2351 "panicmsg", 2352 + ] 2353 + 2354 + [[package]] 2355 + name = "rusqlite" 2356 + version = "0.34.0" 2357 + source = "registry+https://github.com/rust-lang/crates.io-index" 2358 + checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" 2359 + dependencies = [ 2360 + "bitflags 2.8.0", 2361 + "fallible-iterator", 2362 + "fallible-streaming-iterator", 2363 + "hashlink", 2364 + "libsqlite3-sys", 2365 + "smallvec", 2319 2366 ] 2320 2367 2321 2368 [[package]]
+2 -1
Cargo.toml
··· 35 35 36 36 [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 37 37 rand = "0.9.0" 38 + rusqlite = { version = "0.34.0", features = ["bundled"] } 38 39 39 40 [target.'cfg(target_arch = "wasm32")'.dependencies] 40 41 console_error_panic_hook = "0.1.6" ··· 50 51 "Storage" 51 52 ] } 52 53 reqwest = "0.12" 53 - instant = { version = "0.1", features = [ "wasm-bindgen" ] } 54 + instant = { version = "0.1", features = ["wasm-bindgen"] }
+67 -9
src/app.rs
··· 1 + use crate::world::chunk::Chunk; 1 2 use crate::{ 3 + concurrency::GameThread, 2 4 gfx::{Gfx, GfxBuilder, MaybeGfx}, 3 5 gui::EguiRenderer, 4 - world::map::new, 5 - world::World, 6 + world::{map::new, World}, 7 + ConnectionOnlyOnNative, 6 8 }; 7 - use glam::dvec2; 8 - use std::sync::Arc; 9 + use glam::{dvec2, ivec3, IVec3}; 10 + use std::sync::mpsc::channel; 11 + use std::sync::{Arc, Mutex, TryLockResult}; 12 + use std::thread::spawn; 9 13 use winit::{ 10 14 application::ApplicationHandler, 11 15 event::{DeviceEvent, DeviceId, ElementState, KeyEvent, WindowEvent}, ··· 26 30 gfx_state: MaybeGfx, 27 31 window: Option<Arc<Window>>, 28 32 egui: Option<EguiRenderer>, 29 - world: World, 33 + world: Arc<Mutex<World>>, 34 + world_update_thread: GameThread<(), (i32, i32, i32)>, 30 35 last_render_time: instant::Instant, 36 + conn: ConnectionOnlyOnNative, 31 37 } 32 38 33 39 impl Application { 34 - pub fn new(event_loop: &EventLoop<Gfx>, title: &str) -> Self { 40 + pub fn new(event_loop: &EventLoop<Gfx>, title: &str, mut conn: rusqlite::Connection) -> Self { 41 + let world = Arc::new(Mutex::new(World { 42 + map: new(), 43 + remake: false, 44 + })); 35 45 Self { 36 46 window_attributes: Window::default_attributes().with_title(title), 37 47 gfx_state: MaybeGfx::Builder(GfxBuilder::new(event_loop.create_proxy())), 38 48 window: None, 39 49 egui: None, 40 - world: World { map: new() }, 50 + world: world.clone(), 51 + world_update_thread: { 52 + let (tx, rx) = channel(); 53 + 54 + let thread = spawn(move || { 55 + let w = world; 56 + let mut conn = rusqlite::Connection::open("./save.sqlite").unwrap(); 57 + 58 + loop { 59 + println!("Looping!"); 60 + 61 + let Ok((x, y, z)) = rx.recv() else { 62 + break; 63 + }; 64 + 65 + let Ok(ref mut w) = w.lock() else { 66 + log::error!("Poisoned mutex?"); 67 + break; 68 + }; 69 + 70 + w.map.chunks.reposition( 71 + IVec3::from((x, y, z)).into(), 72 + |_old, new, chunk| { 73 + *chunk = 74 + Chunk::load(ivec3(new.0, new.1, new.2), &mut conn).unwrap(); 75 + }, 76 + ); 77 + 78 + w.remake = true; 79 + dbg!(&w.map.chunks.offset()); 80 + } 81 + }); 82 + 83 + GameThread::new(thread, tx) 84 + }, 41 85 last_render_time: instant::Instant::now(), 86 + conn, 42 87 } 43 88 } 44 89 } ··· 204 249 let dt = now - self.last_render_time; 205 250 self.last_render_time = now; 206 251 252 + // let mut world = self.world.lock().unwrap(); 253 + 207 254 window.request_redraw(); 208 - match gfx.render(&mut self.egui, window.clone(), &mut self.world, dt) { 255 + match gfx.render( 256 + &mut self.egui, 257 + window.clone(), 258 + self.world.clone(), 259 + &mut self.conn, 260 + dt, 261 + ) { 209 262 Ok(_) => { 210 263 // TODO CITE https://github.com/kaphula/winit-egui-wgpu-template/blob/master/src/app.rs#L3 211 - gfx.update(&mut self.world, dt); 264 + gfx.update( 265 + self.world.clone(), 266 + &mut self.conn, 267 + &mut self.world_update_thread, 268 + dt, 269 + ); 212 270 } 213 271 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { 214 272 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 + }
+20 -11
src/gfx.rs
··· 4 4 mod resources; 5 5 mod texture; 6 6 7 + use crate::concurrency::GameThread; 7 8 use crate::gfx::camera::CameraUniform; 8 9 use crate::gfx::model::DrawLight; 9 10 use crate::world::chunk::{sl3get, sl3get_opt, CHUNK_SIZE}; ··· 13 14 gui::EguiRenderer, 14 15 world::map::{BlockKind, WorldMap}, 15 16 world::World, 16 - Instance, InstanceRaw, 17 + ConnectionOnlyOnNative, Instance, InstanceRaw, 17 18 }; 18 19 use egui_wgpu::ScreenDescriptor; 19 20 use glam::{uvec2, vec3, IVec3, Quat, Vec3}; 20 21 use std::f32::consts::{FRAC_PI_2, FRAC_PI_3}; 21 - use std::sync::Arc; 22 + use std::sync::mpsc::{channel, Receiver, Sender}; 23 + use std::sync::{Arc, Mutex}; 22 24 use wgpu::util::DeviceExt; 23 25 use wgpu::BindingResource; 24 26 use winit::{ ··· 46 48 pub model: model::Model, 47 49 pub instances: Vec<Instance>, 48 50 pub instance_buffer: wgpu::Buffer, 49 - pub remake: bool, 50 51 } 51 52 52 53 pub struct LightState { ··· 624 625 model: obj_model, 625 626 instances, 626 627 instance_buffer, 627 - remake: false, 628 628 }, 629 629 light: LightState { 630 630 object: light, ··· 754 754 }); 755 755 self.object.instances = instances; 756 756 self.object.instance_buffer = instance_buffer; 757 - self.object.remake = false; 758 757 } 759 758 760 759 pub(crate) fn render( 761 760 &mut self, 762 761 egui: &mut Option<EguiRenderer>, 763 762 window: Arc<Window>, 764 - world: &mut World, 763 + world: Arc<Mutex<World>>, 764 + conn: &mut ConnectionOnlyOnNative, 765 765 dt: instant::Duration, 766 766 ) -> Result<(), wgpu::SurfaceError> { 767 767 let output = self.surface.get_current_texture()?; ··· 889 889 }; 890 890 egui.begin_frame(&window); 891 891 892 - egui.update(self, world, dt); 892 + egui.update(self, &mut world.lock().unwrap(), conn, dt); 893 893 894 894 egui.end_frame_and_draw( 895 895 &self.device, ··· 907 907 Ok(()) 908 908 } 909 909 910 - pub fn update(&mut self, world: &mut World, dt: instant::Duration) { 910 + pub fn update( 911 + &mut self, 912 + world: Arc<Mutex<World>>, 913 + conn: &mut ConnectionOnlyOnNative, 914 + thread: &mut GameThread<(), (i32, i32, i32)>, 915 + dt: instant::Duration, 916 + ) { 911 917 // Camera update 912 918 self.camera.controller.update_camera( 913 919 &mut self.camera.object, 914 920 dt, 915 - world, 916 - &mut self.object.remake, 921 + world.clone(), 922 + conn, 923 + thread, 917 924 ); 918 925 self.camera 919 926 .uniform ··· 940 947 bytemuck::cast_slice(&[self.light.uniform]), 941 948 ); 942 949 950 + let mut world = world.lock().unwrap(); 943 951 // Object update 944 - if self.object.remake { 952 + if world.remake { 945 953 self.update_instance_buf(&world.map); 954 + world.remake = false; 946 955 } 947 956 } 948 957
+24 -18
src/gfx/camera.rs
··· 1 + use crate::concurrency::GameThread; 2 + use crate::world::{chunk::Chunk, map::RENDER_GRID_SIZE, World}; 3 + use crate::ConnectionOnlyOnNative; 1 4 use glam::{ivec3, vec3, IVec3, Mat4, Vec2, Vec3, Vec4}; 2 5 use instant::Duration; 3 6 use itertools::Itertools; 7 + use std::sync::{Arc, Mutex}; 4 8 use winit::{ 5 9 dpi::PhysicalPosition, 6 10 event::{ElementState, MouseScrollDelta}, 7 11 keyboard::KeyCode, 8 12 }; 9 - 10 - use crate::world::{chunk::Chunk, map::RENDER_GRID_SIZE, World}; 11 - 12 13 13 14 const MAX_CAMERA_PITCH: f32 = (3.0 / std::f32::consts::PI) - 0.0001; 14 15 ··· 118 119 scroll: 0., 119 120 speed, 120 121 sensitivity, 121 - load_chunks: true, 122 + load_chunks: false, 122 123 } 123 124 } 124 125 ··· 162 163 &mut self, 163 164 camera: &mut Camera, 164 165 duration: Duration, 165 - world: &mut World, 166 - remake: &mut bool, 166 + world: Arc<Mutex<World>>, 167 + conn: &mut ConnectionOnlyOnNative, 168 + world_thread: &mut GameThread<(), (i32, i32, i32)>, 167 169 ) { 168 170 let dt = duration.as_secs_f32(); 169 171 let movement = self.movement.vec3(); ··· 188 190 189 191 if self.load_chunks { 190 192 const BLOCK_UNIT_SIZE: i32 = 32; 191 - let chunk_relative = IVec3::from( 192 - (camera.position.x as i32 / BLOCK_UNIT_SIZE, 193 + let chunk_relative = IVec3::from(( 194 + camera.position.x as i32 / BLOCK_UNIT_SIZE, 193 195 camera.position.y as i32 / BLOCK_UNIT_SIZE, 194 196 camera.position.z as i32 / BLOCK_UNIT_SIZE, 195 - )) + IVec3::splat(-(RENDER_GRID_SIZE as i32/2)); 196 - if chunk_relative != world.map.chunks.offset().into() { 197 - world 198 - .map 199 - .chunks 200 - .reposition((IVec3::from(chunk_relative)).into(), |_old, new, chunk| { 201 - *chunk = Chunk::load(ivec3(new.0, new.1, new.2)).unwrap(); 202 - }); 203 - *remake = true; 197 + )) + IVec3::splat(-(RENDER_GRID_SIZE as i32 / 2)); 198 + if chunk_relative != world.lock().unwrap().map.chunks.offset().into() { 199 + let IVec3 { x, y, z } = chunk_relative; 200 + world_thread.tx.send((x, y, z)).unwrap(); 204 201 } 202 + 203 + // if chunk_relative != world.map.chunks.offset().into() { 204 + // world.map.chunks.reposition( 205 + // (IVec3::from(chunk_relative)).into(), 206 + // |_old, new, chunk| { 207 + // *chunk = Chunk::load(ivec3(new.0, new.1, new.2), conn).unwrap(); 208 + // }, 209 + // ); 210 + // *remake = true; 211 + // } 205 212 } 206 213 207 214 self.rotation = Vec2::ZERO; ··· 235 242 self.view_proj = projection.mat4() * camera.mat4(); 236 243 } 237 244 } 238 -
+40 -20
src/gui.rs
··· 1 + use std::thread; 2 + 1 3 use egui::{FontId, RichText}; 2 4 use egui_winit::EventResponse; 3 5 use glam::ivec3; ··· 9 11 chunk::{Chunk, ChunkScramble}, 10 12 World, 11 13 }, 14 + ConnectionOnlyOnNative, 12 15 }; 13 16 14 17 const FPS_AVG_WINDOW: usize = 120; ··· 146 149 self.frame_started = false; 147 150 } 148 151 149 - pub fn update(&mut self, gfx: &mut Gfx, world: &mut World, dt: instant::Duration) { 152 + pub fn update( 153 + &mut self, 154 + gfx: &mut Gfx, 155 + world: &mut World, 156 + conn: &mut ConnectionOnlyOnNative, 157 + dt: instant::Duration, 158 + ) { 150 159 let mut scale_factor = self.scale_factor; 151 160 let (mut chunk_x, mut chunk_y, mut chunk_z) = self.chunk_influence; 152 161 let (mut grid_x, mut grid_y, mut grid_z) = world.map.chunks.offset(); ··· 251 260 ui.checkbox(&mut camera_load, "Camera position loads chunks"); 252 261 ui.label("Move chunk window... "); 253 262 ui.horizontal(|ui| { 254 - ui.add_enabled(!camera_load, egui::DragValue::new(&mut grid_x).speed(0.1).update_while_editing(false)); 263 + ui.add_enabled( 264 + !camera_load, 265 + egui::DragValue::new(&mut grid_x) 266 + .speed(0.1) 267 + .update_while_editing(false), 268 + ); 255 269 ui.label("x "); 256 270 257 - ui.add_enabled(!camera_load, egui::DragValue::new(&mut grid_y).speed(0.1).update_while_editing(false)); 271 + ui.add_enabled( 272 + !camera_load, 273 + egui::DragValue::new(&mut grid_y) 274 + .speed(0.1) 275 + .update_while_editing(false), 276 + ); 258 277 ui.label("y "); 259 278 260 - ui.add_enabled(!camera_load, egui::DragValue::new(&mut grid_z).speed(0.1).update_while_editing(false)); 279 + ui.add_enabled( 280 + !camera_load, 281 + egui::DragValue::new(&mut grid_z) 282 + .speed(0.1) 283 + .update_while_editing(false), 284 + ); 261 285 ui.label("z."); 262 286 }); 263 287 ··· 281 305 if ui.button("Random").clicked() { 282 306 let c = Chunk::generate(pos, ChunkScramble::Random); 283 307 world.map.chunks.set(pos.into(), c); 284 - gfx.object.remake = true; 308 + world.remake = true; 285 309 } 286 310 if ui.button("Normal").clicked() { 287 311 let c = Chunk::generate(pos, ChunkScramble::Normal); 288 312 world.map.chunks.set(pos.into(), c); 289 - gfx.object.remake = true; 313 + world.remake = true; 290 314 } 291 315 if ui.button("Inverse").clicked() { 292 316 let c = Chunk::generate(pos, ChunkScramble::Inverse); 293 317 world.map.chunks.set(pos.into(), c); 294 - gfx.object.remake = true; 318 + world.remake = true; 295 319 } 296 320 }); 297 321 ··· 299 323 300 324 ui.horizontal(|ui| { 301 325 if ui.button("Save").clicked() { 302 - world.save().unwrap(); 326 + world.save(conn).unwrap(); 303 327 } 304 328 }); 305 329 }); ··· 309 333 310 334 gfx.camera.controller.load_chunks = camera_load; 311 335 312 - if !camera_load { 313 - if (grid_x, grid_y, grid_z) != world.map.chunks.offset() { 314 - world 315 - .map 316 - .chunks 317 - .reposition((grid_x, grid_y, grid_z), |_old, new, chunk| { 318 - *chunk = Chunk::load(ivec3(new.0, new.1, new.2)).unwrap(); 319 - }); 320 - gfx.object.remake = true; 321 - } 336 + if !camera_load && (grid_x, grid_y, grid_z) != world.map.chunks.offset() { 337 + world 338 + .map 339 + .chunks 340 + .reposition((grid_x, grid_y, grid_z), |_old, new, chunk| { 341 + *chunk = Chunk::load(ivec3(new.0, new.1, new.2), conn).unwrap(); 342 + }); 343 + world.remake = true; 322 344 } 323 - 324 - 325 345 } 326 346 }
+25 -4
src/lib.rs
··· 1 1 #![allow(rust_analyzer::inactive_code)] 2 2 3 3 mod app; 4 + mod concurrency; 4 5 mod gfx; 5 6 mod gui; 6 7 mod world; ··· 17 18 rotation: Quat, 18 19 } 19 20 21 + #[cfg(not(target_arch = "wasm32"))] 22 + type ConnectionOnlyOnNative = rusqlite::Connection; 23 + 24 + #[cfg(target_arch = "wasm32")] 25 + type ConnectionOnlyOnNative = (); 26 + 20 27 impl Instance { 21 28 fn to_raw(&self) -> InstanceRaw { 22 29 InstanceRaw { ··· 36 43 37 44 impl InstanceRaw { 38 45 fn desc() -> wgpu::VertexBufferLayout<'static> { 39 - 40 - 41 46 wgpu::VertexBufferLayout { 42 47 array_stride: size_of::<InstanceRaw>() as wgpu::BufferAddress, 43 48 step_mode: wgpu::VertexStepMode::Instance, ··· 98 103 init_logger(); 99 104 100 105 log::info!("Hello world!"); 101 - preload_chunk_cache(); 106 + // preload_chunk_cache(); 107 + 108 + let conn = rusqlite::Connection::open("./save.sqlite").unwrap(); 109 + conn.execute( 110 + r#" 111 + CREATE TABLE IF NOT EXISTS chunks ( 112 + x INTEGER, 113 + y INTEGER, 114 + z INTEGER, 115 + data BLOB, 116 + PRIMARY KEY (x,y,z) 117 + ) 118 + "#, 119 + (), 120 + ) 121 + .unwrap(); 122 + 102 123 let event_loop = EventLoop::with_user_event().build().unwrap_throw(); 103 124 104 - let mut app = app::Application::new(&event_loop, "BL0CK"); 125 + let mut app = app::Application::new(&event_loop, "BL0CK", conn); 105 126 event_loop.run_app(&mut app).unwrap(); 106 127 }
+10 -5
src/world.rs
··· 1 - pub(crate) mod map; 2 - pub(crate) mod encoded; 3 1 pub(crate) mod chunk; 2 + pub(crate) mod encoded; 3 + pub(crate) mod map; 4 4 5 5 use bincode::{Decode, Encode}; 6 6 7 + use crate::ConnectionOnlyOnNative; 7 8 use glam::ivec3; 8 9 use map::WorldMap; 9 10 10 11 #[derive(Encode, Decode)] 11 12 pub struct World { 12 13 pub map: WorldMap, 14 + pub remake: bool, 13 15 } 14 16 impl World { 15 - pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> { 16 - for ((x,y,z), chunk) in self.map.chunks.iter() { 17 - chunk.save(ivec3(x,y,z))?; 17 + pub fn save( 18 + &self, 19 + conn: &mut ConnectionOnlyOnNative, 20 + ) -> Result<(), Box<dyn std::error::Error>> { 21 + for ((x, y, z), chunk) in self.map.chunks.iter() { 22 + chunk.save(ivec3(x, y, z), conn)?; 18 23 } 19 24 Ok(()) 20 25 }
+56 -27
src/world/chunk.rs
··· 1 1 use super::map::BlockKind; 2 + use crate::ConnectionOnlyOnNative; 2 3 use bincode::{Decode, Encode}; 3 4 use glam::{ivec3, IVec3}; 4 5 use itertools::Itertools; ··· 98 99 Chunk::generate_callback(method)(map_pos) 99 100 } 100 101 101 - pub fn save(&self, map_pos: IVec3) -> Result<(), Box<dyn std::error::Error>> { 102 + pub fn save( 103 + &self, 104 + map_pos: IVec3, 105 + conn: &mut ConnectionOnlyOnNative, 106 + ) -> Result<(), Box<dyn std::error::Error>> { 102 107 let config = bincode::config::standard(); 103 108 104 - let file_hash = calculate_hash(&map_pos); 105 - let file_name = format!("chunk_{}.bl0ck", file_hash); 106 - 107 109 #[cfg(not(target_arch = "wasm32"))] 108 110 { 109 - let file_path = Path::new("./save/chunk/").join(Path::new(&file_name)); 110 - let mut file = File::create(&file_path).unwrap(); 111 - let encoded = bincode::encode_into_std_write(self, &mut file, config)?; 111 + let encoded = bincode::encode_to_vec(self, config)?; 112 112 113 - log::warn!("Wrote to file {file_name} with {encoded}b."); 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))?; 114 120 } 115 121 116 122 // We are going to use LocalStorage for web. I don't like it either. 117 123 #[cfg(target_arch = "wasm32")] 118 124 { 119 - use base64::prelude::{BASE64_STANDARD, Engine}; 125 + use base64::prelude::{Engine, BASE64_STANDARD}; 120 126 let encoded = bincode::encode_to_vec(self, config)?; 121 127 let encoded = BASE64_STANDARD.encode(encoded); 122 128 ··· 125 131 } 126 132 Ok(()) 127 133 } 128 - fn load_from_file(map_pos: IVec3) -> Result<Option<Chunk>, Box<dyn std::error::Error>> { 134 + fn load_from_file( 135 + map_pos: IVec3, 136 + conn: &mut ConnectionOnlyOnNative, 137 + ) -> Result<Option<Chunk>, Box<dyn std::error::Error>> { 129 138 let config = bincode::config::standard(); 130 139 let file_hash = calculate_hash(&map_pos); 131 140 let file_name = format!("chunk_{}.bl0ck", file_hash); 132 141 133 142 #[cfg(not(target_arch = "wasm32"))] 134 143 { 135 - let file_path = Path::new("./save/chunk/").join(Path::new(&file_name)); 136 - if file_path.exists() { 137 - log::warn!("Load Chunk!"); 138 - let mut file = File::open(file_path).unwrap(); 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(); 148 + 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 + }; 139 165 140 - let decoded = bincode::decode_from_std_read(&mut file, config)?; 166 + let (decoded, _) = bincode::decode_from_slice(i.as_slice(), config)?; 141 167 142 - Ok(Some(decoded)) 143 - } else { 144 - log::warn!("Chunk not loaded!"); 145 - Ok(None) 146 - } 168 + Ok(Some(decoded)) 169 + // } else { 170 + // log::warn!("Chunk not loaded!"); 171 + // Ok(None) 172 + // } 147 173 } 148 174 #[cfg(target_arch = "wasm32")] 149 175 { 150 - use base64::prelude::{BASE64_STANDARD, Engine}; 176 + use base64::prelude::{Engine, BASE64_STANDARD}; 151 177 let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 152 178 if let Ok(Some(s)) = store.get(&file_name) { 153 179 let s = BASE64_STANDARD.decode(s)?; ··· 160 186 } 161 187 } 162 188 163 - pub fn load(map_pos: IVec3) -> Result<Chunk, Box<dyn std::error::Error>> { 189 + pub fn load( 190 + map_pos: IVec3, 191 + conn: &mut ConnectionOnlyOnNative, 192 + ) -> Result<Chunk, Box<dyn std::error::Error>> { 164 193 #[cfg(not(target_arch = "wasm32"))] 165 194 let cached = CHUNK_FILE_CACHE.lock().unwrap().contains_key(&map_pos); 166 195 #[cfg(target_arch = "wasm32")] 167 196 let cached = false; 168 197 169 198 if cached { 170 - log::warn!("Cache hit!"); 199 + // log::warn!("Cache hit!"); 171 200 #[cfg(not(target_arch = "wasm32"))] 172 201 return Ok(CHUNK_FILE_CACHE.lock().unwrap()[&map_pos]); 173 202 #[cfg(target_arch = "wasm32")] 174 203 return unreachable!(); 175 204 } else { 176 - log::warn!("Cache miss!"); 177 - let chunk = match Chunk::load_from_file(map_pos)? { 205 + // log::warn!("Cache miss!"); 206 + let chunk = match Chunk::load_from_file(map_pos, conn)? { 178 207 Some(chunk) => chunk, 179 208 None => { 180 209 let new_chunk = Chunk::generate(map_pos, ChunkScramble::Normal); 181 - new_chunk.save(map_pos)?; 210 + new_chunk.save(map_pos, conn)?; 182 211 new_chunk 183 212 } 184 213 }; ··· 207 236 let _3diter = itertools::iproduct!(-r..r, -r..r, -r..r); 208 237 209 238 for (x, y, z) in _3diter { 210 - let _ = Chunk::load(ivec3(x, y, z)); 239 + // let _ = Chunk::load(ivec3(x, y, z)); 211 240 } 212 241 } 213 242 }
+5 -2
src/world/map.rs
··· 1 1 use super::chunk::Chunk; 2 + use crate::ConnectionOnlyOnNative; 2 3 use bincode::{Decode, Encode}; 3 4 use glam::ivec3; 4 5 #[cfg(not(target_arch = "wasm32"))] ··· 32 33 pub struct WorldMap { 33 34 pub chunks: RollGrid3D<Chunk>, 34 35 } 35 - pub(crate) const RENDER_GRID_SIZE: usize = 15; 36 + pub(crate) const RENDER_GRID_SIZE: usize = 9; 36 37 pub fn new() -> WorldMap { 38 + let mut conn = rusqlite::Connection::open("./save.sqlite").unwrap(); 39 + 37 40 WorldMap { 38 41 chunks: RollGrid3D::new( 39 42 ( ··· 42 45 RENDER_GRID_SIZE as _, 43 46 ), 44 47 (0, 0, 0), 45 - |(x, y, z)| Chunk::load(ivec3(x, y, z)).unwrap(), 48 + |(x, y, z)| Chunk::load(ivec3(x, y, z), &mut conn).unwrap(), 46 49 ), 47 50 } 48 51 }