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