tangled
alpha
login
or
join now
j0.lol
/
bl0ck
0
fork
atom
WebGPU Voxel Game
0
fork
atom
overview
issues
4
pulls
pipelines
Multithread the chunk loader
j0.lol
1 year ago
ab2d0494
d7160fc4
+314
-98
11 changed files
expand all
collapse all
unified
split
Cargo.lock
Cargo.toml
src
app.rs
concurrency.rs
gfx
camera.rs
gfx.rs
gui.rs
lib.rs
world
chunk.rs
map.rs
world.rs
+48
-1
Cargo.lock
···
1
1
# This file is automatically @generated by Cargo.
2
2
# It is not intended for manual editing.
3
3
-
version = 3
3
3
+
version = 4
4
4
5
5
[[package]]
6
6
name = "ab_glyph"
···
275
275
"rand",
276
276
"reqwest",
277
277
"rollgrid",
278
278
+
"rusqlite",
278
279
"thiserror 2.0.11",
279
280
"tobj",
280
281
"wasm-bindgen",
···
708
709
]
709
710
710
711
[[package]]
712
712
+
name = "fallible-iterator"
713
713
+
version = "0.3.0"
714
714
+
source = "registry+https://github.com/rust-lang/crates.io-index"
715
715
+
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
716
716
+
717
717
+
[[package]]
718
718
+
name = "fallible-streaming-iterator"
719
719
+
version = "0.1.9"
720
720
+
source = "registry+https://github.com/rust-lang/crates.io-index"
721
721
+
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
722
722
+
723
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
1063
+
]
1064
1064
+
1065
1065
+
[[package]]
1066
1066
+
name = "hashlink"
1067
1067
+
version = "0.10.0"
1068
1068
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1069
1069
+
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
1070
1070
+
dependencies = [
1071
1071
+
"hashbrown",
1050
1072
]
1051
1073
1052
1074
[[package]]
···
1473
1495
]
1474
1496
1475
1497
[[package]]
1498
1498
+
name = "libsqlite3-sys"
1499
1499
+
version = "0.32.0"
1500
1500
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1501
1501
+
checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7"
1502
1502
+
dependencies = [
1503
1503
+
"cc",
1504
1504
+
"pkg-config",
1505
1505
+
"vcpkg",
1506
1506
+
]
1507
1507
+
1508
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
2352
+
]
2353
2353
+
2354
2354
+
[[package]]
2355
2355
+
name = "rusqlite"
2356
2356
+
version = "0.34.0"
2357
2357
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2358
2358
+
checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143"
2359
2359
+
dependencies = [
2360
2360
+
"bitflags 2.8.0",
2361
2361
+
"fallible-iterator",
2362
2362
+
"fallible-streaming-iterator",
2363
2363
+
"hashlink",
2364
2364
+
"libsqlite3-sys",
2365
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
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
53
-
instant = { version = "0.1", features = [ "wasm-bindgen" ] }
54
54
+
instant = { version = "0.1", features = ["wasm-bindgen"] }
+67
-9
src/app.rs
···
1
1
+
use crate::world::chunk::Chunk;
1
2
use crate::{
3
3
+
concurrency::GameThread,
2
4
gfx::{Gfx, GfxBuilder, MaybeGfx},
3
5
gui::EguiRenderer,
4
4
-
world::map::new,
5
5
-
world::World,
6
6
+
world::{map::new, World},
7
7
+
ConnectionOnlyOnNative,
6
8
};
7
7
-
use glam::dvec2;
8
8
-
use std::sync::Arc;
9
9
+
use glam::{dvec2, ivec3, IVec3};
10
10
+
use std::sync::mpsc::channel;
11
11
+
use std::sync::{Arc, Mutex, TryLockResult};
12
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
29
-
world: World,
33
33
+
world: Arc<Mutex<World>>,
34
34
+
world_update_thread: GameThread<(), (i32, i32, i32)>,
30
35
last_render_time: instant::Instant,
36
36
+
conn: ConnectionOnlyOnNative,
31
37
}
32
38
33
39
impl Application {
34
34
-
pub fn new(event_loop: &EventLoop<Gfx>, title: &str) -> Self {
40
40
+
pub fn new(event_loop: &EventLoop<Gfx>, title: &str, mut conn: rusqlite::Connection) -> Self {
41
41
+
let world = Arc::new(Mutex::new(World {
42
42
+
map: new(),
43
43
+
remake: false,
44
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
40
-
world: World { map: new() },
50
50
+
world: world.clone(),
51
51
+
world_update_thread: {
52
52
+
let (tx, rx) = channel();
53
53
+
54
54
+
let thread = spawn(move || {
55
55
+
let w = world;
56
56
+
let mut conn = rusqlite::Connection::open("./save.sqlite").unwrap();
57
57
+
58
58
+
loop {
59
59
+
println!("Looping!");
60
60
+
61
61
+
let Ok((x, y, z)) = rx.recv() else {
62
62
+
break;
63
63
+
};
64
64
+
65
65
+
let Ok(ref mut w) = w.lock() else {
66
66
+
log::error!("Poisoned mutex?");
67
67
+
break;
68
68
+
};
69
69
+
70
70
+
w.map.chunks.reposition(
71
71
+
IVec3::from((x, y, z)).into(),
72
72
+
|_old, new, chunk| {
73
73
+
*chunk =
74
74
+
Chunk::load(ivec3(new.0, new.1, new.2), &mut conn).unwrap();
75
75
+
},
76
76
+
);
77
77
+
78
78
+
w.remake = true;
79
79
+
dbg!(&w.map.chunks.offset());
80
80
+
}
81
81
+
});
82
82
+
83
83
+
GameThread::new(thread, tx)
84
84
+
},
41
85
last_render_time: instant::Instant::now(),
86
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
252
+
// let mut world = self.world.lock().unwrap();
253
253
+
207
254
window.request_redraw();
208
208
-
match gfx.render(&mut self.egui, window.clone(), &mut self.world, dt) {
255
255
+
match gfx.render(
256
256
+
&mut self.egui,
257
257
+
window.clone(),
258
258
+
self.world.clone(),
259
259
+
&mut self.conn,
260
260
+
dt,
261
261
+
) {
209
262
Ok(_) => {
210
263
// TODO CITE https://github.com/kaphula/winit-egui-wgpu-template/blob/master/src/app.rs#L3
211
211
-
gfx.update(&mut self.world, dt);
264
264
+
gfx.update(
265
265
+
self.world.clone(),
266
266
+
&mut self.conn,
267
267
+
&mut self.world_update_thread,
268
268
+
dt,
269
269
+
);
212
270
}
213
271
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
214
272
gfx.resize(window.inner_size());
+17
src/concurrency.rs
···
1
1
+
use std::sync::mpsc::Sender;
2
2
+
use std::thread::JoinHandle;
3
3
+
4
4
+
pub struct GameThread<T, U> {
5
5
+
pub join_handle: JoinHandle<T>,
6
6
+
pub tx: Sender<U>,
7
7
+
}
8
8
+
9
9
+
impl<T, U> GameThread<T, U> {
10
10
+
pub fn new(join_handle: JoinHandle<T>, tx: Sender<U>) -> Self {
11
11
+
GameThread { join_handle, tx }
12
12
+
}
13
13
+
14
14
+
// pub fn fulfil(&mut self, join_handle: JoinHandle<T>) {
15
15
+
// self.join_handle = Some(join_handle);
16
16
+
// }
17
17
+
}
+20
-11
src/gfx.rs
···
4
4
mod resources;
5
5
mod texture;
6
6
7
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
16
-
Instance, InstanceRaw,
17
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
21
-
use std::sync::Arc;
22
22
+
use std::sync::mpsc::{channel, Receiver, Sender};
23
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
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
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
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
764
-
world: &mut World,
763
763
+
world: Arc<Mutex<World>>,
764
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
892
-
egui.update(self, world, dt);
892
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
910
-
pub fn update(&mut self, world: &mut World, dt: instant::Duration) {
910
910
+
pub fn update(
911
911
+
&mut self,
912
912
+
world: Arc<Mutex<World>>,
913
913
+
conn: &mut ConnectionOnlyOnNative,
914
914
+
thread: &mut GameThread<(), (i32, i32, i32)>,
915
915
+
dt: instant::Duration,
916
916
+
) {
911
917
// Camera update
912
918
self.camera.controller.update_camera(
913
919
&mut self.camera.object,
914
920
dt,
915
915
-
world,
916
916
-
&mut self.object.remake,
921
921
+
world.clone(),
922
922
+
conn,
923
923
+
thread,
917
924
);
918
925
self.camera
919
926
.uniform
···
940
947
bytemuck::cast_slice(&[self.light.uniform]),
941
948
);
942
949
950
950
+
let mut world = world.lock().unwrap();
943
951
// Object update
944
944
-
if self.object.remake {
952
952
+
if world.remake {
945
953
self.update_instance_buf(&world.map);
954
954
+
world.remake = false;
946
955
}
947
956
}
948
957
+24
-18
src/gfx/camera.rs
···
1
1
+
use crate::concurrency::GameThread;
2
2
+
use crate::world::{chunk::Chunk, map::RENDER_GRID_SIZE, World};
3
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
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
9
-
10
10
-
use crate::world::{chunk::Chunk, map::RENDER_GRID_SIZE, World};
11
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
121
-
load_chunks: true,
122
122
+
load_chunks: false,
122
123
}
123
124
}
124
125
···
162
163
&mut self,
163
164
camera: &mut Camera,
164
165
duration: Duration,
165
165
-
world: &mut World,
166
166
-
remake: &mut bool,
166
166
+
world: Arc<Mutex<World>>,
167
167
+
conn: &mut ConnectionOnlyOnNative,
168
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
191
-
let chunk_relative = IVec3::from(
192
192
-
(camera.position.x as i32 / BLOCK_UNIT_SIZE,
193
193
+
let chunk_relative = IVec3::from((
194
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
195
-
)) + IVec3::splat(-(RENDER_GRID_SIZE as i32/2));
196
196
-
if chunk_relative != world.map.chunks.offset().into() {
197
197
-
world
198
198
-
.map
199
199
-
.chunks
200
200
-
.reposition((IVec3::from(chunk_relative)).into(), |_old, new, chunk| {
201
201
-
*chunk = Chunk::load(ivec3(new.0, new.1, new.2)).unwrap();
202
202
-
});
203
203
-
*remake = true;
197
197
+
)) + IVec3::splat(-(RENDER_GRID_SIZE as i32 / 2));
198
198
+
if chunk_relative != world.lock().unwrap().map.chunks.offset().into() {
199
199
+
let IVec3 { x, y, z } = chunk_relative;
200
200
+
world_thread.tx.send((x, y, z)).unwrap();
204
201
}
202
202
+
203
203
+
// if chunk_relative != world.map.chunks.offset().into() {
204
204
+
// world.map.chunks.reposition(
205
205
+
// (IVec3::from(chunk_relative)).into(),
206
206
+
// |_old, new, chunk| {
207
207
+
// *chunk = Chunk::load(ivec3(new.0, new.1, new.2), conn).unwrap();
208
208
+
// },
209
209
+
// );
210
210
+
// *remake = true;
211
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
238
-
+40
-20
src/gui.rs
···
1
1
+
use std::thread;
2
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
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
149
-
pub fn update(&mut self, gfx: &mut Gfx, world: &mut World, dt: instant::Duration) {
152
152
+
pub fn update(
153
153
+
&mut self,
154
154
+
gfx: &mut Gfx,
155
155
+
world: &mut World,
156
156
+
conn: &mut ConnectionOnlyOnNative,
157
157
+
dt: instant::Duration,
158
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
254
-
ui.add_enabled(!camera_load, egui::DragValue::new(&mut grid_x).speed(0.1).update_while_editing(false));
263
263
+
ui.add_enabled(
264
264
+
!camera_load,
265
265
+
egui::DragValue::new(&mut grid_x)
266
266
+
.speed(0.1)
267
267
+
.update_while_editing(false),
268
268
+
);
255
269
ui.label("x ");
256
270
257
257
-
ui.add_enabled(!camera_load, egui::DragValue::new(&mut grid_y).speed(0.1).update_while_editing(false));
271
271
+
ui.add_enabled(
272
272
+
!camera_load,
273
273
+
egui::DragValue::new(&mut grid_y)
274
274
+
.speed(0.1)
275
275
+
.update_while_editing(false),
276
276
+
);
258
277
ui.label("y ");
259
278
260
260
-
ui.add_enabled(!camera_load, egui::DragValue::new(&mut grid_z).speed(0.1).update_while_editing(false));
279
279
+
ui.add_enabled(
280
280
+
!camera_load,
281
281
+
egui::DragValue::new(&mut grid_z)
282
282
+
.speed(0.1)
283
283
+
.update_while_editing(false),
284
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
284
-
gfx.object.remake = true;
308
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
289
-
gfx.object.remake = true;
313
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
294
-
gfx.object.remake = true;
318
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
302
-
world.save().unwrap();
326
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
312
-
if !camera_load {
313
313
-
if (grid_x, grid_y, grid_z) != world.map.chunks.offset() {
314
314
-
world
315
315
-
.map
316
316
-
.chunks
317
317
-
.reposition((grid_x, grid_y, grid_z), |_old, new, chunk| {
318
318
-
*chunk = Chunk::load(ivec3(new.0, new.1, new.2)).unwrap();
319
319
-
});
320
320
-
gfx.object.remake = true;
321
321
-
}
336
336
+
if !camera_load && (grid_x, grid_y, grid_z) != world.map.chunks.offset() {
337
337
+
world
338
338
+
.map
339
339
+
.chunks
340
340
+
.reposition((grid_x, grid_y, grid_z), |_old, new, chunk| {
341
341
+
*chunk = Chunk::load(ivec3(new.0, new.1, new.2), conn).unwrap();
342
342
+
});
343
343
+
world.remake = true;
322
344
}
323
323
-
324
324
-
325
345
}
326
346
}
+25
-4
src/lib.rs
···
1
1
#![allow(rust_analyzer::inactive_code)]
2
2
3
3
mod app;
4
4
+
mod concurrency;
4
5
mod gfx;
5
6
mod gui;
6
7
mod world;
···
17
18
rotation: Quat,
18
19
}
19
20
21
21
+
#[cfg(not(target_arch = "wasm32"))]
22
22
+
type ConnectionOnlyOnNative = rusqlite::Connection;
23
23
+
24
24
+
#[cfg(target_arch = "wasm32")]
25
25
+
type ConnectionOnlyOnNative = ();
26
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
39
-
40
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
101
-
preload_chunk_cache();
106
106
+
// preload_chunk_cache();
107
107
+
108
108
+
let conn = rusqlite::Connection::open("./save.sqlite").unwrap();
109
109
+
conn.execute(
110
110
+
r#"
111
111
+
CREATE TABLE IF NOT EXISTS chunks (
112
112
+
x INTEGER,
113
113
+
y INTEGER,
114
114
+
z INTEGER,
115
115
+
data BLOB,
116
116
+
PRIMARY KEY (x,y,z)
117
117
+
)
118
118
+
"#,
119
119
+
(),
120
120
+
)
121
121
+
.unwrap();
122
122
+
102
123
let event_loop = EventLoop::with_user_event().build().unwrap_throw();
103
124
104
104
-
let mut app = app::Application::new(&event_loop, "BL0CK");
125
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
1
-
pub(crate) mod map;
2
2
-
pub(crate) mod encoded;
3
1
pub(crate) mod chunk;
2
2
+
pub(crate) mod encoded;
3
3
+
pub(crate) mod map;
4
4
5
5
use bincode::{Decode, Encode};
6
6
7
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
14
+
pub remake: bool,
13
15
}
14
16
impl World {
15
15
-
pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
16
16
-
for ((x,y,z), chunk) in self.map.chunks.iter() {
17
17
-
chunk.save(ivec3(x,y,z))?;
17
17
+
pub fn save(
18
18
+
&self,
19
19
+
conn: &mut ConnectionOnlyOnNative,
20
20
+
) -> Result<(), Box<dyn std::error::Error>> {
21
21
+
for ((x, y, z), chunk) in self.map.chunks.iter() {
22
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
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
101
-
pub fn save(&self, map_pos: IVec3) -> Result<(), Box<dyn std::error::Error>> {
102
102
+
pub fn save(
103
103
+
&self,
104
104
+
map_pos: IVec3,
105
105
+
conn: &mut ConnectionOnlyOnNative,
106
106
+
) -> Result<(), Box<dyn std::error::Error>> {
102
107
let config = bincode::config::standard();
103
108
104
104
-
let file_hash = calculate_hash(&map_pos);
105
105
-
let file_name = format!("chunk_{}.bl0ck", file_hash);
106
106
-
107
109
#[cfg(not(target_arch = "wasm32"))]
108
110
{
109
109
-
let file_path = Path::new("./save/chunk/").join(Path::new(&file_name));
110
110
-
let mut file = File::create(&file_path).unwrap();
111
111
-
let encoded = bincode::encode_into_std_write(self, &mut file, config)?;
111
111
+
let encoded = bincode::encode_to_vec(self, config)?;
112
112
113
113
-
log::warn!("Wrote to file {file_name} with {encoded}b.");
113
113
+
let mut stmt = conn.prepare_cached(
114
114
+
r#"
115
115
+
INSERT INTO chunks (x,y,z,data)
116
116
+
VALUES (?,?,?,?)
117
117
+
"#,
118
118
+
)?;
119
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
119
-
use base64::prelude::{BASE64_STANDARD, Engine};
125
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
128
-
fn load_from_file(map_pos: IVec3) -> Result<Option<Chunk>, Box<dyn std::error::Error>> {
134
134
+
fn load_from_file(
135
135
+
map_pos: IVec3,
136
136
+
conn: &mut ConnectionOnlyOnNative,
137
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
135
-
let file_path = Path::new("./save/chunk/").join(Path::new(&file_name));
136
136
-
if file_path.exists() {
137
137
-
log::warn!("Load Chunk!");
138
138
-
let mut file = File::open(file_path).unwrap();
144
144
+
// let file_path = Path::new("./save/chunk/").join(Path::new(&file_name));
145
145
+
// if file_path.exists() {
146
146
+
// log::warn!("Load Chunk!");
147
147
+
// let mut file = File::open(file_path).unwrap();
148
148
+
149
149
+
let mut stmt = conn.prepare_cached(
150
150
+
r#"
151
151
+
SELECT (data) from chunks
152
152
+
WHERE (x,y,z) == (?,?,?)
153
153
+
"#,
154
154
+
)?;
155
155
+
let i: Vec<u8> =
156
156
+
match stmt.query_row((map_pos.x, map_pos.y, map_pos.z), |f| f.get("data")) {
157
157
+
Ok(x) => x,
158
158
+
Err(rusqlite::Error::QueryReturnedNoRows) => {
159
159
+
return Ok(None);
160
160
+
}
161
161
+
Err(e) => {
162
162
+
return Err(e.into());
163
163
+
}
164
164
+
};
139
165
140
140
-
let decoded = bincode::decode_from_std_read(&mut file, config)?;
166
166
+
let (decoded, _) = bincode::decode_from_slice(i.as_slice(), config)?;
141
167
142
142
-
Ok(Some(decoded))
143
143
-
} else {
144
144
-
log::warn!("Chunk not loaded!");
145
145
-
Ok(None)
146
146
-
}
168
168
+
Ok(Some(decoded))
169
169
+
// } else {
170
170
+
// log::warn!("Chunk not loaded!");
171
171
+
// Ok(None)
172
172
+
// }
147
173
}
148
174
#[cfg(target_arch = "wasm32")]
149
175
{
150
150
-
use base64::prelude::{BASE64_STANDARD, Engine};
176
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
163
-
pub fn load(map_pos: IVec3) -> Result<Chunk, Box<dyn std::error::Error>> {
189
189
+
pub fn load(
190
190
+
map_pos: IVec3,
191
191
+
conn: &mut ConnectionOnlyOnNative,
192
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
170
-
log::warn!("Cache hit!");
199
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
176
-
log::warn!("Cache miss!");
177
177
-
let chunk = match Chunk::load_from_file(map_pos)? {
205
205
+
// log::warn!("Cache miss!");
206
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
181
-
new_chunk.save(map_pos)?;
210
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
210
-
let _ = Chunk::load(ivec3(x, y, z));
239
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
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
35
-
pub(crate) const RENDER_GRID_SIZE: usize = 15;
36
36
+
pub(crate) const RENDER_GRID_SIZE: usize = 9;
36
37
pub fn new() -> WorldMap {
38
38
+
let mut conn = rusqlite::Connection::open("./save.sqlite").unwrap();
39
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
45
-
|(x, y, z)| Chunk::load(ivec3(x, y, z)).unwrap(),
48
48
+
|(x, y, z)| Chunk::load(ivec3(x, y, z), &mut conn).unwrap(),
46
49
),
47
50
}
48
51
}